277 lines
9.6 KiB
JavaScript
277 lines
9.6 KiB
JavaScript
|
import {isPresent, isBlank, BaseException} from 'facade/lang';
|
||
|
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'facade/collection';
|
||
|
|
||
|
import {
|
||
|
AccessMember,
|
||
|
Assignment,
|
||
|
AST,
|
||
|
ASTWithSource,
|
||
|
AstVisitor,
|
||
|
Binary,
|
||
|
Chain,
|
||
|
Structural,
|
||
|
Conditional,
|
||
|
Formatter,
|
||
|
FunctionCall,
|
||
|
ImplicitReceiver,
|
||
|
KeyedAccess,
|
||
|
LiteralArray,
|
||
|
LiteralMap,
|
||
|
LiteralPrimitive,
|
||
|
MethodCall,
|
||
|
PrefixNot
|
||
|
} from './parser/ast';
|
||
|
|
||
|
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
||
|
import {ChangeDispatcher, ChangeDetector} from './interfaces';
|
||
|
import {DynamicChangeDetector} from './dynamic_change_detector';
|
||
|
|
||
|
export const RECORD_TYPE_SELF = 0;
|
||
|
export const RECORD_TYPE_PROPERTY = 1;
|
||
|
export const RECORD_TYPE_INVOKE_METHOD = 2;
|
||
|
export const RECORD_TYPE_CONST = 3;
|
||
|
export const RECORD_TYPE_INVOKE_CLOSURE = 4;
|
||
|
export const RECORD_TYPE_INVOKE_PURE_FUNCTION = 5;
|
||
|
export const RECORD_TYPE_INVOKE_FORMATTER = 6;
|
||
|
export const RECORD_TYPE_STRUCTURAL_CHECK = 10;
|
||
|
|
||
|
export class ProtoRecord {
|
||
|
mode:number;
|
||
|
name:string;
|
||
|
funcOrValue:any;
|
||
|
args:List;
|
||
|
contextIndex:number;
|
||
|
record_type_selfIndex:number;
|
||
|
bindingMemento:any;
|
||
|
groupMemento:any;
|
||
|
terminal:boolean;
|
||
|
expressionAsString:string;
|
||
|
|
||
|
constructor(mode:number,
|
||
|
name:string,
|
||
|
funcOrValue,
|
||
|
args:List,
|
||
|
contextIndex:number,
|
||
|
record_type_selfIndex:number,
|
||
|
bindingMemento:any,
|
||
|
groupMemento:any,
|
||
|
terminal:boolean,
|
||
|
expressionAsString:string) {
|
||
|
|
||
|
this.mode = mode;
|
||
|
this.name = name;
|
||
|
this.funcOrValue = funcOrValue;
|
||
|
this.args = args;
|
||
|
this.contextIndex = contextIndex;
|
||
|
this.record_type_selfIndex = record_type_selfIndex;
|
||
|
this.bindingMemento = bindingMemento;
|
||
|
this.groupMemento = groupMemento;
|
||
|
this.terminal = terminal;
|
||
|
this.expressionAsString = expressionAsString;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export class ProtoChangeDetector {
|
||
|
records:List<ProtoRecord>;
|
||
|
|
||
|
constructor() {
|
||
|
this.records = [];
|
||
|
}
|
||
|
|
||
|
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
||
|
if (structural) ast = new Structural(ast);
|
||
|
|
||
|
var c = new ProtoOperationsCreator(bindingMemento, groupMemento,
|
||
|
this.records.length, ast.toString());
|
||
|
ast.visit(c);
|
||
|
|
||
|
if (! ListWrapper.isEmpty(c.protoRecords)) {
|
||
|
var last = ListWrapper.last(c.protoRecords);
|
||
|
last.terminal = true;
|
||
|
this.records = ListWrapper.concat(this.records, c.protoRecords);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
instantiate(dispatcher:any, formatters:Map) {
|
||
|
return new DynamicChangeDetector(dispatcher, formatters, this.records);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
class ProtoOperationsCreator {
|
||
|
protoRecords:List;
|
||
|
bindingMemento:any;
|
||
|
groupMemento:any;
|
||
|
contextIndex:number;
|
||
|
expressionAsString:string;
|
||
|
|
||
|
constructor(bindingMemento:any, groupMemento:any, contextIndex:number, expressionAsString:string) {
|
||
|
this.protoRecords = [];
|
||
|
this.bindingMemento = bindingMemento;
|
||
|
this.groupMemento = groupMemento;
|
||
|
this.contextIndex = contextIndex;
|
||
|
this.expressionAsString = expressionAsString;
|
||
|
}
|
||
|
|
||
|
visitImplicitReceiver(ast:ImplicitReceiver) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
visitLiteralPrimitive(ast:LiteralPrimitive) {
|
||
|
return this._addRecord(RECORD_TYPE_CONST, null, ast.value, [], 0);
|
||
|
}
|
||
|
|
||
|
visitAccessMember(ast:AccessMember) {
|
||
|
var receiver = ast.receiver.visit(this);
|
||
|
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], receiver);
|
||
|
}
|
||
|
|
||
|
visitFormatter(ast:Formatter) {
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), 0);
|
||
|
}
|
||
|
|
||
|
visitMethodCall(ast:MethodCall) {
|
||
|
var receiver = ast.receiver.visit(this);
|
||
|
var args = this._visitAll(ast.args);
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, ast.name, ast.fn, args, receiver);
|
||
|
}
|
||
|
|
||
|
visitFunctionCall(ast:FunctionCall) {
|
||
|
var target = ast.target.visit(this);
|
||
|
var args = this._visitAll(ast.args);
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, null, null, args, target);
|
||
|
}
|
||
|
|
||
|
visitLiteralArray(ast:LiteralArray) {
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Array()", _arrayFn(ast.expressions.length),
|
||
|
this._visitAll(ast.expressions), 0);
|
||
|
}
|
||
|
|
||
|
visitLiteralMap(ast:LiteralMap) {
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "Map()", _mapFn(ast.keys, ast.values.length),
|
||
|
this._visitAll(ast.values), 0);
|
||
|
}
|
||
|
|
||
|
visitBinary(ast:Binary) {
|
||
|
var left = ast.left.visit(this);
|
||
|
var right = ast.right.visit(this);
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, ast.operation, _operationToFunction(ast.operation), [left, right], 0);
|
||
|
}
|
||
|
|
||
|
visitPrefixNot(ast:PrefixNot) {
|
||
|
var exp = ast.expression.visit(this)
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "-", _operation_negate, [exp], 0);
|
||
|
}
|
||
|
|
||
|
visitConditional(ast:Conditional) {
|
||
|
var c = ast.condition.visit(this);
|
||
|
var t = ast.trueExp.visit(this);
|
||
|
var f = ast.falseExp.visit(this);
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_PURE_FUNCTION, "?:", _cond, [c,t,f], 0);
|
||
|
}
|
||
|
|
||
|
visitStructural(ast:Structural) {
|
||
|
var value = ast.value.visit(this);
|
||
|
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "record_type_structural_check", null, [], value);
|
||
|
}
|
||
|
|
||
|
visitKeyedAccess(ast:KeyedAccess) {
|
||
|
var obj = ast.obj.visit(this);
|
||
|
var key = ast.key.visit(this);
|
||
|
return this._addRecord(RECORD_TYPE_INVOKE_METHOD, "[]", _keyedAccess, [key], obj);
|
||
|
}
|
||
|
|
||
|
_visitAll(asts:List) {
|
||
|
var res = ListWrapper.createFixedSize(asts.length);
|
||
|
for (var i = 0; i < asts.length; ++i) {
|
||
|
res[i] = asts[i].visit(this);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
_addRecord(type, name, funcOrValue, args, context) {
|
||
|
var record_type_selfIndex = ++ this.contextIndex;
|
||
|
ListWrapper.push(this.protoRecords,
|
||
|
new ProtoRecord(type, name, funcOrValue, args, context, record_type_selfIndex,
|
||
|
this.bindingMemento, this.groupMemento, false, this.expressionAsString));
|
||
|
return record_type_selfIndex;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _arrayFn(length:int) {
|
||
|
switch (length) {
|
||
|
case 0: return () => [];
|
||
|
case 1: return (a1) => [a1];
|
||
|
case 2: return (a1, a2) => [a1, a2];
|
||
|
case 3: return (a1, a2, a3) => [a1, a2, a3];
|
||
|
case 4: return (a1, a2, a3, a4) => [a1, a2, a3, a4];
|
||
|
case 5: return (a1, a2, a3, a4, a5) => [a1, a2, a3, a4, a5];
|
||
|
case 6: return (a1, a2, a3, a4, a5, a6) => [a1, a2, a3, a4, a5, a6];
|
||
|
case 7: return (a1, a2, a3, a4, a5, a6, a7) => [a1, a2, a3, a4, a5, a6, a7];
|
||
|
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => [a1, a2, a3, a4, a5, a6, a7, a8];
|
||
|
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => [a1, a2, a3, a4, a5, a6, a7, a8, a9];
|
||
|
default: throw new BaseException(`Does not support literal arrays with more than 9 elements`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _mapFn(keys:List, length:int) {
|
||
|
function buildMap(values) {
|
||
|
var res = StringMapWrapper.create();
|
||
|
for(var i = 0; i < keys.length; ++i) {
|
||
|
StringMapWrapper.set(res, keys[i], values[i]);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
switch (length) {
|
||
|
case 0: return () => [];
|
||
|
case 1: return (a1) => buildMap([a1]);
|
||
|
case 2: return (a1, a2) => buildMap([a1, a2]);
|
||
|
case 3: return (a1, a2, a3) => buildMap([a1, a2, a3]);
|
||
|
case 4: return (a1, a2, a3, a4) => buildMap([a1, a2, a3, a4]);
|
||
|
case 5: return (a1, a2, a3, a4, a5) => buildMap([a1, a2, a3, a4, a5]);
|
||
|
case 6: return (a1, a2, a3, a4, a5, a6) => buildMap([a1, a2, a3, a4, a5, a6]);
|
||
|
case 7: return (a1, a2, a3, a4, a5, a6, a7) => buildMap([a1, a2, a3, a4, a5, a6, a7]);
|
||
|
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8]);
|
||
|
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => buildMap([a1, a2, a3, a4, a5, a6, a7, a8, a9]);
|
||
|
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _operationToFunction(operation:string):Function {
|
||
|
switch(operation) {
|
||
|
case '+' : return _operation_add;
|
||
|
case '-' : return _operation_subtract;
|
||
|
case '*' : return _operation_multiply;
|
||
|
case '/' : return _operation_divide;
|
||
|
case '%' : return _operation_remainder;
|
||
|
case '==' : return _operation_equals;
|
||
|
case '!=' : return _operation_not_equals;
|
||
|
case '<' : return _operation_less_then;
|
||
|
case '>' : return _operation_greater_then;
|
||
|
case '<=' : return _operation_less_or_equals_then;
|
||
|
case '>=' : return _operation_greater_or_equals_then;
|
||
|
case '&&' : return _operation_logical_and;
|
||
|
case '||' : return _operation_logical_or;
|
||
|
default: throw new BaseException(`Unsupported operation ${operation}`);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function _operation_negate(value) {return !value;}
|
||
|
function _operation_add(left, right) {return left + right;}
|
||
|
function _operation_subtract(left, right) {return left - right;}
|
||
|
function _operation_multiply(left, right) {return left * right;}
|
||
|
function _operation_divide(left, right) {return left / right;}
|
||
|
function _operation_remainder(left, right) {return left % right;}
|
||
|
function _operation_equals(left, right) {return left == right;}
|
||
|
function _operation_not_equals(left, right) {return left != right;}
|
||
|
function _operation_less_then(left, right) {return left < right;}
|
||
|
function _operation_greater_then(left, right) {return left > right;}
|
||
|
function _operation_less_or_equals_then(left, right) {return left <= right;}
|
||
|
function _operation_greater_or_equals_then(left, right) {return left >= right;}
|
||
|
function _operation_logical_and(left, right) {return left && right;}
|
||
|
function _operation_logical_or(left, right) {return left || right;}
|
||
|
function _cond(cond, trueVal, falseVal) {return cond ? trueVal : falseVal;}
|
||
|
|
||
|
function _keyedAccess(obj, args) {
|
||
|
return obj[args[0]];
|
||
|
}
|