390 lines
13 KiB
JavaScript
390 lines
13 KiB
JavaScript
import {isPresent, isBlank, BaseException, Type, isString} from 'angular2/src/facade/lang';
|
|
import {List, ListWrapper, MapWrapper, StringMapWrapper} from 'angular2/src/facade/collection';
|
|
|
|
import {
|
|
AccessMember,
|
|
Assignment,
|
|
AST,
|
|
ASTWithSource,
|
|
AstVisitor,
|
|
Binary,
|
|
Chain,
|
|
Structural,
|
|
Conditional,
|
|
Formatter,
|
|
FunctionCall,
|
|
ImplicitReceiver,
|
|
Interpolation,
|
|
KeyedAccess,
|
|
LiteralArray,
|
|
LiteralMap,
|
|
LiteralPrimitive,
|
|
MethodCall,
|
|
PrefixNot
|
|
} from './parser/ast';
|
|
|
|
import {ContextWithVariableBindings} from './parser/context_with_variable_bindings';
|
|
import {ChangeRecord, ChangeDispatcher, ChangeDetector} from './interfaces';
|
|
import {ChangeDetectionUtil} from './change_detection_util';
|
|
import {DynamicChangeDetector} from './dynamic_change_detector';
|
|
import {ChangeDetectorJITGenerator} from './change_detection_jit_generator';
|
|
|
|
import {ArrayChanges} from './array_changes';
|
|
import {KeyValueChanges} from './keyvalue_changes';
|
|
import {coalesce} from './coalesce';
|
|
|
|
export const RECORD_TYPE_SELF = 0;
|
|
export const RECORD_TYPE_CONST = 1;
|
|
export const RECORD_TYPE_PRIMITIVE_OP = 2;
|
|
export const RECORD_TYPE_PROPERTY = 3;
|
|
export const RECORD_TYPE_INVOKE_METHOD = 4;
|
|
export const RECORD_TYPE_INVOKE_CLOSURE = 5;
|
|
export const RECORD_TYPE_KEYED_ACCESS = 6;
|
|
export const RECORD_TYPE_INVOKE_FORMATTER = 7;
|
|
export const RECORD_TYPE_STRUCTURAL_CHECK = 8;
|
|
export const RECORD_TYPE_INTERPOLATE = 9;
|
|
|
|
export class ProtoRecord {
|
|
mode:number;
|
|
name:string;
|
|
funcOrValue:any;
|
|
args:List;
|
|
fixedArgs:List;
|
|
contextIndex:number;
|
|
selfIndex:number;
|
|
bindingMemento:any;
|
|
groupMemento:any;
|
|
lastInBinding:boolean;
|
|
lastInGroup:boolean;
|
|
expressionAsString:string;
|
|
|
|
constructor(mode:number,
|
|
name:string,
|
|
funcOrValue,
|
|
args:List,
|
|
fixedArgs:List,
|
|
contextIndex:number,
|
|
selfIndex:number,
|
|
bindingMemento:any,
|
|
groupMemento:any,
|
|
expressionAsString:string,
|
|
lastInBinding:boolean,
|
|
lastInGroup:boolean) {
|
|
|
|
this.mode = mode;
|
|
this.name = name;
|
|
this.funcOrValue = funcOrValue;
|
|
this.args = args;
|
|
this.fixedArgs = fixedArgs;
|
|
this.contextIndex = contextIndex;
|
|
this.selfIndex = selfIndex;
|
|
this.bindingMemento = bindingMemento;
|
|
this.groupMemento = groupMemento;
|
|
this.lastInBinding = lastInBinding;
|
|
this.lastInGroup = lastInGroup;
|
|
this.expressionAsString = expressionAsString;
|
|
}
|
|
|
|
isPureFunction():boolean {
|
|
return this.mode === RECORD_TYPE_INTERPOLATE ||
|
|
this.mode === RECORD_TYPE_INVOKE_FORMATTER ||
|
|
this.mode === RECORD_TYPE_PRIMITIVE_OP;
|
|
}
|
|
}
|
|
|
|
export class ProtoChangeDetector {
|
|
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false){}
|
|
instantiate(dispatcher:any, formatters:Map):ChangeDetector{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export class DynamicProtoChangeDetector extends ProtoChangeDetector {
|
|
_records:List<ProtoRecord>;
|
|
_recordBuilder:ProtoRecordBuilder;
|
|
|
|
constructor() {
|
|
this._records = null;
|
|
this._recordBuilder = new ProtoRecordBuilder();
|
|
}
|
|
|
|
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
|
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
|
|
}
|
|
|
|
instantiate(dispatcher:any, formatters:Map) {
|
|
this._createRecordsIfNecessary();
|
|
return new DynamicChangeDetector(dispatcher, formatters, this._records);
|
|
}
|
|
|
|
_createRecordsIfNecessary() {
|
|
if (isBlank(this._records)) {
|
|
var records = this._recordBuilder.records;
|
|
this._records = coalesce(records);
|
|
}
|
|
}
|
|
}
|
|
|
|
var _jitProtoChangeDetectorClassCounter:number = 0;
|
|
export class JitProtoChangeDetector extends ProtoChangeDetector {
|
|
_factory:Function;
|
|
_recordBuilder:ProtoRecordBuilder;
|
|
|
|
constructor() {
|
|
this._factory = null;
|
|
this._recordBuilder = new ProtoRecordBuilder();
|
|
}
|
|
|
|
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
|
this._recordBuilder.addAst(ast, bindingMemento, groupMemento, structural);
|
|
}
|
|
|
|
instantiate(dispatcher:any, formatters:Map) {
|
|
this._createFactoryIfNecessary();
|
|
return this._factory(dispatcher, formatters);
|
|
}
|
|
|
|
_createFactoryIfNecessary() {
|
|
if (isBlank(this._factory)) {
|
|
var c = _jitProtoChangeDetectorClassCounter++;
|
|
var records = coalesce(this._recordBuilder.records);
|
|
var typeName = `ChangeDetector${c}`;
|
|
this._factory = new ChangeDetectorJITGenerator(typeName, records).generate();
|
|
}
|
|
}
|
|
}
|
|
|
|
class ProtoRecordBuilder {
|
|
records:List<ProtoRecord>;
|
|
|
|
constructor() {
|
|
this.records = [];
|
|
}
|
|
|
|
addAst(ast:AST, bindingMemento:any, groupMemento:any = null, structural:boolean = false) {
|
|
if (structural) ast = new Structural(ast);
|
|
|
|
var last = ListWrapper.last(this.records);
|
|
if (isPresent(last) && last.groupMemento == groupMemento) {
|
|
last.lastInGroup = false;
|
|
}
|
|
|
|
var pr = _ConvertAstIntoProtoRecords.convert(ast, bindingMemento, groupMemento, this.records.length);
|
|
if (! ListWrapper.isEmpty(pr)) {
|
|
var last = ListWrapper.last(pr);
|
|
last.lastInBinding = true;
|
|
last.lastInGroup = true;
|
|
|
|
this.records = ListWrapper.concat(this.records, pr);
|
|
}
|
|
}
|
|
}
|
|
|
|
class _ConvertAstIntoProtoRecords {
|
|
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;
|
|
}
|
|
|
|
static convert(ast:AST, bindingMemento:any, groupMemento:any, contextIndex:number) {
|
|
var c = new _ConvertAstIntoProtoRecords(bindingMemento, groupMemento, contextIndex, ast.toString());
|
|
ast.visit(c);
|
|
return c.protoRecords;
|
|
}
|
|
|
|
visitImplicitReceiver(ast:ImplicitReceiver) {
|
|
return 0;
|
|
}
|
|
|
|
visitInterpolation(ast:Interpolation) {
|
|
var args = this._visitAll(ast.expressions);
|
|
return this._addRecord(RECORD_TYPE_INTERPOLATE, "interpolate", _interpolationFn(ast.strings),
|
|
args, ast.strings, 0);
|
|
}
|
|
|
|
visitLiteralPrimitive(ast:LiteralPrimitive) {
|
|
return this._addRecord(RECORD_TYPE_CONST, "literal", ast.value, [], null, 0);
|
|
}
|
|
|
|
visitAccessMember(ast:AccessMember) {
|
|
var receiver = ast.receiver.visit(this);
|
|
return this._addRecord(RECORD_TYPE_PROPERTY, ast.name, ast.getter, [], null, receiver);
|
|
}
|
|
|
|
visitFormatter(ast:Formatter) {
|
|
return this._addRecord(RECORD_TYPE_INVOKE_FORMATTER, ast.name, ast.name, this._visitAll(ast.allArgs), null, 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, null, receiver);
|
|
}
|
|
|
|
visitFunctionCall(ast:FunctionCall) {
|
|
var target = ast.target.visit(this);
|
|
var args = this._visitAll(ast.args);
|
|
return this._addRecord(RECORD_TYPE_INVOKE_CLOSURE, "closure", null, args, null, target);
|
|
}
|
|
|
|
visitLiteralArray(ast:LiteralArray) {
|
|
var primitiveName = `arrayFn${ast.expressions.length}`;
|
|
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, primitiveName, _arrayFn(ast.expressions.length),
|
|
this._visitAll(ast.expressions), null, 0);
|
|
}
|
|
|
|
visitLiteralMap(ast:LiteralMap) {
|
|
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _mapPrimitiveName(ast.keys),
|
|
ChangeDetectionUtil.mapFn(ast.keys), this._visitAll(ast.values), null, 0);
|
|
}
|
|
|
|
visitBinary(ast:Binary) {
|
|
var left = ast.left.visit(this);
|
|
var right = ast.right.visit(this);
|
|
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, _operationToPrimitiveName(ast.operation),
|
|
_operationToFunction(ast.operation), [left, right], null, 0);
|
|
}
|
|
|
|
visitPrefixNot(ast:PrefixNot) {
|
|
var exp = ast.expression.visit(this)
|
|
return this._addRecord(RECORD_TYPE_PRIMITIVE_OP, "operation_negate",
|
|
ChangeDetectionUtil.operation_negate, [exp], null, 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_PRIMITIVE_OP, "cond",
|
|
ChangeDetectionUtil.cond, [c,t,f], null, 0);
|
|
}
|
|
|
|
visitStructural(ast:Structural) {
|
|
var value = ast.value.visit(this);
|
|
return this._addRecord(RECORD_TYPE_STRUCTURAL_CHECK, "structural", null, [], null, value);
|
|
}
|
|
|
|
visitKeyedAccess(ast:KeyedAccess) {
|
|
var obj = ast.obj.visit(this);
|
|
var key = ast.key.visit(this);
|
|
return this._addRecord(RECORD_TYPE_KEYED_ACCESS, "keyedAccess",
|
|
ChangeDetectionUtil.keyedAccess, [key], null, 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, fixedArgs, context) {
|
|
var selfIndex = ++ this.contextIndex;
|
|
ListWrapper.push(this.protoRecords,
|
|
new ProtoRecord(type, name, funcOrValue, args, fixedArgs, context, selfIndex,
|
|
this.bindingMemento, this.groupMemento, this.expressionAsString, false, false));
|
|
return selfIndex;
|
|
}
|
|
}
|
|
|
|
|
|
function _arrayFn(length:number):Function {
|
|
switch (length) {
|
|
case 0: return ChangeDetectionUtil.arrayFn0;
|
|
case 1: return ChangeDetectionUtil.arrayFn1;
|
|
case 2: return ChangeDetectionUtil.arrayFn2;
|
|
case 3: return ChangeDetectionUtil.arrayFn3;
|
|
case 4: return ChangeDetectionUtil.arrayFn4;
|
|
case 5: return ChangeDetectionUtil.arrayFn5;
|
|
case 6: return ChangeDetectionUtil.arrayFn6;
|
|
case 7: return ChangeDetectionUtil.arrayFn7;
|
|
case 8: return ChangeDetectionUtil.arrayFn8;
|
|
case 9: return ChangeDetectionUtil.arrayFn9;
|
|
default: throw new BaseException(`Does not support literal maps with more than 9 elements`);
|
|
}
|
|
}
|
|
|
|
function _mapPrimitiveName(keys:List) {
|
|
var stringifiedKeys = ListWrapper.join(
|
|
ListWrapper.map(keys, (k) => isString(k) ? `"${k}"` : `${k}`),
|
|
", ");
|
|
return `mapFn([${stringifiedKeys}])`;
|
|
}
|
|
|
|
function _operationToPrimitiveName(operation:string):string {
|
|
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 _operationToFunction(operation:string):Function {
|
|
switch(operation) {
|
|
case '+' : return ChangeDetectionUtil.operation_add;
|
|
case '-' : return ChangeDetectionUtil.operation_subtract;
|
|
case '*' : return ChangeDetectionUtil.operation_multiply;
|
|
case '/' : return ChangeDetectionUtil.operation_divide;
|
|
case '%' : return ChangeDetectionUtil.operation_remainder;
|
|
case '==' : return ChangeDetectionUtil.operation_equals;
|
|
case '!=' : return ChangeDetectionUtil.operation_not_equals;
|
|
case '<' : return ChangeDetectionUtil.operation_less_then;
|
|
case '>' : return ChangeDetectionUtil.operation_greater_then;
|
|
case '<=' : return ChangeDetectionUtil.operation_less_or_equals_then;
|
|
case '>=' : return ChangeDetectionUtil.operation_greater_or_equals_then;
|
|
case '&&' : return ChangeDetectionUtil.operation_logical_and;
|
|
case '||' : return ChangeDetectionUtil.operation_logical_or;
|
|
default: throw new BaseException(`Unsupported operation ${operation}`);
|
|
}
|
|
}
|
|
|
|
function s(v) {
|
|
return isPresent(v) ? '' + v : '';
|
|
}
|
|
|
|
function _interpolationFn(strings:List) {
|
|
var length = strings.length;
|
|
var c0 = length > 0 ? strings[0] : null;
|
|
var c1 = length > 1 ? strings[1] : null;
|
|
var c2 = length > 2 ? strings[2] : null;
|
|
var c3 = length > 3 ? strings[3] : null;
|
|
var c4 = length > 4 ? strings[4] : null;
|
|
var c5 = length > 5 ? strings[5] : null;
|
|
var c6 = length > 6 ? strings[6] : null;
|
|
var c7 = length > 7 ? strings[7] : null;
|
|
var c8 = length > 8 ? strings[8] : null;
|
|
var c9 = length > 9 ? strings[9] : null;
|
|
switch (length - 1) {
|
|
case 1: return (a1) => c0 + s(a1) + c1;
|
|
case 2: return (a1, a2) => c0 + s(a1) + c1 + s(a2) + c2;
|
|
case 3: return (a1, a2, a3) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3;
|
|
case 4: return (a1, a2, a3, a4) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4;
|
|
case 5: return (a1, a2, a3, a4, a5) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5;
|
|
case 6: return (a1, a2, a3, a4, a5, a6) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6;
|
|
case 7: return (a1, a2, a3, a4, a5, a6, a7) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7;
|
|
case 8: return (a1, a2, a3, a4, a5, a6, a7, a8) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8;
|
|
case 9: return (a1, a2, a3, a4, a5, a6, a7, a8, a9) => c0 + s(a1) + c1 + s(a2) + c2 + s(a3) + c3 + s(a4) + c4 + s(a5) + c5 + s(a6) + c6 + s(a7) + c7 + s(a8) + c8 + s(a9) + c9;
|
|
default: throw new BaseException(`Does not support more than 9 expressions`);
|
|
}
|
|
} |