diff --git a/bin/parser_generator_for_spec.dart b/bin/parser_generator_for_spec.dart index b1e6396c8..8820674e2 100644 --- a/bin/parser_generator_for_spec.dart +++ b/bin/parser_generator_for_spec.dart @@ -120,6 +120,14 @@ main(arguments) { 'true', 'true || run()', 'undefined', + 'null < 0', + 'null * 3', + 'null + 6', + '5 + null', + 'null - 4', + '3 - null', + 'null + null', + 'null - null', ';;1;;', '1==1', diff --git a/lib/core/parser/eval.dart b/lib/core/parser/eval.dart index aa0e1aef7..0fc59accd 100644 --- a/lib/core/parser/eval.dart +++ b/lib/core/parser/eval.dart @@ -58,6 +58,22 @@ class Binary extends syntax.Binary { case '||': return toBool(left) || toBool(this.right.eval(scope)); } var right = this.right.eval(scope); + + // Null check for the operations. + if (left == null || right == null) { + switch (operation) { + case '+': + if (left != null) return left; + if (right != null) return right; + return 0; + case '-': + if (left != null) return left; + if (right != null) return 0 - right; + return 0; + } + return null; + } + switch (operation) { case '+' : return autoConvertAdd(left, right); case '-' : return left - right; diff --git a/lib/core/parser/utils.dart b/lib/core/parser/utils.dart index d79c9c96f..4bdf372ec 100644 --- a/lib/core/parser/utils.dart +++ b/lib/core/parser/utils.dart @@ -47,7 +47,7 @@ autoConvertAdd(a, b) { } if (a != null) return a; if (b != null) return b; - return null; + return 0; } /** diff --git a/lib/core/scope.dart b/lib/core/scope.dart index 3bdc7417a..d5a69bc64 100644 --- a/lib/core/scope.dart +++ b/lib/core/scope.dart @@ -954,19 +954,19 @@ Function _operationToFunction(String operation) { _operation_negate(value) => !toBool(value); _operation_add(left, right) => autoConvertAdd(left, right); -_operation_subtract(left, right) => left - right; -_operation_multiply(left, right) => left * right; -_operation_divide(left, right) => left / right; -_operation_divide_int(left, right) => left ~/ right; -_operation_remainder(left, right) => left % right; -_operation_equals(left, right) => left == right; -_operation_not_equals(left, right) => left != right; -_operation_less_then(left, right) => left < right; -_operation_greater_then(left, right) => (left == null || right == null) ? false : left > right; -_operation_less_or_equals_then(left, right) => left <= right; -_operation_greater_or_equals_then(left, right) => left >= right; -_operation_power(left, right) => left ^ right; -_operation_bitwise_and(left, right) => left & right; +_operation_subtract(left, right) => (left != null && right != null) ? left - right : (left != null ? left : (right != null ? 0 - right : 0)); +_operation_multiply(left, right) => (left == null || right == null) ? null : left * right; +_operation_divide(left, right) => (left == null || right == null) ? null : left / right; +_operation_divide_int(left, right) => (left == null || right == null) ? null : left ~/ right; +_operation_remainder(left, right) => (left == null || right == null) ? null : left % right; +_operation_equals(left, right) => (left == null || right == null) ? null : left == right; +_operation_not_equals(left, right) => (left == null || right == null) ? null : left != right; +_operation_less_then(left, right) => (left == null || right == null) ? null : left < right; +_operation_greater_then(left, right) => (left == null || right == null) ? null : left > right; +_operation_less_or_equals_then(left, right) => (left == null || right == null) ? null : left <= right; +_operation_greater_or_equals_then(left, right) => (left == null || right == null) ? null : left >= right; +_operation_power(left, right) => (left == null || right == null) ? null : left ^ right; +_operation_bitwise_and(left, right) => (left == null || right == null) ? null : left & right; // TODO(misko): these should short circuit the evaluation. _operation_logical_and(left, right) => toBool(left) && toBool(right); _operation_logical_or(left, right) => toBool(left) || toBool(right); diff --git a/lib/tools/parser_generator/dart_code_gen.dart b/lib/tools/parser_generator/dart_code_gen.dart index 5bac0003c..879a55df5 100644 --- a/lib/tools/parser_generator/dart_code_gen.dart +++ b/lib/tools/parser_generator/dart_code_gen.dart @@ -173,9 +173,12 @@ class DartCodeGenVisitor extends Visitor { String right = evaluate(binary.right, convertToBool: logical); if (operation == '+') { return 'autoConvertAdd($left, $right)'; - } else { - return '($left $operation $right)'; + } else if (operation == '-') { + return '(($left != null && $right != null) ? $left - $right : ($left != null ? $left : ($right != null ? 0 - $right : 0)))'; + } else if (!logical) { + return '($left == null || $right == null ? null : $left $operation $right)'; } + return '($left $operation $right)'; } visitPrefix(Prefix prefix) { diff --git a/test/core/parser/parser_spec.dart b/test/core/parser/parser_spec.dart index 6db4a5b3f..1441a93e5 100644 --- a/test/core/parser/parser_spec.dart +++ b/test/core/parser/parser_spec.dart @@ -283,6 +283,20 @@ main() { }); + it('should eval binary operators with null as null', () { + expect(eval("null < 0")).toEqual(null); + expect(eval("null * 3")).toEqual(null); + + // But + and - are special cases. + expect(eval("null + 6")).toEqual(6); + expect(eval("5 + null")).toEqual(5); + expect(eval("null - 4")).toEqual(-4); + expect(eval("3 - null")).toEqual(3); + expect(eval("null + null")).toEqual(0); + expect(eval("null - null")).toEqual(0); + }); + + it('should pass exceptions through getters', () { expect(() { parser('boo').eval(new ScopeWithErrors()); diff --git a/test/core/scope_spec.dart b/test/core/scope_spec.dart index 40a305efd..bc8b7da2f 100644 --- a/test/core/scope_spec.dart +++ b/test/core/scope_spec.dart @@ -76,6 +76,21 @@ void main() { expect(logger).toEqual([[3, 2, 3], {'a': 3, 'b': 2}]); })); + it('should watch nulls', inject((Logger logger, Map context, RootScope rootScope) { + var r = (value, _) => logger(value); + rootScope + ..watch('null < 0',r) + ..watch('null * 3', r) + ..watch('null + 6', r) + ..watch('5 + null', r) + ..watch('null - 4', r) + ..watch('3 - null', r) + ..watch('null + null', r) + ..watch('null - null', r) + ..digest(); + expect(logger).toEqual([null, null, 6, 5, -4, 3, 0, 0]); + })); + it('should invoke closures', inject((Logger logger, Map context, RootScope rootScope) { context['fn'] = () { logger('fn');