import {FIELD, int, isBlank, BaseException} from 'facade/lang'; import {ListWrapper, List} from 'facade/collection'; import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET} from './lexer'; import {ClosureMap} from './closure_map'; import { AST, ImplicitReceiver, FieldRead, LiteralPrimitive, Expression, Binary, PrefixNot, Conditional, Formatter, Assignment, Chain, KeyedAccess } from './ast'; var _implicitReceiver = new ImplicitReceiver(); export class Parser { @FIELD('final _lexer:Lexer') @FIELD('final _closureMap:ClosureMap') constructor(lexer:Lexer, closureMap:ClosureMap){ this._lexer = lexer; this._closureMap = closureMap; } parseAction(input:string):AST { var tokens = this._lexer.tokenize(input); return new _ParseAST(input, tokens, this._closureMap, true).parseChain(); } parseBinding(input:string):AST { var tokens = this._lexer.tokenize(input); return new _ParseAST(input, tokens, this._closureMap, false).parseChain(); } } class _ParseAST { @FIELD('final input:string') @FIELD('final tokens:List') @FIELD('final closureMap:ClosureMap') @FIELD('final parseAction:boolean') @FIELD('index:int') constructor(input:string, tokens:List, closureMap:ClosureMap, parseAction:boolean) { this.input = input; this.tokens = tokens; this.index = 0; this.closureMap = closureMap; this.parseAction = parseAction; } peek(offset:int):Token { var i = this.index + offset; return i < this.tokens.length ? this.tokens[i] : EOF; } get next():Token { return this.peek(0); } get inputIndex():int { return (this.index < this.tokens.length) ? this.next.index : this.input.length; } advance() { this.index ++; } optionalCharacter(code:int):boolean { if (this.next.isCharacter(code)) { this.advance(); return true; } else { return false; } } expectCharacter(code:int) { if (this.optionalCharacter(code)) return; this.error(`Missing expected ${code}`); } optionalOperator(op:string):boolean { if (this.next.isOperator(op)) { this.advance(); return true; } else { return false; } } expectOperator(operator:string) { if (this.optionalOperator(operator)) return; this.error(`Missing expected operator ${operator}`); } expectIdentifierOrKeyword():string { var n = this.next; if (!n.isIdentifier() && !n.isKeyword()) { this.error(`Unexpected token ${n}, expected identifier or keyword`) } this.advance(); return n.toString(); } parseChain():AST { var exprs = []; while (this.index < this.tokens.length) { var expr = this.parseFormatter(); ListWrapper.push(exprs, expr); while (this.optionalCharacter($SEMICOLON)) { if (! this.parseAction) { this.error("Binding expression cannot contain chained expression"); } } } return exprs.length == 1 ? exprs[0] : new Chain(exprs); } parseFormatter() { var result = this.parseExpression(); while (this.optionalOperator("|")) { if (this.parseAction) { this.error("Cannot have a formatter in an action expression"); } var name = this.expectIdentifierOrKeyword(); var args = ListWrapper.create(); while (this.optionalCharacter($COLON)) { ListWrapper.push(args, this.parseExpression()); } result = new Formatter(result, name, args); } return result; } parseExpression() { var result = this.parseConditional(); while (this.next.isOperator('=')) { //if (!backend.isAssignable(result)) { // int end = (index < tokens.length) ? next.index : input.length; // String expression = input.substring(start, end); // error('Expression $expression is not assignable'); // } this.expectOperator('='); result = new Assignment(result, this.parseConditional()); } return result; } parseConditional() { var start = this.inputIndex; var result = this.parseLogicalOr(); if (this.optionalOperator('?')) { var yes = this.parseExpression(); if (!this.optionalCharacter($COLON)) { var end = this.inputIndex; var expression = this.input.substring(start, end); this.error(`Conditional expression ${expression} requires all 3 expressions`); } var no = this.parseExpression(); return new Conditional(result, yes, no); } else { return result; } } parseLogicalOr() { // '||' var result = this.parseLogicalAnd(); while (this.optionalOperator('||')) { result = new Binary('||', result, this.parseLogicalAnd()); } return result; } parseLogicalAnd() { // '&&' var result = this.parseEquality(); while (this.optionalOperator('&&')) { result = new Binary('&&', result, this.parseEquality()); } return result; } parseEquality() { // '==','!=' var result = this.parseRelational(); while (true) { if (this.optionalOperator('==')) { result = new Binary('==', result, this.parseRelational()); } else if (this.optionalOperator('!=')) { result = new Binary('!=', result, this.parseRelational()); } else { return result; } } } parseRelational() { // '<', '>', '<=', '>=' var result = this.parseAdditive(); while (true) { if (this.optionalOperator('<')) { result = new Binary('<', result, this.parseAdditive()); } else if (this.optionalOperator('>')) { result = new Binary('>', result, this.parseAdditive()); } else if (this.optionalOperator('<=')) { result = new Binary('<=', result, this.parseAdditive()); } else if (this.optionalOperator('>=')) { result = new Binary('>=', result, this.parseAdditive()); } else { return result; } } } parseAdditive() { // '+', '-' var result = this.parseMultiplicative(); while (true) { if (this.optionalOperator('+')) { result = new Binary('+', result, this.parseMultiplicative()); } else if (this.optionalOperator('-')) { result = new Binary('-', result, this.parseMultiplicative()); } else { return result; } } } parseMultiplicative() { // '*', '%', '/' var result = this.parsePrefix(); while (true) { if (this.optionalOperator('*')) { result = new Binary('*', result, this.parsePrefix()); } else if (this.optionalOperator('%')) { result = new Binary('%', result, this.parsePrefix()); } else if (this.optionalOperator('/')) { result = new Binary('/', result, this.parsePrefix()); } else { return result; } } } parsePrefix() { if (this.optionalOperator('+')) { return this.parsePrefix(); } else if (this.optionalOperator('-')) { return new Binary('-', new LiteralPrimitive(0), this.parsePrefix()); } else if (this.optionalOperator('!')) { return new PrefixNot(this.parsePrefix()); } else { return this.parseAccessOrCallMember(); } } parseAccessOrCallMember():AST { var result = this.parsePrimary(); while (true) { if (this.optionalCharacter($PERIOD)) { result = this.parseFieldRead(result); } else { return result; } } } parsePrimary() { if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) { this.advance(); return new LiteralPrimitive(null); } else if (this.next.isKeywordTrue()) { this.advance(); return new LiteralPrimitive(true); } else if (this.next.isKeywordFalse()) { this.advance(); return new LiteralPrimitive(false); } else if (this.next.isIdentifier()) { return this.parseFieldRead(_implicitReceiver); } else if (this.next.isNumber()) { var value = this.next.toNumber(); this.advance(); return new LiteralPrimitive(value); } else if (this.next.isString()) { var value = this.next.toString(); this.advance(); return new LiteralPrimitive(value); } else if (this.index >= this.tokens.length) { this.error(`Unexpected end of expression: ${this.input}`); } else { this.error(`Unexpected token ${this.next}`); } } parseFieldRead(receiver):AST { var id = this.expectIdentifierOrKeyword(); return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id)); } error(message:string, index:int = null) { if (isBlank(index)) index = this.index; var location = (index < this.tokens.length) ? `at column ${this.tokens[index].index + 1} in` : `at the end of the expression`; throw new BaseException(`Parser Error: ${message} ${location} [${this.input}]`); } }