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"); |     throw new BaseException("Not supported"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   assign(context, value) { | ||||||
|  |     throw new BaseException("Not supported"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   visit(visitor) { |   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 { | export class Conditional extends AST { | ||||||
|   @FIELD('final condition:AST') |   @FIELD('final condition:AST') | ||||||
|   @FIELD('final trueExp:AST') |   @FIELD('final trueExp:AST') | ||||||
| @ -43,21 +63,34 @@ export class FieldRead extends AST { | |||||||
|   @FIELD('final receiver:AST') |   @FIELD('final receiver:AST') | ||||||
|   @FIELD('final name:string') |   @FIELD('final name:string') | ||||||
|   @FIELD('final getter:Function') |   @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.receiver = receiver; | ||||||
|     this.name = name; |     this.name = name; | ||||||
|     this.getter = getter; |     this.getter = getter; | ||||||
|  |     this.setter = setter; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   eval(context) { |   eval(context) { | ||||||
|     return this.getter(this.receiver.eval(context)); |     return this.getter(this.receiver.eval(context)); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   assign(context, value) { | ||||||
|  |     return this.setter(this.receiver.eval(context), value); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   visit(visitor) { |   visit(visitor) { | ||||||
|     visitor.visitFieldRead(this); |     visitor.visitFieldRead(this); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export class KeyedAccess extends AST { | ||||||
|  |   constructor(obj:AST, key:AST) { | ||||||
|  |     this.obj = obj; | ||||||
|  |     this.key = key; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export class Formatter extends AST { | export class Formatter extends AST { | ||||||
|   @FIELD('final exp:AST') |   @FIELD('final exp:AST') | ||||||
|   @FIELD('final name:string') |   @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
 | //INTERFACE
 | ||||||
| export class AstVisitor { | export class AstVisitor { | ||||||
|   visitImplicitReceiver(ast:ImplicitReceiver) {} |   visitImplicitReceiver(ast:ImplicitReceiver) {} | ||||||
| @ -155,6 +202,7 @@ export class AstVisitor { | |||||||
|   visitPrefixNot(ast:PrefixNot) {} |   visitPrefixNot(ast:PrefixNot) {} | ||||||
|   visitLiteralPrimitive(ast:LiteralPrimitive) {} |   visitLiteralPrimitive(ast:LiteralPrimitive) {} | ||||||
|   visitFormatter(ast:Formatter) {} |   visitFormatter(ast:Formatter) {} | ||||||
|  |   visitAssignment(ast:Assignment) {} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| var _evalListCache = [[],[0],[0,0],[0,0,0],[0,0,0,0],[0,0,0,0,0]]; | 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); |     var symbol = new Symbol(name); | ||||||
|     return (receiver) => reflect(receiver).getField(symbol).reflectee; |     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) { |   getter(name:string) { | ||||||
|     return new Function('o', 'return o.' + name + ';'); |     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 {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 {ClosureMap} from './closure_map'; | ||||||
| import { | import { | ||||||
|   AST, |   AST, | ||||||
| @ -11,7 +11,10 @@ import { | |||||||
|   Binary, |   Binary, | ||||||
|   PrefixNot, |   PrefixNot, | ||||||
|   Conditional, |   Conditional, | ||||||
|   Formatter |   Formatter, | ||||||
|  |   Assignment, | ||||||
|  |   Chain, | ||||||
|  |   KeyedAccess | ||||||
|   } from './ast'; |   } from './ast'; | ||||||
| 
 | 
 | ||||||
| var _implicitReceiver = new ImplicitReceiver(); | 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 { |   optionalOperator(op:string):boolean { | ||||||
|     if (this.next.isOperator(op)) { |     if (this.next.isOperator(op)) { | ||||||
|       this.advance(); |       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 { |   parseChain():AST { | ||||||
|     var exprs = []; |     var exprs = []; | ||||||
|     while (this.index < this.tokens.length) { |     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() { |   parseFormatter() { | ||||||
| @ -105,7 +128,7 @@ class _ParseAST { | |||||||
|       if (this.parseAction) { |       if (this.parseAction) { | ||||||
|         this.error("Cannot have a formatter in an action expression"); |         this.error("Cannot have a formatter in an action expression"); | ||||||
|       } |       } | ||||||
|       var name = this.parseIdentifier(); |       var name = this.expectIdentifierOrKeyword(); | ||||||
|       var args = ListWrapper.create(); |       var args = ListWrapper.create(); | ||||||
|       while (this.optionalCharacter($COLON)) { |       while (this.optionalCharacter($COLON)) { | ||||||
|         ListWrapper.push(args, this.parseExpression()); |         ListWrapper.push(args, this.parseExpression()); | ||||||
| @ -116,7 +139,19 @@ class _ParseAST { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   parseExpression() { |   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() { |   parseConditional() { | ||||||
| @ -202,7 +237,7 @@ class _ParseAST { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   parseMultiplicative() { |   parseMultiplicative() { | ||||||
|     // '*', '%', '/', '~/'
 |     // '*', '%', '/'
 | ||||||
|     var result = this.parsePrefix(); |     var result = this.parsePrefix(); | ||||||
|     while (true) { |     while (true) { | ||||||
|       if (this.optionalOperator('*')) { |       if (this.optionalOperator('*')) { | ||||||
| @ -211,9 +246,6 @@ class _ParseAST { | |||||||
|         result = new Binary('%', result, this.parsePrefix()); |         result = new Binary('%', result, this.parsePrefix()); | ||||||
|       } else if (this.optionalOperator('/')) { |       } else if (this.optionalOperator('/')) { | ||||||
|         result = new Binary('/', result, this.parsePrefix()); |         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 { |       } else { | ||||||
|         return result; |         return result; | ||||||
|       } |       } | ||||||
| @ -232,59 +264,54 @@ class _ParseAST { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   parseAccessOrCallMember() { |   parseAccessOrCallMember():AST { | ||||||
|     var result = this.parsePrimary(); |     var result = this.parsePrimary(); | ||||||
|     // TODO: add missing cases.
 |     while (true) { | ||||||
|     return result; |       if (this.optionalCharacter($PERIOD)) { | ||||||
|  |         result = this.parseFieldRead(result); | ||||||
|  |       } else { | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   parsePrimary() { |   parsePrimary() { | ||||||
|     if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) { |     if (this.next.isKeywordNull() || this.next.isKeywordUndefined()) { | ||||||
|       this.advance(); |       this.advance(); | ||||||
|       return new LiteralPrimitive(null); |       return new LiteralPrimitive(null); | ||||||
|  | 
 | ||||||
|     } else if (this.next.isKeywordTrue()) { |     } else if (this.next.isKeywordTrue()) { | ||||||
|       this.advance(); |       this.advance(); | ||||||
|       return new LiteralPrimitive(true); |       return new LiteralPrimitive(true); | ||||||
|  | 
 | ||||||
|     } else if (this.next.isKeywordFalse()) { |     } else if (this.next.isKeywordFalse()) { | ||||||
|       this.advance(); |       this.advance(); | ||||||
|       return new LiteralPrimitive(false); |       return new LiteralPrimitive(false); | ||||||
|  | 
 | ||||||
|     } else if (this.next.isIdentifier()) { |     } else if (this.next.isIdentifier()) { | ||||||
|       return this.parseAccess(); |       return this.parseFieldRead(_implicitReceiver); | ||||||
|  | 
 | ||||||
|     } else if (this.next.isNumber()) { |     } else if (this.next.isNumber()) { | ||||||
|       var value = this.next.toNumber(); |       var value = this.next.toNumber(); | ||||||
|       this.advance(); |       this.advance(); | ||||||
|       return new LiteralPrimitive(value); |       return new LiteralPrimitive(value); | ||||||
|  | 
 | ||||||
|     } else if (this.next.isString()) { |     } else if (this.next.isString()) { | ||||||
|       var value = this.next.toString(); |       var value = this.next.toString(); | ||||||
|       this.advance(); |       this.advance(); | ||||||
|       return new LiteralPrimitive(value); |       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 { |     } else if (this.index >= this.tokens.length) { | ||||||
|     var result = this.parseFieldRead(_implicitReceiver); |       this.error(`Unexpected end of expression: ${this.input}`); | ||||||
|     while(this.optionalCharacter($PERIOD)) { | 
 | ||||||
|       result = this.parseFieldRead(result); |     } else { | ||||||
|  |       this.error(`Unexpected token ${this.next}`); | ||||||
|     } |     } | ||||||
|     return result; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   parseFieldRead(receiver):AST { |   parseFieldRead(receiver):AST { | ||||||
|     var id = this.parseIdentifier(); |     var id = this.expectIdentifierOrKeyword(); | ||||||
|     return new FieldRead(receiver, id, this.closureMap.getter(id)); |     return new FieldRead(receiver, id, this.closureMap.getter(id), this.closureMap.setter(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(); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   error(message:string, index:int = null) { |   error(message:string, index:int = null) { | ||||||
| @ -294,16 +321,6 @@ class _ParseAST { | |||||||
|       ? `at column ${this.tokens[index].index + 1} in` |       ? `at column ${this.tokens[index].index + 1} in` | ||||||
|       : `at the end of the expression`; |       : `at the end of the expression`; | ||||||
| 
 | 
 | ||||||
|     throw new ParserError(`Parser Error: ${message} ${location} [${this.input}]`); |     throw new BaseException(`Parser Error: ${message} ${location} [${this.input}]`); | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| class ParserError extends Error { |  | ||||||
|   constructor(message) { |  | ||||||
|     this.message = message; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   toString() { |  | ||||||
|     return this.message; |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ export function main() { | |||||||
|     var parts = exp.split("."); |     var parts = exp.split("."); | ||||||
|     var cm = new ClosureMap(); |     var cm = new ClosureMap(); | ||||||
|     return ListWrapper.reduce(parts, function (ast, fieldName) { |     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()); |     }, new ImplicitReceiver()); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -150,25 +150,43 @@ export function main() { | |||||||
|           createParser().parseAction('boo').eval(new ContextWithErrors()); |           createParser().parseAction('boo').eval(new ContextWithErrors()); | ||||||
|         }).toThrowError('boo to you'); |         }).toThrowError('boo to you'); | ||||||
|       }); |       }); | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     describe("parseBinding", () => { |       it('should evaluate assignments', () => { | ||||||
|       it("should parse formatters", function () { |         var context = td(); | ||||||
|         var exp = parseBinding("'Foo'|uppercase"); |         expectEval("a=12", context).toEqual(12); | ||||||
|         expect(exp).toBeAnInstanceOf(Formatter); |         expect(context.a).toEqual(12); | ||||||
|         expect(exp.name).toEqual("uppercase"); | 
 | ||||||
|  |         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 () { |       describe("parseBinding", () => { | ||||||
|         var exp = parseBinding("1|increment:2"); |         //throw on assignment
 | ||||||
|         expect(exp).toBeAnInstanceOf(Formatter); |  | ||||||
|         expect(exp.name).toEqual("increment"); |  | ||||||
|         expect(exp.args[0]).toBeAnInstanceOf(LiteralPrimitive); |  | ||||||
|       }); |  | ||||||
| 
 | 
 | ||||||
|       it('should throw on chain expressions', () => { |         it("should parse formatters", function () { | ||||||
|         expect(() => parseBinding("1;2")).toThrowError(new RegExp("contain chained expression")); |           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() { | export function main() { | ||||||
|   var oneFieldAst = (fieldName) => |   var oneFieldAst = (fieldName) => { | ||||||
|       new FieldRead(new ImplicitReceiver(), fieldName, |     var cm = new ClosureMap(); | ||||||
|           (new ClosureMap()).getter(fieldName)); |     return new FieldRead(new ImplicitReceiver(), fieldName, | ||||||
|  |       cm.getter(fieldName), cm.setter(fieldName)); | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   describe('view', function() { |   describe('view', function() { | ||||||
|     var tempalteWithThreeTypesOfBindings = |     var tempalteWithThreeTypesOfBindings = | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user