blob: 5bac0003cc392c259382017d0d31ec10cf9b57d3 [file] [log] [blame]
library dart_code_gen;
import 'package:angular/utils.dart' show isReservedWord;
import 'package:angular/core/parser/syntax.dart';
escape(String s) => s.replaceAllMapped(new RegExp(r'(\"|\$|\n)'), (m) {
var char = m[1];
if (char == '\n') char = 'n';
return "\\$char";
});
class DartCodeGen {
final HelperMap getters = new HelperMap('_',
getterTemplate, getterTemplateForReserved);
final HelperMap holders = new HelperMap('_ensure\$',
holderTemplate, holderTemplateForReserved);
final HelperMap setters = new HelperMap('_set\$',
setterTemplate, setterTemplateForReserved);
String generate(Expression expression, bool assign) {
var v = new DartCodeGenVisitor(getters, holders, setters);
return assign ? v.assign(expression)('value') : v.evaluate(expression);
}
}
class DartCodeGenVisitor extends Visitor {
static const int STATE_EVAL = 0;
static const int STATE_EVAL_HOLDER = 1;
static const int STATE_ASSIGN = 2;
int state = STATE_EVAL;
final HelperMap getters;
final HelperMap holders;
final HelperMap setters;
DartCodeGenVisitor(this.getters, this.holders, this.setters);
bool get isEvaluating => state == STATE_EVAL;
bool get isEvaluatingHolder => state == STATE_EVAL_HOLDER;
bool get isAssigning => state == STATE_ASSIGN;
String lookupGetter(String key) => getters.lookup(key);
String lookupHolder(String key) => holders.lookup(key);
String lookupSetter(String key) => setters.lookup(key);
String lookupAccessor(String key) {
if (state == STATE_EVAL) {
return lookupGetter(key);
} else if (state == STATE_EVAL_HOLDER) {
return lookupHolder(key);
} else {
assert(state == STATE_ASSIGN);
return lookupSetter(key);
}
}
String toBool(String value)
=> 'toBool($value)';
String safeCallFunction(String function, String name, String arguments)
=> 'ensureFunction($function, "$name")($arguments)';
String evaluate(Expression expression, {bool convertToBool: false}) {
int old = state;
try {
state = STATE_EVAL;
String result = visit(expression);
if (convertToBool) result = toBool(result);
return result;
} finally {
state = old;
}
}
String evaluateHolder(Expression expression) {
int old = state;
try {
state = STATE_EVAL_HOLDER;
return visit(expression);
} finally {
state = old;
}
}
Function assign(Expression target) {
int old = state;
try {
state = STATE_ASSIGN;
return visit(target);
} finally {
state = old;
}
}
visitChain(Chain chain) {
StringBuffer buffer = new StringBuffer();
buffer.writeln("var result, last;");
for (int i = 0; i < chain.expressions.length; i++) {
String expression = evaluate(chain.expressions[i]);
buffer.writeln('last = $expression;');
buffer.writeln('if (last != null) result = last;');
}
buffer.write('return result;');
return "$buffer";
}
visitFilter(Filter filter) {
List expressions = [ filter.expression ]..addAll(filter.arguments);
String arguments = expressions.map((e) => evaluate(e)).join(', ');
String name = escape(filter.name);
return 'filters("$name")($arguments)';
}
visitAssign(Assign expression) {
String value = evaluate(expression.value);
return assign(expression.target)(value);
}
visitConditional(Conditional conditional) {
String condition = evaluate(conditional.condition, convertToBool: true);
String yes = evaluate(conditional.yes);
String no = evaluate(conditional.no);
return "$condition ? $yes : $no";
}
visitAccessScope(AccessScope access) {
String accessor = lookupAccessor(access.name);
return isAssigning
? (value) => '$accessor(scope, $value)'
: '$accessor(scope)';
}
visitAccessMember(AccessMember access) {
String object = !isEvaluating
? evaluateHolder(access.object)
: evaluate(access.object);
String accessor = lookupAccessor(access.name);
return isAssigning
? (value) => '$accessor($object, $value)'
: '$accessor($object)';
}
visitAccessKeyed(AccessKeyed access) {
String object = evaluate(access.object);
String key = evaluate(access.key);
return (isAssigning)
? (value) => 'setKeyed($object, $key, $value)'
: 'getKeyed($object, $key)';
}
visitCallScope(CallScope call) {
String arguments = call.arguments.map((e) => evaluate(e)).join(', ');
String getter = lookupGetter(call.name);
return safeCallFunction('$getter(scope)', call.name, arguments);
}
visitCallFunction(CallFunction call) {
String function = evaluate(call.function);
String arguments = call.arguments.map((e) => evaluate(e)).join(', ');
return safeCallFunction(function, "${call.function}", arguments);
}
visitCallMember(CallMember call) {
String object = evaluate(call.object);
String arguments = call.arguments.map((e) => evaluate(e)).join(', ');
String getter = lookupGetter(call.name);
return safeCallFunction('$getter($object)', call.name, arguments);
}
visitBinary(Binary binary) {
String operation = binary.operation;
bool logical = (operation == '||') || (operation == '&&');
String left = evaluate(binary.left, convertToBool: logical);
String right = evaluate(binary.right, convertToBool: logical);
if (operation == '+') {
return 'autoConvertAdd($left, $right)';
} else {
return '($left $operation $right)';
}
}
visitPrefix(Prefix prefix) {
String operation = prefix.operation;
bool logical = (operation == '!');
String expression = evaluate(prefix.expression, convertToBool: logical);
return '$operation$expression';
}
visitLiteral(Literal literal) {
return '$literal';
}
visitLiteralString(LiteralString literal) {
return 'r$literal';
}
visitLiteralArray(LiteralArray literal) {
if (literal.elements.isEmpty) return '[]';
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < literal.elements.length; i++) {
if (i != 0) buffer.write(', ');
buffer.write(evaluate(literal.elements[i]));
}
return "[ $buffer ]";
}
visitLiteralObject(LiteralObject literal) {
if (literal.keys.isEmpty) return '{}';
StringBuffer buffer = new StringBuffer();
List<String> keys = literal.keys;
for (int i = 0; i < keys.length; i++) {
if (i != 0) buffer.write(', ');
buffer.write("'${keys[i]}': ");
buffer.write(evaluate(literal.values[i]));
}
return "{ $buffer }";
}
}
class HelperMap {
final Map<String, String> helpers = new Map<String, String>();
final Map<String, String> names = new Map<String, String>();
final String prefix;
final Function template;
final Function templateForReserved;
HelperMap(this.prefix, this.template, this.templateForReserved);
String lookup(String key) {
String name = _computeName(key);
if (helpers.containsKey(key)) return name;
helpers[key] = isReservedWord(key)
? templateForReserved(name, key)
: template(name, key);
return name;
}
String _computeName(String key) {
String result = names[key];
if (result != null) return result;
return names[key] = "$prefix$key";
}
}
// ------------------------------------------------------------------
// Templates for generated getters.
// ------------------------------------------------------------------
String getterTemplate(String name, String key) => """
$name(o) {
if (o == null) return null;
return (o is Map) ? o["${escape(key)}"] : o.$key;
}
""";
String getterTemplateForReserved(String name, String key) => """
$name(o) {
if (o == null) return null;
return (o is Map) ? o["${escape(key)}"] : null;
}
""";
// ------------------------------------------------------------------
// Templates for generated holders (getters for assignment).
// ------------------------------------------------------------------
String holderTemplate(String name, String key) => """
$name(o) {
if (o == null) return null;
if (o is Map) {
var key = "${escape(key)}";
var result = o[key];
return (result == null) ? result = o[key] = {} : result;
} else {
var result = o.$key;
return (result == null) ? result = o.$key = {} : result;
}
}
""";
String holderTemplateForReserved(String name, String key) => """
$name(o) {
if (o == null) return null;
if (o is !Map) return {};
var key = "${escape(key)}";
var result = o[key];
return (result == null) ? result = o[key] = {} : result;
}
""";
// ------------------------------------------------------------------
// Templates for generated setters.
// ------------------------------------------------------------------
String setterTemplate(String name, String key) => """
$name(o, v) {
if (o is Map) o["${escape(key)}"] = v; else o.$key = v;
return v;
}
""";
String setterTemplateForReserved(String name, String key) => """
$name(o, v) {
if (o is Map) o["${escape(key)}"] = v;
return v;
}
""";