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

Commit

Permalink
feat(parse): add support for local vars in expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
mhevery committed Feb 22, 2012
1 parent c8ee631 commit 761b2ed
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 65 deletions.
139 changes: 74 additions & 65 deletions src/service/parse.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
'use strict';

var OPERATORS = {
'null':function(self){return null;},
'true':function(self){return true;},
'false':function(self){return false;},
'null':function(){return null;},
'true':function(){return true;},
'false':function(){return false;},
undefined:noop,
'+':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
'-':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
'*':function(self, a,b){return a(self)*b(self);},
'/':function(self, a,b){return a(self)/b(self);},
'%':function(self, a,b){return a(self)%b(self);},
'^':function(self, a,b){return a(self)^b(self);},
'+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
'-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
'*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
'=':noop,
'==':function(self, a,b){return a(self)==b(self);},
'!=':function(self, a,b){return a(self)!=b(self);},
'<':function(self, a,b){return a(self)<b(self);},
'>':function(self, a,b){return a(self)>b(self);},
'<=':function(self, a,b){return a(self)<=b(self);},
'>=':function(self, a,b){return a(self)>=b(self);},
'&&':function(self, a,b){return a(self)&&b(self);},
'||':function(self, a,b){return a(self)||b(self);},
'&':function(self, a,b){return a(self)&b(self);},
// '|':function(self, a,b){return a|b;},
'|':function(self, a,b){return b(self)(self, a(self));},
'!':function(self, a){return !a(self);}
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
'>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
'<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
'>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
'&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
// '|':function(self, locals, a,b){return a|b;},
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
'!':function(self, locals, a){return !a(self, locals);}
};
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};

Expand Down Expand Up @@ -146,7 +146,7 @@ function lex(text){
function readIdent() {
var ident = "",
start = index,
fn, lastDot, peekIndex, methodName;
fn, lastDot, peekIndex, methodName, getter;

while (index < text.length) {
var ch = text.charAt(index);
Expand Down Expand Up @@ -179,15 +179,21 @@ function lex(text){
}

fn = OPERATORS[ident];
getter = getterFn(ident);
tokens.push({
index:start,
text:ident,
json: fn,
fn:fn||extend(getterFn(ident), {
assign:function(self, value){
return setter(self, ident, value);
}
})
fn:fn||extend(
function(self, locals) {
return (getter(self, locals));
},
{
assign:function(self, value){
return setter(self, ident, value);
}
}
)
});

if (methodName) {
Expand Down Expand Up @@ -257,22 +263,18 @@ function parser(text, json, $filter){
value,
tokens = lex(text),
assignment = _assignment,
assignable = logicalOR,
functionCall = _functionCall,
fieldAccess = _fieldAccess,
objectIndex = _objectIndex,
filterChain = _filterChain,
functionIdent = _functionIdent;
filterChain = _filterChain
if(json){
// The extra level of aliasing is here, just in case the lexer misses something, so that
// we prevent any accidental execution in JSON.
assignment = logicalOR;
functionCall =
fieldAccess =
objectIndex =
assignable =
filterChain =
functionIdent =
function() { throwError("is not valid json", {text:text, index:0}); };
value = primary();
} else {
Expand Down Expand Up @@ -328,14 +330,14 @@ function parser(text, json, $filter){
}

function unaryFn(fn, right) {
return function(self) {
return fn(self, right);
return function(self, locals) {
return fn(self, locals, right);
};
}

function binaryFn(left, fn, right) {
return function(self) {
return fn(self, left, right);
return function(self, locals) {
return fn(self, locals, left, right);
};
}

Expand All @@ -353,12 +355,12 @@ function parser(text, json, $filter){
// TODO(size): maybe we should not support multiple statements?
return statements.length == 1
? statements[0]
: function(self){
: function(self, locals){
var value;
for ( var i = 0; i < statements.length; i++) {
var statement = statements[i];
if (statement)
value = statement(self);
value = statement(self, locals);
}
return value;
};
Expand Down Expand Up @@ -386,10 +388,10 @@ function parser(text, json, $filter){
if ((token = expect(':'))) {
argsFn.push(expression());
} else {
var fnInvoke = function(self, input){
var fnInvoke = function(self, locals, input){
var args = [input];
for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
args.push(argsFn[i](self, locals));
}
return fn.apply(self, args);
};
Expand All @@ -414,8 +416,8 @@ function parser(text, json, $filter){
text.substring(0, token.index) + "] can not be assigned to", token);
}
right = logicalOR();
return function(self){
return left.assign(self, right(self));
return function(self, locals){
return left.assign(self, right(self, locals), locals);
};
} else {
return left;
Expand Down Expand Up @@ -546,22 +548,25 @@ function parser(text, json, $filter){
function _fieldAccess(object) {
var field = expect().text;
var getter = getterFn(field);
return extend(function(self){
return getter(object(self));
}, {
assign:function(self, value){
return setter(object(self), field, value);
}
});
return extend(
function(self, locals) {
return getter(object(self, locals), locals);
},
{
assign:function(self, value, locals) {
return setter(object(self, locals), field, value);
}
}
);
}

function _objectIndex(obj) {
var indexFn = expression();
consume(']');
return extend(
function(self){
var o = obj(self),
i = indexFn(self),
function(self, locals){
var o = obj(self, locals),
i = indexFn(self, locals),
v, p;

if (!o) return undefined;
Expand All @@ -576,8 +581,8 @@ function parser(text, json, $filter){
}
return v;
}, {
assign:function(self, value){
return obj(self)[indexFn(self)] = value;
assign:function(self, value, locals){
return obj(self, locals)[indexFn(self, locals)] = value;
}
});
}
Expand All @@ -590,14 +595,14 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume(')');
return function(self){
return function(self, locals){
var args = [],
context = contextGetter ? contextGetter(self) : self;
context = contextGetter ? contextGetter(self, locals) : self;

for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self));
args.push(argsFn[i](self, locals));
}
var fnPtr = fn(self) || noop;
var fnPtr = fn(self, locals) || noop;
// IE stupidity!
return fnPtr.apply
? fnPtr.apply(context, args)
Expand All @@ -614,10 +619,10 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume(']');
return function(self){
return function(self, locals){
var array = [];
for ( var i = 0; i < elementFns.length; i++) {
array.push(elementFns[i](self));
array.push(elementFns[i](self, locals));
}
return array;
};
Expand All @@ -635,11 +640,11 @@ function parser(text, json, $filter){
} while (expect(','));
}
consume('}');
return function(self){
return function(self, locals){
var object = {};
for ( var i = 0; i < keyValues.length; i++) {
var keyValue = keyValues[i];
var value = keyValue.value(self);
var value = keyValue.value(self, locals);
object[keyValue.key] = value;
}
return object;
Expand Down Expand Up @@ -700,10 +705,14 @@ function getterFn(path) {
if (fn) return fn;

var code = 'var l, fn, p;\n';
forEach(path.split('.'), function(key) {
forEach(path.split('.'), function(key, index) {
code += 'if(!s) return s;\n' +
'l=s;\n' +
's=s' + '["' + key + '"]' + ';\n' +
's='+ (index
// we simply direference 's' on any .dot notation
? 's'
// but if we are first then we check locals firs, and if so read it first
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
'if (s && s.then) {\n' +
' if (!("$$v" in s)) {\n' +
' p=s;\n' +
Expand All @@ -714,7 +723,7 @@ function getterFn(path) {
'}\n';
});
code += 'return s;';
fn = Function('s', code);
fn = Function('s', 'k', code);
fn.toString = function() { return code; };

return getterFnCache[path] = fn;
Expand Down
14 changes: 14 additions & 0 deletions test/service/parseSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,4 +603,18 @@ describe('parser', function() {
expect(scope).toEqual({a:123});
}));
});


describe('locals', function() {
it('should expose local variables', inject(function($parse) {
expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
}));

it('should expose traverse locals', inject(function($parse) {
expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
}));
});
});

0 comments on commit 761b2ed

Please sign in to comment.