feat(Parser): add support for assignments
This commit is contained in:
		
							parent
							
								
									8e6326f838
								
							
						
					
					
						commit
						8cc008bda1
					
				| @ -6,6 +6,10 @@ export class AST { | ||||
|     throw new BaseException("Not supported"); | ||||
|   } | ||||
| 
 | ||||
|   assign(context, value) { | ||||
|     throw new BaseException("Not supported"); | ||||
|   } | ||||
| 
 | ||||
|   visit(visitor) { | ||||
|   } | ||||
| } | ||||
| @ -20,6 +24,22 @@ export class ImplicitReceiver extends AST { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class Chain extends AST { | ||||
|   constructor(expressions:List) { | ||||
|     this.expressions = expressions; | ||||
|   } | ||||
| 
 | ||||
|   eval(context) { | ||||
|     var result; | ||||
|     for (var i = 0; i < this.expressions.length; i++) { | ||||
|       var last = this.expressions[i].eval(context); | ||||
|       if (last != null) result = last; | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export class Conditional extends AST { | ||||
|   @FIELD('final condition:AST') | ||||
|   @FIELD('final trueExp:AST') | ||||
| @ -43,21 +63,34 @@ export class FieldRead extends AST { | ||||
|   @FIELD('final receiver:AST') | ||||
|   @FIELD('final name:string') | ||||
|   @FIELD('final getter:Function') | ||||
|   constructor(receiver:AST, name:string, getter:Function) { | ||||
|   @FIELD('final setter:Function') | ||||
|   constructor(receiver:AST, name:string, getter:Function, setter:Function) { | ||||
|     this.receiver = receiver; | ||||
|     this.name = name; | ||||
|     this.getter = getter; | ||||
|     this.setter = setter; | ||||
|   } | ||||
| 
 | ||||
|   eval(context) { | ||||
|     return this.getter(this.receiver.eval(context)); | ||||
|   } | ||||
| 
 | ||||
|   assign(context, value) { | ||||
|     return this.setter(this.receiver.eval(context), value); | ||||
|   } | ||||
| 
 | ||||
|   visit(visitor) { | ||||
|     visitor.visitFieldRead(this); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class KeyedAccess extends AST { | ||||
|   constructor(obj:AST, key:AST) { | ||||
|     this.obj = obj; | ||||
|     this.key = key; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class Formatter extends AST { | ||||
|   @FIELD('final exp:AST') | ||||
|   @FIELD('final name:string') | ||||
| @ -147,6 +180,20 @@ export class PrefixNot extends AST { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class Assignment extends AST { | ||||
|   @FIELD('final target:AST') | ||||
|   @FIELD('final value:AST') | ||||
|   constructor(target:AST, value:AST) { | ||||
|     this.target = target; | ||||
|     this.value = value; | ||||
|   } | ||||
|   visit(visitor) { visitor.visitAssignment(this); } | ||||
| 
 | ||||
|   eval(context) { | ||||
|     return this.target.assign(context, this.value.eval(context)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| //INTERFACE
 | ||||
| export class AstVisitor { | ||||
|   visitImplicitReceiver(ast:ImplicitReceiver) {} | ||||
| @ -155,6 +202,7 @@ export class AstVisitor { | ||||
|   visitPrefixNot(ast:PrefixNot) {} | ||||
|   visitLiteralPrimitive(ast:LiteralPrimitive) {} | ||||
|   visitFormatter(ast:Formatter) {} | ||||
|   visitAssignment(ast:Assignment) {} | ||||
| } | ||||
| 
 | ||||
| var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]]; | ||||
|  | ||||
| @ -7,4 +7,9 @@ class ClosureMap { | ||||
|     var symbol = new Symbol(name); | ||||
|     return (receiver) => reflect(receiver).getField(symbol).reflectee; | ||||
|   } | ||||
| 
 | ||||
|   Function setter(String name) { | ||||
|     var symbol = new Symbol(name); | ||||
|     return (receiver, value) => reflect(receiver).setField(symbol, value).reflectee; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -4,4 +4,8 @@ export class ClosureMap { | ||||
|   getter(name:string) { | ||||
|     return new Function('o', 'return o.' + name + ';'); | ||||
|   } | ||||
| 
 | ||||
|   setter(name:string) { | ||||
|     return new Function('o', 'v', 'return o.' + name + ' = v;'); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import {FIELD, int, isBlank} from 'facade/lang'; | ||||
| import {FIELD, int, isBlank,  BaseException} from 'facade/lang'; | ||||
| import {ListWrapper, List} from 'facade/collection'; | ||||
| import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON} from './lexer'; | ||||
| import {Lexer, EOF, Token, $PERIOD, $COLON, $SEMICOLON, $LBRACKET, $RBRACKET} from './lexer'; | ||||
| import {ClosureMap} from './closure_map'; | ||||
| import { | ||||
|   AST, | ||||
| @ -11,7 +11,10 @@ import { | ||||
|   Binary, | ||||
|   PrefixNot, | ||||
|   Conditional, | ||||
|   Formatter | ||||
|   Formatter, | ||||
|   Assignment, | ||||
|   Chain, | ||||
|   KeyedAccess | ||||
|   } from './ast'; | ||||
| 
 | ||||
| var _implicitReceiver = new ImplicitReceiver(); | ||||
| @ -75,6 +78,12 @@ class _ParseAST { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   expectCharacter(code:int) { | ||||
|     if (this.optionalCharacter(code)) return; | ||||
|     this.error(`Missing expected ${code}`); | ||||
|   } | ||||
| 
 | ||||
| 
 | ||||
|   optionalOperator(op:string):boolean { | ||||
|     if (this.next.isOperator(op)) { | ||||
|       this.advance(); | ||||
| @ -84,6 +93,20 @@ class _ParseAST { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   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) { | ||||
| @ -96,7 +119,7 @@ class _ParseAST { | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return ListWrapper.first(exprs); | ||||
|     return exprs.length == 1 ? exprs[0] : new Chain(exprs); | ||||
|   } | ||||
| 
 | ||||
|   parseFormatter() { | ||||
| @ -105,7 +128,7 @@ class _ParseAST { | ||||
|       if (this.parseAction) { | ||||
|         this.error("Cannot have a formatter in an action expression"); | ||||
|       } | ||||
|       var name = this.parseIdentifier(); | ||||
|       var name = this.expectIdentifierOrKeyword(); | ||||
|       var args = ListWrapper.create(); | ||||
|       while (this.optionalCharacter($COLON)) { | ||||
|         ListWrapper.push(args, this.parseExpression()); | ||||
| @ -116,7 +139,19 @@ class _ParseAST { | ||||
|   } | ||||
| 
 | ||||
|   parseExpression() { | ||||
|     return this.parseConditional(); | ||||
|     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() { | ||||
| @ -202,7 +237,7 @@ class _ParseAST { | ||||
|   } | ||||
| 
 | ||||
|   parseMultiplicative() { | ||||
|     // '*', '%', '/', '~/'
 | ||||
|     // '*', '%', '/'
 | ||||
|     var result = this.parsePrefix(); | ||||
|     while (true) { | ||||
|       if (this.optionalOperator('*')) { | ||||
| @ -211,9 +246,6 @@ class _ParseAST { | ||||
|         result = new Binary('%', result, this.parsePrefix()); | ||||
|       } else if (this.optionalOperator('/')) { | ||||
|         result = new Binary('/', result, this.parsePrefix()); | ||||
|       // TODO(rado): This exists only in Dart, figure out whether to support it.
 | ||||
|       // } else if (this.optionalOperator('~/')) {
 | ||||
|       //   result = new BinaryTruncatingDivide(result, this.parsePrefix());
 | ||||
|       } else { | ||||
|         return result; | ||||
|       } | ||||
| @ -232,59 +264,54 @@ class _ParseAST { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   parseAccessOrCallMember() { | ||||
|   parseAccessOrCallMember():AST { | ||||
|     var result = this.parsePrimary(); | ||||
|     // TODO: add missing cases.
 | ||||
|     return result; | ||||
|     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.parseAccess(); | ||||
|       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) { | ||||
|       throw `Unexpected end of expression: ${this.input}`; | ||||
|     } else { | ||||
|       throw `Unexpected token ${this.next}`; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   parseAccess():AST { | ||||
|     var result = this.parseFieldRead(_implicitReceiver); | ||||
|     while(this.optionalCharacter($PERIOD)) { | ||||
|       result = this.parseFieldRead(result); | ||||
|     } else if (this.index >= this.tokens.length) { | ||||
|       this.error(`Unexpected end of expression: ${this.input}`); | ||||
| 
 | ||||
|     } else { | ||||
|       this.error(`Unexpected token ${this.next}`); | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| 
 | ||||
|   parseFieldRead(receiver):AST { | ||||
|     var id = this.parseIdentifier(); | ||||
|     return new FieldRead(receiver, id, this.closureMap.getter(id)); | ||||
|   } | ||||
| 
 | ||||
|   parseIdentifier():string { | ||||
|     var n = this.next; | ||||
|     if (!n.isIdentifier() && !n.isKeyword()) { | ||||
|       this.error(`Unexpected token ${n}, expected identifier or keyword`) | ||||
|     } | ||||
|     this.advance(); | ||||
|     return n.toString(); | ||||
|     var id = this.expectIdentifierOrKeyword(); | ||||
|     return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(id)); | ||||
|   } | ||||
| 
 | ||||
|   error(message:string, index:int = null) { | ||||
| @ -294,16 +321,6 @@ class _ParseAST { | ||||
|       ? `at column ${this.tokens[index].index + 1} in` | ||||
|       : `at the end of the expression`; | ||||
| 
 | ||||
|     throw new ParserError(`Parser Error: ${message} ${location} [${this.input}]`); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class ParserError extends Error { | ||||
|   constructor(message) { | ||||
|     this.message = message; | ||||
|   } | ||||
| 
 | ||||
|   toString() { | ||||
|     return this.message; | ||||
|     throw new BaseException(`Parser Error: ${message} ${location} [${this.input}]`); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -19,7 +19,7 @@ export function main() { | ||||
|     var parts = exp.split("."); | ||||
|     var cm = new ClosureMap(); | ||||
|     return ListWrapper.reduce(parts, function (ast, fieldName) { | ||||
|       return new FieldRead(ast, fieldName, cm.getter(fieldName)); | ||||
|       return new FieldRead(ast, fieldName, cm.getter(fieldName), cm.setter(fieldName)); | ||||
|     }, new ImplicitReceiver()); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -150,25 +150,43 @@ export function main() { | ||||
|           createParser().parseAction('boo').eval(new ContextWithErrors()); | ||||
|         }).toThrowError('boo to you'); | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     describe("parseBinding", () => { | ||||
|       it("should parse formatters", function () { | ||||
|         var exp = parseBinding("'Foo'|uppercase"); | ||||
|         expect(exp).toBeAnInstanceOf(Formatter); | ||||
|         expect(exp.name).toEqual("uppercase"); | ||||
|       it('should evaluate assignments', () => { | ||||
|         var context = td(); | ||||
|         expectEval("a=12", context).toEqual(12); | ||||
|         expect(context.a).toEqual(12); | ||||
| 
 | ||||
|         context = td(td(td())); | ||||
|         expectEval("a.a.a=123;", context).toEqual(123); | ||||
|         expect(context.a.a.a).toEqual(123); | ||||
| 
 | ||||
|         context = td(); | ||||
|         expectEval("a=123; b=234", context).toEqual(234); | ||||
|         expect(context.a).toEqual(123); | ||||
|         expect(context.b).toEqual(234); | ||||
|       }); | ||||
| 
 | ||||
|       it("should parse formatters with args", function () { | ||||
|         var exp = parseBinding("1|increment:2"); | ||||
|         expect(exp).toBeAnInstanceOf(Formatter); | ||||
|         expect(exp.name).toEqual("increment"); | ||||
|         expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive); | ||||
|       }); | ||||
|       describe("parseBinding", () => { | ||||
|         //throw on assignment
 | ||||
| 
 | ||||
|       it('should throw on chain expressions', () => { | ||||
|         expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression")); | ||||
|         it("should parse formatters", function () { | ||||
|           var exp = parseBinding("'Foo'|uppercase"); | ||||
|           expect(exp).toBeAnInstanceOf(Formatter); | ||||
|           expect(exp.name).toEqual("uppercase"); | ||||
|         }); | ||||
| 
 | ||||
|         it("should parse formatters with args", function () { | ||||
|           var exp = parseBinding("1|increment:2"); | ||||
|           expect(exp).toBeAnInstanceOf(Formatter); | ||||
|           expect(exp.name).toEqual("increment"); | ||||
|           expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive); | ||||
|         }); | ||||
| 
 | ||||
|         it('should throw on chain expressions', () => { | ||||
|           expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression")); | ||||
|         }); | ||||
|       }); | ||||
|     }); | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -18,9 +18,11 @@ class Directive { | ||||
| } | ||||
| 
 | ||||
| export function main() { | ||||
|   var oneFieldAst = (fieldName) => | ||||
|       new FieldRead(new ImplicitReceiver(), fieldName, | ||||
|           (new ClosureMap()).getter(fieldName)); | ||||
|   var oneFieldAst = (fieldName) => { | ||||
|     var cm = new ClosureMap(); | ||||
|     return new FieldRead(new ImplicitReceiver(), fieldName, | ||||
|       cm.getter(fieldName), cm.setter(fieldName)); | ||||
|   }; | ||||
| 
 | ||||
|   describe('view', function() { | ||||
|     var tempalteWithThreeTypesOfBindings = | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user