You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
509 lines
6.0 KiB
509 lines
6.0 KiB
'use strict';
|
|
|
|
function tokenizer(input) {
|
|
let current = 0;
|
|
let tokens = [];
|
|
while (current < input.length) {
|
|
let char = input[current];
|
|
if (char === '(') {
|
|
tokens.push({
|
|
type: 'paren',
|
|
value: '(',
|
|
});
|
|
current++;
|
|
continue;
|
|
}
|
|
|
|
if (char === ')') {
|
|
tokens.push({
|
|
type: 'paren',
|
|
value: ')',
|
|
});
|
|
current++;
|
|
continue;
|
|
}
|
|
|
|
let WHITESPACE = /\s/;
|
|
if (WHITESPACE.test(char)) {
|
|
current++;
|
|
continue;
|
|
}
|
|
|
|
let NUMBERS = /[0-9]/;
|
|
if (NUMBERS.test(char)) {
|
|
let value = '';
|
|
|
|
while (NUMBERS.test(char)) {
|
|
value += char;
|
|
char = input[++current];
|
|
}
|
|
|
|
tokens.push({ type: 'number', value });
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (char === '"') {
|
|
|
|
let value = '';
|
|
|
|
|
|
char = input[++current];
|
|
|
|
|
|
|
|
while (char !== '"') {
|
|
value += char;
|
|
char = input[++current];
|
|
}
|
|
|
|
|
|
char = input[++current];
|
|
|
|
|
|
tokens.push({ type: 'string', value });
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let LETTERS = /[a-z]/i;
|
|
if (LETTERS.test(char)) {
|
|
let value = '';
|
|
|
|
|
|
|
|
while (LETTERS.test(char)) {
|
|
value += char;
|
|
char = input[++current];
|
|
}
|
|
|
|
|
|
tokens.push({ type: 'name', value });
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
|
|
throw new TypeError('I dont know what this character is: ' + char);
|
|
}
|
|
|
|
|
|
return tokens;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parser(tokens) {
|
|
|
|
|
|
let current = 0;
|
|
|
|
|
|
|
|
function walk() {
|
|
|
|
|
|
let token = tokens[current];
|
|
|
|
|
|
|
|
|
|
|
|
if (token.type === 'number') {
|
|
|
|
|
|
current++;
|
|
|
|
|
|
|
|
return {
|
|
type: 'NumberLiteral',
|
|
value: token.value,
|
|
};
|
|
}
|
|
|
|
|
|
|
|
if (token.type === 'string') {
|
|
current++;
|
|
|
|
return {
|
|
type: 'StringLiteral',
|
|
value: token.value,
|
|
};
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
token.type === 'paren' &&
|
|
token.value === '('
|
|
) {
|
|
|
|
|
|
|
|
token = tokens[++current];
|
|
|
|
|
|
|
|
|
|
let node = {
|
|
type: 'CallExpression',
|
|
name: token.value,
|
|
params: [],
|
|
};
|
|
|
|
|
|
token = tokens[++current];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (
|
|
(token.type !== 'paren') ||
|
|
(token.type === 'paren' && token.value !== ')')
|
|
) {
|
|
|
|
|
|
node.params.push(walk());
|
|
token = tokens[current];
|
|
}
|
|
|
|
|
|
|
|
current++;
|
|
|
|
|
|
return node;
|
|
}
|
|
|
|
|
|
|
|
throw new TypeError(token.type);
|
|
}
|
|
|
|
|
|
|
|
let ast = {
|
|
type: 'Program',
|
|
body: [],
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
while (current < tokens.length) {
|
|
ast.body.push(walk());
|
|
}
|
|
|
|
|
|
return ast;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function traverser(ast, visitor) {
|
|
|
|
|
|
|
|
function traverseArray(array, parent) {
|
|
array.forEach(child => {
|
|
traverseNode(child, parent);
|
|
});
|
|
}
|
|
|
|
|
|
|
|
function traverseNode(node, parent) {
|
|
|
|
|
|
|
|
let methods = visitor[node.type];
|
|
|
|
|
|
|
|
if (methods && methods.enter) {
|
|
methods.enter(node, parent);
|
|
}
|
|
|
|
|
|
switch (node.type) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
case 'Program':
|
|
traverseArray(node.body, node);
|
|
break;
|
|
|
|
|
|
case 'CallExpression':
|
|
traverseArray(node.params, node);
|
|
break;
|
|
|
|
|
|
|
|
case 'NumberLiteral':
|
|
case 'StringLiteral':
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
throw new TypeError(node.type);
|
|
}
|
|
|
|
|
|
|
|
if (methods && methods.exit) {
|
|
methods.exit(node, parent);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
traverseNode(ast, null);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function transformer(ast) {
|
|
|
|
|
|
|
|
let newAst = {
|
|
type: 'Program',
|
|
body: [],
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ast._context = newAst.body;
|
|
|
|
|
|
traverser(ast, {
|
|
|
|
|
|
NumberLiteral: {
|
|
|
|
enter(node, parent) {
|
|
|
|
|
|
parent._context.push({
|
|
type: 'NumberLiteral',
|
|
value: node.value,
|
|
});
|
|
},
|
|
},
|
|
|
|
|
|
StringLiteral: {
|
|
enter(node, parent) {
|
|
parent._context.push({
|
|
type: 'StringLiteral',
|
|
value: node.value,
|
|
});
|
|
},
|
|
},
|
|
|
|
|
|
CallExpression: {
|
|
enter(node, parent) {
|
|
|
|
|
|
|
|
let expression = {
|
|
type: 'CallExpression',
|
|
callee: {
|
|
type: 'Identifier',
|
|
name: node.name,
|
|
},
|
|
arguments: [],
|
|
};
|
|
|
|
|
|
|
|
|
|
node._context = expression.arguments;
|
|
|
|
|
|
|
|
if (parent.type !== 'CallExpression') {
|
|
|
|
|
|
|
|
|
|
expression = {
|
|
type: 'ExpressionStatement',
|
|
expression: expression,
|
|
};
|
|
}
|
|
|
|
|
|
|
|
parent._context.push(expression);
|
|
},
|
|
}
|
|
});
|
|
|
|
|
|
|
|
return newAst;
|
|
}
|
|
|
|
function codeGenerator(node) {
|
|
|
|
|
|
switch (node.type) {
|
|
|
|
|
|
|
|
case 'Program':
|
|
return node.body.map(codeGenerator)
|
|
.join('\n');
|
|
|
|
|
|
|
|
case 'ExpressionStatement':
|
|
return (
|
|
codeGenerator(node.expression) +
|
|
';'
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
case 'CallExpression':
|
|
return (
|
|
codeGenerator(node.callee) +
|
|
'(' +
|
|
node.arguments.map(codeGenerator)
|
|
.join(', ') +
|
|
')'
|
|
);
|
|
|
|
|
|
case 'Identifier':
|
|
return node.name;
|
|
|
|
|
|
case 'NumberLiteral':
|
|
return node.value;
|
|
|
|
|
|
case 'StringLiteral':
|
|
return '"' + node.value + '"';
|
|
|
|
|
|
default:
|
|
throw new TypeError(node.type);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function compiler(input) {
|
|
let tokens = tokenizer(input);
|
|
let ast = parser(tokens);
|
|
let newAst = transformer(ast);
|
|
let output = codeGenerator(newAst);
|
|
|
|
|
|
return output;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
tokenizer,
|
|
parser,
|
|
traverser,
|
|
transformer,
|
|
codeGenerator,
|
|
compiler,
|
|
};
|