feat(parser): adds support for variable bindings

This commit is contained in:
vsavkin
2014-11-26 09:44:31 -08:00
parent a3d9f0fead
commit 1863d50978
7 changed files with 155 additions and 19 deletions

View File

@ -1,5 +1,6 @@
import {FIELD, autoConvertAdd, isBlank, isPresent, FunctionWrapper, BaseException} from "facade/lang";
import {List, Map, ListWrapper, MapWrapper} from "facade/collection";
import {ContextWithVariableBindings} from "./context_with_variable_bindings";
export class AST {
eval(context) {
@ -97,7 +98,16 @@ export class AccessMember extends AST {
}
eval(context) {
return this.getter(this.receiver.eval(context));
var evaluatedContext = this.receiver.eval(context);
while (evaluatedContext instanceof ContextWithVariableBindings) {
if (evaluatedContext.hasBinding(this.name)) {
return evaluatedContext.get(this.name);
}
evaluatedContext = evaluatedContext.parent;
}
return this.getter(evaluatedContext);
}
get isAssignable() {
@ -105,7 +115,16 @@ export class AccessMember extends AST {
}
assign(context, value) {
return this.setter(this.receiver.eval(context), value);
var evaluatedContext = this.receiver.eval(context);
while (evaluatedContext instanceof ContextWithVariableBindings) {
if (evaluatedContext.hasBinding(this.name)) {
throw new BaseException(`Cannot reassign a variable binding ${this.name}`)
}
evaluatedContext = evaluatedContext.parent;
}
return this.setter(evaluatedContext, value);
}
visit(visitor, args) {

View File

@ -0,0 +1,20 @@
import {MapWrapper} from 'facade/collection';
export class ContextWithVariableBindings {
parent:any;
/// varBindings are read-only. updating/adding keys is not supported.
varBindings:Map;
constructor(parent:any, varBindings:Map) {
this.parent = parent;
this.varBindings = varBindings;
}
hasBinding(name:string):boolean {
return MapWrapper.contains(this.varBindings, name);
}
get(name:string) {
return MapWrapper.get(this.varBindings, name);
}
}

View File

@ -30,6 +30,7 @@ export class ProtoRecord {
context:any;
funcOrValue:any;
arity:int;
name:string;
dest;
next:ProtoRecord;
@ -39,12 +40,14 @@ export class ProtoRecord {
mode:int,
funcOrValue,
arity:int,
name:string,
dest) {
this.recordRange = recordRange;
this._mode = mode;
this.funcOrValue = funcOrValue;
this.arity = arity;
this.name = name;
this.dest = dest;
this.next = null;

View File

@ -15,7 +15,7 @@ import {List, Map, ListWrapper, MapWrapper} from 'facade/collection';
import {AST, AccessMember, ImplicitReceiver, AstVisitor, LiteralPrimitive,
Binary, Formatter, MethodCall, FunctionCall, PrefixNot, Conditional,
LiteralArray, LiteralMap, KeyedAccess, Chain, Assignment} from './parser/ast';
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
export class ProtoRecordRange {
headRecord:ProtoRecord;
@ -304,11 +304,36 @@ export class RecordRange {
for (var record:Record = this.headRecord;
record != null;
record = record.next) {
if (record.isImplicitReceiver) {
record.updateContext(context);
this._setContextForRecord(context, record);
}
}
}
_setContextForRecord(context, record:Record) {
var proto = record.protoRecord;
while (context instanceof ContextWithVariableBindings) {
if (context.hasBinding(proto.name)) {
this._setVarBindingGetter(context, record, proto);
return;
}
context = context.parent;
}
this._setRegularGetter(context, record, proto);
}
_setVarBindingGetter(context, record:Record, proto:ProtoRecord) {
record.funcOrValue = _mapGetter(proto.name);
record.updateContext(context.varBindings);
}
_setRegularGetter(context, record:Record, proto:ProtoRecord) {
record.funcOrValue = proto.funcOrValue;
record.updateContext(context);
}
}
function _link(a:Record, b:Record) {
@ -353,25 +378,25 @@ class ProtoRecordCreator {
}
visitLiteralPrimitive(ast:LiteralPrimitive, dest) {
this.add(this.construct(RECORD_TYPE_CONST, ast.value, 0, dest));
this.add(this.construct(RECORD_TYPE_CONST, ast.value, 0, null, dest));
}
visitBinary(ast:Binary, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION,
_operationToFunction(ast.operation), 2, dest);
_operationToFunction(ast.operation), 2, null, dest);
ast.left.visit(this, new Destination(record, 0));
ast.right.visit(this, new Destination(record, 1));
this.add(record);
}
visitPrefixNot(ast:PrefixNot, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _operation_negate, 1, dest);
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _operation_negate, 1, null, dest);
ast.expression.visit(this, new Destination(record, 0));
this.add(record);
}
visitAccessMember(ast:AccessMember, dest) {
var record = this.construct(RECORD_TYPE_PROPERTY, ast.getter, 0, dest);
var record = this.construct(RECORD_TYPE_PROPERTY, ast.getter, 0, ast.name, dest);
if (ast.receiver instanceof ImplicitReceiver) {
record.setIsImplicitReceiver();
} else {
@ -381,7 +406,7 @@ class ProtoRecordCreator {
}
visitFormatter(ast:Formatter, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.allArgs.length, dest);
var record = this.construct(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.allArgs.length, null, dest);
for (var i = 0; i < ast.allArgs.length; ++i) {
ast.allArgs[i].visit(this, new Destination(record, i));
}
@ -389,7 +414,7 @@ class ProtoRecordCreator {
}
visitMethodCall(ast:MethodCall, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_METHOD, ast.fn, ast.args.length, dest);
var record = this.construct(RECORD_TYPE_INVOKE_METHOD, ast.fn, ast.args.length, null, dest);
for (var i = 0; i < ast.args.length; ++i) {
ast.args[i].visit(this, new Destination(record, i));
}
@ -402,7 +427,7 @@ class ProtoRecordCreator {
}
visitFunctionCall(ast:FunctionCall, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_CLOSURE, null, ast.args.length, dest);
var record = this.construct(RECORD_TYPE_INVOKE_CLOSURE, null, ast.args.length, null, dest);
ast.target.visit(this, new Destination(record, null));
for (var i = 0; i < ast.args.length; ++i) {
ast.args[i].visit(this, new Destination(record, i));
@ -411,7 +436,7 @@ class ProtoRecordCreator {
}
visitConditional(ast:Conditional, dest) {
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _cond, 3, dest);
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _cond, 3, null, dest);
ast.condition.visit(this, new Destination(record, 0));
ast.trueExp.visit(this, new Destination(record, 1));
ast.falseExp.visit(this, new Destination(record, 2));
@ -422,7 +447,7 @@ class ProtoRecordCreator {
visitLiteralArray(ast:LiteralArray, dest) {
var length = ast.expressions.length;
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _arrayFn(length), length, dest);
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _arrayFn(length), length, null, dest);
for (var i = 0; i < length; ++i) {
ast.expressions[i].visit(this, new Destination(record, i));
}
@ -431,7 +456,7 @@ class ProtoRecordCreator {
visitLiteralMap(ast:LiteralMap, dest) {
var length = ast.values.length;
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _mapFn(ast.keys, length), length, dest);
var record = this.construct(RECORD_TYPE_INVOKE_PURE_FUNCTION, _mapFn(ast.keys, length), length, null, dest);
for (var i = 0; i < length; ++i) {
ast.values[i].visit(this, new Destination(record, i));
}
@ -448,8 +473,8 @@ class ProtoRecordCreator {
ast.visit(this, memento);
}
construct(recordType, funcOrValue, arity, dest) {
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, dest);
construct(recordType, funcOrValue, arity, name, dest) {
return new ProtoRecord(this.protoRecordRange, recordType, funcOrValue, arity, name, dest);
}
add(protoRecord:ProtoRecord) {
@ -540,3 +565,10 @@ function _mapFn(keys:List, length:int) {
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
}
}
//TODO: cache the getters
function _mapGetter(key) {
return function(map) {
return MapWrapper.get(map, key);
}
}