Created
September 10, 2018 05:26
-
-
Save madeinfree/0011435e050bacd57b1884cf99d62e28 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 如果 (if) | |
* 就 {} | |
* 喊(1) console.log(1) | |
* 不然就 else {} | |
* 喊(2) console.log(2) | |
* parse: if(15 > 10) { console.log(1); } else { console.log(2); } | |
*/ | |
const code = `如果 (15 > 10) 就 喊(1)`; | |
/** | |
* [ | |
* { type: 'name', value: '如果' }, | |
* { type: 'paren', value: '(' }, | |
* { type: 'number', value: 15 }, | |
* { type: 'bth', value: '>' }, | |
* { type: 'number', value: 10 }, | |
* { type: 'paren', value: ')' }, | |
* { type: 'name', value: '就' }, | |
* { type: 'name', value: '喊' }, | |
* { type: 'paren', value: '(' }, | |
* { type: 'number', value: 1 }, | |
* { type: 'paren', value: ')' }, | |
* { type: 'name', value: '不然就' }, | |
* { type: 'name', value: '喊' }, | |
* { type: 'paren', value: '(' }, | |
* { type: 'number', value: 2 }, | |
* { type: 'paren', value: ')' } | |
* ] | |
*/ | |
const tokenizer = input => { | |
// 記錄當下位置 | |
let current = 0; | |
// token 群組 | |
const tokens = []; | |
while (current < input.length) { | |
let char = input[current]; | |
// 跳過空白 | |
if (/\s/.test(char)) { | |
current++; | |
continue; | |
} | |
// 括號 | |
if (char === '(') { | |
tokens.push({ | |
type: 'paren', | |
value: '(' | |
}); | |
current++; | |
continue; | |
} | |
if (char === ')') { | |
tokens.push({ | |
type: 'paren', | |
value: ')' | |
}); | |
current++; | |
continue; | |
} | |
// 抓出如果 | |
if (/如/.test(char) && /果/.test(input[current + 1])) { | |
tokens.push({ | |
type: 'name', | |
value: '如果' | |
}); | |
current = current + 2; | |
continue; | |
} | |
// 抓出就 | |
if (/就/.test(char)) { | |
if (/\s/.test(input[current - 1]) && /\s/.test(input[current + 1])) { | |
tokens.push({ | |
type: 'name', | |
value: '就' | |
}); | |
current++; | |
continue; | |
} | |
} | |
// 抓出不然就 | |
if (/不/.test(char)) { | |
if (/然/.test(input[current + 1]) && /就/.test(input[current + 2])) { | |
tokens.push({ | |
type: 'name', | |
value: '不然就' | |
}); | |
current = current + 3; | |
continue; | |
} | |
} | |
// 抓出喊 | |
if (/喊/.test(char)) { | |
tokens.push({ | |
type: 'name', | |
value: '喊' | |
}); | |
current++; | |
continue; | |
} | |
// 抓出數字 | |
if (/[0-9]/.test(char)) { | |
let number = ''; | |
while (/[0-9]/.test(char)) { | |
number += char; | |
char = input[++current]; | |
} | |
tokens.push({ | |
type: 'number', | |
value: number | |
}); | |
continue; | |
} | |
// 抓出大於 | |
if (/\>|\</.test(char)) { | |
tokens.push({ | |
type: 'bth', | |
value: char | |
}); | |
current++; | |
continue; | |
} | |
throw new Error('看不懂的符號: ' + char); | |
} | |
return tokens; | |
}; | |
/** | |
* parse to AST | |
* 轉為人可讀的訊息 | |
*/ | |
const parser = tokens => { | |
// 計數 | |
let current = 0; | |
const walk = () => { | |
// 抓取 token | |
let token = tokens[current]; | |
// 轉化數字 | |
if (token.type === 'number') { | |
current++; | |
return { | |
type: 'NumberLiteral', | |
value: token.value | |
}; | |
} | |
if (token.type === 'bth') { | |
current++; | |
return { | |
type: 'ThenExpression', | |
value: token.value | |
}; | |
} | |
if (token.type === 'name' && token.value === '如果') { | |
// 跳過如果 | |
token = tokens[++current]; | |
if (token.type === 'paren' && token.value === '(') { | |
// 跳過 `()` | |
token = tokens[++current]; | |
let node = { | |
type: 'ConditionStatement', | |
test: [], | |
consequent: [], | |
other: [] | |
}; | |
while (token.type !== 'paren') { | |
node.test.push(walk()); | |
// loop token | |
token = tokens[current]; | |
} // 如果() | |
// 找到「就」 | |
token = tokens[++current]; | |
if (token && token.type === 'name' && token.value === '就') { | |
token = tokens[++current]; | |
// 跳過「就」 | |
while (token.value === '喊') { | |
node.consequent.push(walk()); | |
token = tokens[current]; | |
} | |
} | |
token = tokens[++current]; | |
if (token && token.type === 'name' && token.value === '不然就') { | |
token = tokens[++current]; | |
while (token.value === '喊') { | |
node.other.push(walk()); | |
token = tokens[current]; | |
} | |
} | |
current++; | |
return node; | |
} | |
} | |
if (token.type === 'name' && token.value === '喊') { | |
// 跳過喊 | |
token = tokens[++current]; | |
if (token.type === 'paren' && token.value === '(') { | |
token = tokens[++current]; | |
// 跳過 `()` | |
let node = { | |
type: 'CallExpression', | |
name: 'log', | |
params: [] | |
}; | |
while (token.type !== 'paren') { | |
node.params.push(walk()); | |
token = tokens[current]; | |
} | |
return node; | |
} | |
} | |
}; | |
let ast = { | |
type: 'Program', | |
body: [] | |
}; | |
while (current < tokens.length) { | |
ast.body.push(walk()); | |
} | |
return ast; | |
}; | |
const traverser = (ast, visitor) => {}; | |
const transformer = ast => {}; | |
const codeGenerator = node => { | |
switch (node.type) { | |
case 'Program': | |
return node.body.map(codeGenerator).join('\n'); | |
case 'ConditionStatement': | |
// console.log(node.other.length); | |
return ( | |
'if (' + | |
node.test.map(codeGenerator).join('') + | |
') {' + | |
node.consequent.map(codeGenerator).join('') + | |
'}' + | |
`${ | |
node.other.length | |
? 'else {' + node.other.map(codeGenerator).join('') + '}' | |
: '' | |
}` | |
); | |
case 'CallExpression': | |
return 'console.log(' + node.params.map(codeGenerator) + ')'; | |
case 'NumberLiteral': | |
return node.value; | |
case 'ThenExpression': | |
return node.value; | |
} | |
}; | |
const compiler = input => { | |
const token = tokenizer(input); | |
const ast = parser(token); | |
const code = codeGenerator(ast); | |
return code; | |
}; | |
const out = compiler(code); | |
eval(compiler('如果 (2 < 5) 就 喊(2) 不然就 喊(3)')); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment