Skip to content

Instantly share code, notes, and snippets.

@madeinfree
Created September 10, 2018 05:26
Show Gist options
  • Save madeinfree/0011435e050bacd57b1884cf99d62e28 to your computer and use it in GitHub Desktop.
Save madeinfree/0011435e050bacd57b1884cf99d62e28 to your computer and use it in GitHub Desktop.
/**
* 如果 (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