mframejs
Version:
simple framework
303 lines (234 loc) • 7.27 kB
text/typescript
import { AST } from './ast';
/**
* Using Douglas Crockford paper on Vaughan Pratt "Top Down Operator Precedence"
* For more info about this look here
* https://crockford.com/javascript/tdop/tdop.html (added to docs folder so I dont loose it)
* Not everything is used
*
* This function adds symbols to our AST
*
*/
export function addSymbols(astInstance: AST) {
const ast = astInstance;
/**
* Symbol id and an optional binding power that defaults to 0 and returns a symbol object for that id.
* If the symbol already exists in the symbol_table, the function returns that symbol object.
* Otherwise, it makes a new symbol object that inherits from the symbolTemplate, stores it in the symbol container, and returns it.
* A symbol object initially contains an id, a value, a left binding power, and the stuff it inherits from the symbolTemplate.
*
*/
ast.symbol('(literal)').nud = function () {
return this;
};
ast.symbol('(variable)').nud = function () {
return this;
};
ast.symbol('(function)').nud = function () {
return this;
};
ast.symbol('(end)');
ast.symbol('(name)');
ast.symbol(':');
ast.symbol(';');
ast.symbol(')');
ast.symbol(']');
ast.symbol('}');
ast.symbol(',');
/**
* The infix function takes an id, a binding power, and an optional led function.
* If a led function is not provided, the infix function supplies a default led that is useful in most cases.
*
*/
ast.infix('+', 50);
ast.infix('-', 50);
ast.infix('*', 60);
ast.infix('%', 60);
ast.infix('/', 60);
ast.infix('===', 40);
ast.infix('!==', 40);
ast.infix('==', 40);
ast.infix('!=', 40);
ast.infix('<', 40);
ast.infix('<=', 40);
ast.infix('>', 40);
ast.infix('^', 70);
ast.infix('>=', 40);
ast.infix('?', 20, function (left: any) {
this.first = left;
this.second = ast.expression(0);
ast.advance(':');
this.third = ast.expression(0);
this.arity = 'ternary';
return this;
});
ast.infix('.', 80, function (left: any) {
this.first = left;
if (ast.currentToken.arity !== 'variable') {
ast.currentToken.error('Expected a property name.');
}
ast.currentToken.arity = 'variable';
this.second = ast.currentToken;
this.arity = 'binary';
this.isObject = true;
ast.advance();
return this;
});
ast.infix('[', 80, function (left: any) {
this.first = left;
this.second = ast.expression(0);
this.arity = 'binary';
this.isObject = true;
this.contextReset = true;
ast.advance(']');
return this;
});
ast.infix('(', 80, function (left: any) {
if (!left) {
return ast.expression(0);
}
const a: any[] = [];
this.arity = 'binary';
this.isFunction = true;
this.first = left;
this.second = a;
if (ast.currentToken.id !== ')') {
while (true) {
a.push(ast.expression(0));
if (ast.currentToken.id !== ',') {
break;
}
ast.advance(',');
}
}
ast.advance(')');
return this;
});
/**
* Prefix operators are right associative.
* A prefix does not have a left binding power because it does not bind to the left.
* Prefix operators can also sometimes be reserved words.
*
*/
ast.prefix('-');
ast.prefix('!');
// ast.prefix('typeof'); // TODO: I dont have anything for this in my tokenizer
ast.prefix('(', function () {
const e = ast.expression(0);
// TODO: check this one... will this even happend ?
if (ast.currentToken.value === '|') {
ast.valueConverter = ast.expression(0);
}
if (ast.currentToken.value === '&') {
ast.behavior = ast.expression(0);
} else {
ast.advance(')');
}
return e;
});
ast.prefix('{', function () {
const a = [];
if (ast.currentToken.value !== '}') {
while (true) {
const n = ast.currentToken;
ast.advance();
ast.advance(':');
const v = ast.expression(0);
v.key = n.value;
a.push(v);
if (ast.currentToken.value !== ',') {
break;
}
ast.advance(',');
}
}
ast.advance('}');
this.first = a;
this.arity = 'unary';
return this;
});
/* ast.stmt('{', function () {
const x = ast.statements();
ast.advance('}');
return x;
}); */
ast.assignment('=');
ast.assignment('+=');
ast.assignment('-=');
ast.assignment('*=');
ast.assignment('/=');
/**
* valueConverter
*
*/
ast.prefix('|', function () {
this.arity = 'valueConverter';
const value = ast.expression(0);
this.value = value.value;
const a: any = [];
this.args = a;
while (true) {
if (ast.currentToken.id !== ':') {
break;
}
ast.advance(',');
a.push(ast.expression(0));
}
// use last statement as argument
this.args.unshift(ast.statementsArray.pop());
ast.currentStatement = this;
return this;
});
/**
* behavior
*
*/
ast.prefix('&', function () {
this.arity = 'behavior';
const value = ast.expression(0);
this.value = value.value;
const a: any = [];
this.args = a;
while (true) {
if (ast.currentToken.id !== ':') {
break;
}
ast.advance(',');
a.push(ast.expression(0));
}
// do I want to add last argument ?
// this.args.push(ast.statementsArray);
return this;
});
/**
* expression
*
*/
ast.prefix('${', function () {
this.e = ast.expression(0);
ast.advance('}');
return this.e;
});
/**
* expression, alternative to ${
*
*/
ast.prefix('@{', function () {
const e = ast.expression(0);
ast.advance('}');
return e;
});
/**
* Those infix operators are left associative.
* We can also make right associative operators, such as short-circuiting logical operators, by reducing the right binding power.
*
*/
ast.infixr('&&', 30);
ast.infixr('||', 30);
/**
* expressions/part before expression is split with ';' operator
*
*/
ast.symbol(';').nud = function (): null {
return null;
};
}