refactor(change_detect): Share more codegen logic

Move more logic in our codegen into a shared util which is called by the
Jit & Prege change detector code.
This commit is contained in:
Tim Blasi 2015-08-03 14:10:07 -07:00
parent 392de4af67
commit c58b0ff787
6 changed files with 148 additions and 173 deletions

View File

@ -7,6 +7,7 @@ import {DirectiveIndex, DirectiveRecord} from './directive_record';
import {ProtoRecord, RecordType} from './proto_record';
import {CodegenNameUtil, sanitizeName} from './codegen_name_util';
import {CodegenLogicUtil} from './codegen_logic_util';
/**
@ -24,6 +25,7 @@ var IS_CHANGED_LOCAL = "isChanged";
var CHANGES_LOCAL = "changes";
export class ChangeDetectorJITGenerator {
_logic: CodegenLogicUtil;
_names: CodegenNameUtil;
_typeName: string;
@ -31,6 +33,7 @@ export class ChangeDetectorJITGenerator {
public records: List<ProtoRecord>, public directiveRecords: List<any>,
private generateCheckNoChanges: boolean) {
this._names = new CodegenNameUtil(this.records, this.directiveRecords, UTIL);
this._logic = new CodegenLogicUtil(this._names, UTIL);
this._typeName = sanitizeName(`ChangeDetector_${this.id}`);
}
@ -206,7 +209,7 @@ export class ChangeDetectorJITGenerator {
var oldValue = this._names.getFieldName(r.selfIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var read = `
${this._genUpdateCurrentValue(r)}
${this._logic.genUpdateCurrentValue(r)}
`;
var check = `
@ -232,80 +235,6 @@ export class ChangeDetectorJITGenerator {
}
}
_genUpdateCurrentValue(r: ProtoRecord): string {
var context = (r.contextIndex == -1) ? this._names.getDirectiveName(r.directiveIndex) :
this._names.getLocalName(r.contextIndex);
var newValue = this._names.getLocalName(r.selfIndex);
var argString = r.args.map((arg) => this._names.getLocalName(arg)).join(", ");
var rhs;
switch (r.mode) {
case RecordType.SELF:
rhs = context;
break;
case RecordType.CONST:
rhs = JSON.stringify(r.funcOrValue);
break;
case RecordType.PROPERTY:
rhs = `${context}.${r.name}`;
break;
case RecordType.SAFE_PROPERTY:
rhs = `${UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}`;
break;
case RecordType.LOCAL:
rhs = `${this._names.getLocalsAccessorName()}.get('${r.name}')`;
break;
case RecordType.INVOKE_METHOD:
rhs = `${context}.${r.name}(${argString})`;
break;
case RecordType.SAFE_INVOKE_METHOD:
rhs = `${UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}(${argString})`;
break;
case RecordType.INVOKE_CLOSURE:
rhs = `${context}(${argString})`;
break;
case RecordType.PRIMITIVE_OP:
rhs = `${UTIL}.${r.name}(${argString})`;
break;
case RecordType.COLLECTION_LITERAL:
rhs = `${UTIL}.${r.name}(${argString})`;
break;
case RecordType.INTERPOLATE:
rhs = this._genInterpolation(r);
break;
case RecordType.KEYED_ACCESS:
rhs = `${context}[${this._names.getLocalName(r.args[0])}]`;
break;
default:
throw new BaseException(`Unknown operation ${r.mode}`);
}
return `${newValue} = ${rhs}`;
}
_genInterpolation(r: ProtoRecord): string {
var res = "";
for (var i = 0; i < r.args.length; ++i) {
res += JSON.stringify(r.fixedArgs[i]);
res += " + ";
res += `${UTIL}.s(${this._names.getLocalName(r.args[i])})`;
res += " + ";
}
res += JSON.stringify(r.fixedArgs[r.args.length]);
return res;
}
_genChangeMarker(r: ProtoRecord): string {
return r.argumentToPureFunction ? `${this._names.getChangeName(r.selfIndex)} = true` : ``;
}

View File

@ -0,0 +1,15 @@
library angular2.src.change_detection.codegen_facade;
import 'dart:convert' show JSON;
/// Converts `funcOrValue` to a string which can be used in generated code.
String codify(funcOrValue) => JSON.encode(funcOrValue).replaceAll(r'$', r'\$');
/// Combine the strings of generated code into a single interpolated string.
/// Each element of `vals` is expected to be a string literal or a codegen'd
/// call to a method returning a string.
/// The return format interpolates each value as an expression which reads
/// poorly, but the resulting code is easily flattened by dart2js.
String combineGeneratedStrings(List<String> vals) {
return '"${vals.map((v) => '\${$v}').join('')}"';
}

View File

@ -0,0 +1,17 @@
import {List} from 'angular2/src/facade/collection';
/**
* Converts `funcOrValue` to a string which can be used in generated code.
*/
export function codify(obj: any): string {
return JSON.stringify(obj);
}
/**
* Combine the strings of generated code into a single interpolated string.
* Each element of `vals` is expected to be a string literal or a codegen'd
* call to a method returning a string.
*/
export function combineGeneratedStrings(vals: string[]): string {
return vals.join(' + ');
}

View File

@ -0,0 +1,90 @@
import {ListWrapper} from 'angular2/src/facade/collection';
import {BaseException, Json} from 'angular2/src/facade/lang';
import {CodegenNameUtil} from './codegen_name_util';
import {codify, combineGeneratedStrings} from './codegen_facade';
import {ProtoRecord, RecordType} from './proto_record';
/**
* Class responsible for providing change detection logic for chagne detector classes.
*/
export class CodegenLogicUtil {
constructor(private _names: CodegenNameUtil, private _utilName: string) {}
/**
* Generates a statement which updates the local variable representing `protoRec` with the current
* value of the record.
*/
genUpdateCurrentValue(protoRec: ProtoRecord): string {
var context = (protoRec.contextIndex == -1) ?
this._names.getDirectiveName(protoRec.directiveIndex) :
this._names.getLocalName(protoRec.contextIndex);
var argString =
ListWrapper.map(protoRec.args, (arg) => this._names.getLocalName(arg)).join(", ");
var rhs: string;
switch (protoRec.mode) {
case RecordType.SELF:
rhs = context;
break;
case RecordType.CONST:
rhs = codify(protoRec.funcOrValue);
break;
case RecordType.PROPERTY:
rhs = `${context}.${protoRec.name}`;
break;
case RecordType.SAFE_PROPERTY:
rhs = `${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}`;
break;
case RecordType.LOCAL:
rhs = `${this._names.getLocalsAccessorName()}.get('${protoRec.name}')`;
break;
case RecordType.INVOKE_METHOD:
rhs = `${context}.${protoRec.name}(${argString})`;
break;
case RecordType.SAFE_INVOKE_METHOD:
rhs =
`${this._utilName}.isValueBlank(${context}) ? null : ${context}.${protoRec.name}(${argString})`;
break;
case RecordType.INVOKE_CLOSURE:
rhs = `${context}(${argString})`;
break;
case RecordType.PRIMITIVE_OP:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.COLLECTION_LITERAL:
rhs = `${this._utilName}.${protoRec.name}(${argString})`;
break;
case RecordType.INTERPOLATE:
rhs = this._genInterpolation(protoRec);
break;
case RecordType.KEYED_ACCESS:
rhs = `${context}[${this._names.getLocalName(protoRec.args[0])}]`;
break;
default:
throw new BaseException(`Unknown operation ${protoRec.mode}`);
}
return `${this._names.getLocalName(protoRec.selfIndex)} = ${rhs};`;
}
_genInterpolation(protoRec: ProtoRecord): string {
var iVals = [];
for (var i = 0; i < protoRec.args.length; ++i) {
iVals.push(codify(protoRec.fixedArgs[i]));
iVals.push(`${this._utilName}.s(${this._names.getLocalName(protoRec.args[i])})`);
}
iVals.push(codify(protoRec.fixedArgs[protoRec.args.length]));
return combineGeneratedStrings(iVals);
}
}

View File

@ -1,8 +1,9 @@
library angular2.transform.template_compiler.change_detector_codegen;
import 'dart:convert' show JSON;
import 'package:angular2/src/change_detection/change_detection_util.dart';
import 'package:angular2/src/change_detection/coalesce.dart';
import 'package:angular2/src/change_detection/codegen_facade.dart';
import 'package:angular2/src/change_detection/codegen_logic_util.dart';
import 'package:angular2/src/change_detection/codegen_name_util.dart';
import 'package:angular2/src/change_detection/directive_record.dart';
import 'package:angular2/src/change_detection/interfaces.dart';
@ -73,27 +74,26 @@ class _CodegenState {
final String _changeDetectionMode;
final List<ProtoRecord> _records;
final List<DirectiveRecord> _directiveRecords;
final CodegenLogicUtil _logic;
final CodegenNameUtil _names;
final bool _generateCheckNoChanges;
_CodegenState._(this._changeDetectorDefId, this._contextTypeName,
this._changeDetectorTypeName, String changeDetectionStrategy,
List<ProtoRecord> records, List<DirectiveRecord> directiveRecords,
this._records, this._directiveRecords, this._logic, this._names,
this._generateCheckNoChanges)
: _records = records,
_directiveRecords = directiveRecords,
_names = new CodegenNameUtil(records, directiveRecords, _UTIL),
_changeDetectionMode = ChangeDetectionUtil
.changeDetectionMode(changeDetectionStrategy);
: _changeDetectionMode = ChangeDetectionUtil
.changeDetectionMode(changeDetectionStrategy);
factory _CodegenState(String typeName, String changeDetectorTypeName,
ChangeDetectorDefinition def) {
var protoRecords = new ProtoRecordBuilder();
def.bindingRecords
.forEach((rec) => protoRecords.add(rec, def.variableNames));
var records = coalesce(protoRecords.records);
var recBuilder = new ProtoRecordBuilder();
def.bindingRecords.forEach((rec) => recBuilder.add(rec, def.variableNames));
var protoRecords = coalesce(recBuilder.records);
var names = new CodegenNameUtil(protoRecords, def.directiveRecords, _UTIL);
var logic = new CodegenLogicUtil(names, _UTIL);
return new _CodegenState._(def.id, typeName, changeDetectorTypeName,
def.strategy, records, def.directiveRecords,
def.strategy, protoRecords, def.directiveRecords, logic, names,
def.generateCheckNoChanges);
}
@ -103,7 +103,7 @@ class _CodegenState {
${_genDeclareFields()}
$_changeDetectorTypeName(dispatcher, protos, directiveRecords)
: super(${_encodeValue(_changeDetectorDefId)},
: super(${codify(_changeDetectorDefId)},
dispatcher, protos, directiveRecords, '$_changeDetectionMode') {
dehydrateDirectives(false);
}
@ -284,7 +284,7 @@ class _CodegenState {
var oldValue = _names.getFieldName(r.selfIndex);
var newValue = _names.getLocalName(r.selfIndex);
var read = '''
${_genUpdateCurrentValue(r)}
${_logic.genUpdateCurrentValue(r)}
''';
var check = '''
@ -303,7 +303,7 @@ class _CodegenState {
var condition = r.args.map((a) => _names.getChangeName(a)).join(' || ');
if (r.isUsedByOtherRecord()) {
return 'if ($condition) { $genCode } else { $newValue = $oldValue; }';
} else {
} else {
return 'if ($condition) { $genCode }';
}
} else {
@ -311,85 +311,10 @@ class _CodegenState {
}
}
String _genUpdateCurrentValue(ProtoRecord r) {
var context = r.contextIndex == -1
? _names.getDirectiveName(r.directiveIndex)
: _names.getLocalName(r.contextIndex);
var newValue = _names.getLocalName(r.selfIndex);
var argString = r.args.map((arg) => _names.getLocalName(arg)).join(', ');
var rhs;
switch (r.mode) {
case RecordType.SELF:
rhs = context;
break;
case RecordType.CONST:
rhs = _encodeValue(r.funcOrValue);
break;
case RecordType.PROPERTY:
rhs = '$context.${r.name}';
break;
case RecordType.SAFE_PROPERTY:
rhs = '${_UTIL}.isValueBlank(${context}) ? null : ${context}.${r.name}';
break;
case RecordType.LOCAL:
rhs = '${_names.getLocalsAccessorName()}.get("${r.name}")';
break;
case RecordType.INVOKE_METHOD:
rhs = '$context.${r.name}($argString)';
break;
case RecordType.SAFE_INVOKE_METHOD:
rhs = '${_UTIL}.isValueBlank(${context}) '
'? null : ${context}.${r.name}(${argString})';
break;
case RecordType.INVOKE_CLOSURE:
rhs = '$context($argString)';
break;
case RecordType.PRIMITIVE_OP:
rhs = '$_UTIL.${r.name}($argString)';
break;
case RecordType.COLLECTION_LITERAL:
rhs = '$_UTIL.${r.name}($argString)';
break;
case RecordType.INTERPOLATE:
rhs = _genInterpolation(r);
break;
case RecordType.KEYED_ACCESS:
rhs = '$context[${_names.getLocalName(r.args[0])}]';
break;
default:
throw new FormatException(
'Unknown operation ${r.mode}', r.expressionAsString);
}
return '$newValue = $rhs;';
}
String _genInterpolation(ProtoRecord r) {
var res = new StringBuffer();
for (var i = 0; i < r.args.length; ++i) {
var name = _names.getLocalName(r.args[i]);
res.write(
'${_encodeValue(r.fixedArgs[i])} "\$\{$name == null ? "" : $name\}" ');
}
res.write(_encodeValue(r.fixedArgs[r.args.length]));
return '$res';
}
String _genChangeMarker(ProtoRecord r) {
return r.argumentToPureFunction ? "${this._names.getChangeName(r.selfIndex)} = true;" : "";
return r.argumentToPureFunction
? "${this._names.getChangeName(r.selfIndex)} = true;"
: "";
}
String _genUpdateDirectiveOrElement(ProtoRecord r) {
@ -438,7 +363,9 @@ class _CodegenState {
String _maybeFirstInBinding(ProtoRecord r) {
var prev = ChangeDetectionUtil.protoByIndex(_records, r.selfIndex - 1);
var firstInBindng = prev == null || prev.bindingRecord != r.bindingRecord;
return firstInBindng ? "${_names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};" : '';
return firstInBindng
? "${_names.getFirstProtoInCurrentBinding()} = ${r.selfIndex};"
: '';
}
String _genAddToChanges(ProtoRecord r) {
@ -485,9 +412,6 @@ class _CodegenState {
}
''';
}
String _encodeValue(funcOrValue) =>
JSON.encode(funcOrValue).replaceAll(r'$', r'\$');
}
const PROTO_CHANGE_DETECTOR_FACTORY_METHOD = 'newProtoChangeDetector';

View File

@ -51,7 +51,7 @@ class _MyComponent_ChangeDetector0
}
if (c_myNum0) {
l_interpolate1 =
"Salad: " "${l_myNum0 == null ? "" : l_myNum0}" " is awesome";
"${"Salad: "}${_gen.ChangeDetectionUtil.s(l_myNum0)}${" is awesome"}";
if (_gen.looseNotIdentical(l_interpolate1, this.interpolate1)) {
if (throwOnChange) {
this.throwOnChangeError(this.interpolate1, l_interpolate1);