Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
feat($parse): added constant and literal properties
Browse files Browse the repository at this point in the history
* `literal` is set to true if the expression's top-level is a JavaScript
  literal (number, string, boolean, null/undefined, array, object), even
  if it contains non-literals inside.

* `constant` is set to true if the expression is known to be made
  entirely of constant values, i.e., evaluating it will always yield the
  same result.

A consequence is that a JSON expression is guaranteed to be both literal
and constant.
  • Loading branch information
mernen authored and mhevery committed Feb 14, 2013
1 parent 3b14092 commit 1ed6385
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 11 deletions.
51 changes: 40 additions & 11 deletions src/ng/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ function parser(text, json, $filter, csp){
if (tokens.length !== 0) {
throwError("is an unexpected token", tokens[0]);
}
value.literal = !!value.literal;
value.constant = !!value.constant;
return value;

///////////////////////////////////
Expand Down Expand Up @@ -350,15 +352,19 @@ function parser(text, json, $filter, csp){
}

function unaryFn(fn, right) {
return function(self, locals) {
return extend(function(self, locals) {
return fn(self, locals, right);
};
}, {
constant:right.constant
});
}

function binaryFn(left, fn, right) {
return function(self, locals) {
return extend(function(self, locals) {
return fn(self, locals, left, right);
};
}, {
constant:left.constant && right.constant
});
}

function statements() {
Expand Down Expand Up @@ -526,6 +532,9 @@ function parser(text, json, $filter, csp){
if (!primary) {
throwError("not a primary expression", token);
}
if (token.json) {
primary.constant = primary.literal = true;
}
}

var next, context;
Expand Down Expand Up @@ -614,42 +623,57 @@ function parser(text, json, $filter, csp){
// This is used with json array declaration
function arrayDeclaration () {
var elementFns = [];
var allConstant = true;
if (peekToken().text != ']') {
do {
elementFns.push(expression());
var elementFn = expression();
elementFns.push(elementFn);
if (!elementFn.constant) {
allConstant = false;
}
} while (expect(','));
}
consume(']');
return function(self, locals){
return extend(function(self, locals){
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
array.push(elementFns[i](self, locals));
}
return array;
};
}, {
literal:true,
constant:allConstant
});
}

function object () {
var keyValues = [];
var allConstant = true;
if (peekToken().text != '}') {
do {
var token = expect(),
key = token.string || token.text;
consume(":");
var value = expression();
keyValues.push({key:key, value:value});
if (!value.constant) {
allConstant = false;
}
} while (expect(','));
}
consume('}');
return function(self, locals){
return extend(function(self, locals){
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
var value = keyValue.value(self, locals);
object[keyValue.key] = value;
}
return object;
};
}, {
literal:true,
constant:allConstant
});
}
}

Expand Down Expand Up @@ -853,8 +877,13 @@ function getterFn(path, csp) {
* * `locals` – `{object=}` – local variables context object, useful for overriding values in
* `context`.
*
* The return function also has an `assign` property, if the expression is assignable, which
* allows one to set values to expressions.
* The returned function also has the following properties:
* * `literal` – `{boolean}` – whether the expression's top-level node is a JavaScript
* literal.
* * `constant` – `{boolean}` – whether the expression is made entirely of JavaScript
* constant literals.
* * `assign` – `{?function(context, value)}` – if the expression is assignable, this will be
* set to a function to change its value on the given context.
*
*/
function $ParseProvider() {
Expand Down
68 changes: 68 additions & 0 deletions test/ng/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,74 @@ describe('parser', function() {
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
}));
});

describe('literal', function() {
it('should mark scalar value expressions as literal', inject(function($parse) {
expect($parse('0').literal).toBe(true);
expect($parse('"hello"').literal).toBe(true);
expect($parse('true').literal).toBe(true);
expect($parse('false').literal).toBe(true);
expect($parse('null').literal).toBe(true);
expect($parse('undefined').literal).toBe(true);
}));

it('should mark array expressions as literal', inject(function($parse) {
expect($parse('[]').literal).toBe(true);
expect($parse('[1, 2, 3]').literal).toBe(true);
expect($parse('[1, identifier]').literal).toBe(true);
}));

it('should mark object expressions as literal', inject(function($parse) {
expect($parse('{}').literal).toBe(true);
expect($parse('{x: 1}').literal).toBe(true);
expect($parse('{foo: bar}').literal).toBe(true);
}));

it('should not mark function calls or operator expressions as literal', inject(function($parse) {
expect($parse('1 + 1').literal).toBe(false);
expect($parse('call()').literal).toBe(false);
expect($parse('[].length').literal).toBe(false);
}));
});

describe('constant', function() {
it('should mark scalar value expressions as constant', inject(function($parse) {
expect($parse('12.3').constant).toBe(true);
expect($parse('"string"').constant).toBe(true);
expect($parse('true').constant).toBe(true);
expect($parse('false').constant).toBe(true);
expect($parse('null').constant).toBe(true);
expect($parse('undefined').constant).toBe(true);
}));

it('should mark arrays as constant if they only contain constant elements', inject(function($parse) {
expect($parse('[]').constant).toBe(true);
expect($parse('[1, 2, 3]').constant).toBe(true);
expect($parse('["string", null]').constant).toBe(true);
expect($parse('[[]]').constant).toBe(true);
expect($parse('[1, [2, 3], {4: 5}]').constant).toBe(true);
}));

it('should not mark arrays as constant if they contain any non-constant elements', inject(function($parse) {
expect($parse('[foo]').constant).toBe(false);
expect($parse('[x + 1]').constant).toBe(false);
expect($parse('[bar[0]]').constant).toBe(false);
}));

it('should mark complex expressions involving constant values as constant', inject(function($parse) {
expect($parse('!true').constant).toBe(true);
expect($parse('1 - 1').constant).toBe(true);
expect($parse('"foo" + "bar"').constant).toBe(true);
expect($parse('5 != null').constant).toBe(true);
expect($parse('{standard: 4/3, wide: 16/9}').constant).toBe(true);
}));

it('should not mark any expression involving variables or function calls as constant', inject(function($parse) {
expect($parse('true.toString()').constant).toBe(false);
expect($parse('foo(1, 2, 3)').constant).toBe(false);
expect($parse('"name" + id').constant).toBe(false);
}));
});
});
});
});

0 comments on commit 1ed6385

Please sign in to comment.