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

Commit 761b2ed

Browse files
committed
feat(parse): add support for local vars in expressions
1 parent c8ee631 commit 761b2ed

File tree

2 files changed

+88
-65
lines changed

2 files changed

+88
-65
lines changed

src/service/parse.js

+74-65
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
'use strict';
22

33
var OPERATORS = {
4-
'null':function(self){return null;},
5-
'true':function(self){return true;},
6-
'false':function(self){return false;},
4+
'null':function(){return null;},
5+
'true':function(){return true;},
6+
'false':function(){return false;},
77
undefined:noop,
8-
'+':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
9-
'-':function(self, a,b){a=a(self); b=b(self); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
10-
'*':function(self, a,b){return a(self)*b(self);},
11-
'/':function(self, a,b){return a(self)/b(self);},
12-
'%':function(self, a,b){return a(self)%b(self);},
13-
'^':function(self, a,b){return a(self)^b(self);},
8+
'+':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
9+
'-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
10+
'*':function(self, locals, a,b){return a(self, locals)*b(self, locals);},
11+
'/':function(self, locals, a,b){return a(self, locals)/b(self, locals);},
12+
'%':function(self, locals, a,b){return a(self, locals)%b(self, locals);},
13+
'^':function(self, locals, a,b){return a(self, locals)^b(self, locals);},
1414
'=':noop,
15-
'==':function(self, a,b){return a(self)==b(self);},
16-
'!=':function(self, a,b){return a(self)!=b(self);},
17-
'<':function(self, a,b){return a(self)<b(self);},
18-
'>':function(self, a,b){return a(self)>b(self);},
19-
'<=':function(self, a,b){return a(self)<=b(self);},
20-
'>=':function(self, a,b){return a(self)>=b(self);},
21-
'&&':function(self, a,b){return a(self)&&b(self);},
22-
'||':function(self, a,b){return a(self)||b(self);},
23-
'&':function(self, a,b){return a(self)&b(self);},
24-
// '|':function(self, a,b){return a|b;},
25-
'|':function(self, a,b){return b(self)(self, a(self));},
26-
'!':function(self, a){return !a(self);}
15+
'==':function(self, locals, a,b){return a(self, locals)==b(self, locals);},
16+
'!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);},
17+
'<':function(self, locals, a,b){return a(self, locals)<b(self, locals);},
18+
'>':function(self, locals, a,b){return a(self, locals)>b(self, locals);},
19+
'<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);},
20+
'>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);},
21+
'&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);},
22+
'||':function(self, locals, a,b){return a(self, locals)||b(self, locals);},
23+
'&':function(self, locals, a,b){return a(self, locals)&b(self, locals);},
24+
// '|':function(self, locals, a,b){return a|b;},
25+
'|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));},
26+
'!':function(self, locals, a){return !a(self, locals);}
2727
};
2828
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
2929

@@ -146,7 +146,7 @@ function lex(text){
146146
function readIdent() {
147147
var ident = "",
148148
start = index,
149-
fn, lastDot, peekIndex, methodName;
149+
fn, lastDot, peekIndex, methodName, getter;
150150

151151
while (index < text.length) {
152152
var ch = text.charAt(index);
@@ -179,15 +179,21 @@ function lex(text){
179179
}
180180

181181
fn = OPERATORS[ident];
182+
getter = getterFn(ident);
182183
tokens.push({
183184
index:start,
184185
text:ident,
185186
json: fn,
186-
fn:fn||extend(getterFn(ident), {
187-
assign:function(self, value){
188-
return setter(self, ident, value);
189-
}
190-
})
187+
fn:fn||extend(
188+
function(self, locals) {
189+
return (getter(self, locals));
190+
},
191+
{
192+
assign:function(self, value){
193+
return setter(self, ident, value);
194+
}
195+
}
196+
)
191197
});
192198

193199
if (methodName) {
@@ -257,22 +263,18 @@ function parser(text, json, $filter){
257263
value,
258264
tokens = lex(text),
259265
assignment = _assignment,
260-
assignable = logicalOR,
261266
functionCall = _functionCall,
262267
fieldAccess = _fieldAccess,
263268
objectIndex = _objectIndex,
264-
filterChain = _filterChain,
265-
functionIdent = _functionIdent;
269+
filterChain = _filterChain
266270
if(json){
267271
// The extra level of aliasing is here, just in case the lexer misses something, so that
268272
// we prevent any accidental execution in JSON.
269273
assignment = logicalOR;
270274
functionCall =
271275
fieldAccess =
272276
objectIndex =
273-
assignable =
274277
filterChain =
275-
functionIdent =
276278
function() { throwError("is not valid json", {text:text, index:0}); };
277279
value = primary();
278280
} else {
@@ -328,14 +330,14 @@ function parser(text, json, $filter){
328330
}
329331

330332
function unaryFn(fn, right) {
331-
return function(self) {
332-
return fn(self, right);
333+
return function(self, locals) {
334+
return fn(self, locals, right);
333335
};
334336
}
335337

336338
function binaryFn(left, fn, right) {
337-
return function(self) {
338-
return fn(self, left, right);
339+
return function(self, locals) {
340+
return fn(self, locals, left, right);
339341
};
340342
}
341343

@@ -353,12 +355,12 @@ function parser(text, json, $filter){
353355
// TODO(size): maybe we should not support multiple statements?
354356
return statements.length == 1
355357
? statements[0]
356-
: function(self){
358+
: function(self, locals){
357359
var value;
358360
for ( var i = 0; i < statements.length; i++) {
359361
var statement = statements[i];
360362
if (statement)
361-
value = statement(self);
363+
value = statement(self, locals);
362364
}
363365
return value;
364366
};
@@ -386,10 +388,10 @@ function parser(text, json, $filter){
386388
if ((token = expect(':'))) {
387389
argsFn.push(expression());
388390
} else {
389-
var fnInvoke = function(self, input){
391+
var fnInvoke = function(self, locals, input){
390392
var args = [input];
391393
for ( var i = 0; i < argsFn.length; i++) {
392-
args.push(argsFn[i](self));
394+
args.push(argsFn[i](self, locals));
393395
}
394396
return fn.apply(self, args);
395397
};
@@ -414,8 +416,8 @@ function parser(text, json, $filter){
414416
text.substring(0, token.index) + "] can not be assigned to", token);
415417
}
416418
right = logicalOR();
417-
return function(self){
418-
return left.assign(self, right(self));
419+
return function(self, locals){
420+
return left.assign(self, right(self, locals), locals);
419421
};
420422
} else {
421423
return left;
@@ -546,22 +548,25 @@ function parser(text, json, $filter){
546548
function _fieldAccess(object) {
547549
var field = expect().text;
548550
var getter = getterFn(field);
549-
return extend(function(self){
550-
return getter(object(self));
551-
}, {
552-
assign:function(self, value){
553-
return setter(object(self), field, value);
554-
}
555-
});
551+
return extend(
552+
function(self, locals) {
553+
return getter(object(self, locals), locals);
554+
},
555+
{
556+
assign:function(self, value, locals) {
557+
return setter(object(self, locals), field, value);
558+
}
559+
}
560+
);
556561
}
557562

558563
function _objectIndex(obj) {
559564
var indexFn = expression();
560565
consume(']');
561566
return extend(
562-
function(self){
563-
var o = obj(self),
564-
i = indexFn(self),
567+
function(self, locals){
568+
var o = obj(self, locals),
569+
i = indexFn(self, locals),
565570
v, p;
566571

567572
if (!o) return undefined;
@@ -576,8 +581,8 @@ function parser(text, json, $filter){
576581
}
577582
return v;
578583
}, {
579-
assign:function(self, value){
580-
return obj(self)[indexFn(self)] = value;
584+
assign:function(self, value, locals){
585+
return obj(self, locals)[indexFn(self, locals)] = value;
581586
}
582587
});
583588
}
@@ -590,14 +595,14 @@ function parser(text, json, $filter){
590595
} while (expect(','));
591596
}
592597
consume(')');
593-
return function(self){
598+
return function(self, locals){
594599
var args = [],
595-
context = contextGetter ? contextGetter(self) : self;
600+
context = contextGetter ? contextGetter(self, locals) : self;
596601

597602
for ( var i = 0; i < argsFn.length; i++) {
598-
args.push(argsFn[i](self));
603+
args.push(argsFn[i](self, locals));
599604
}
600-
var fnPtr = fn(self) || noop;
605+
var fnPtr = fn(self, locals) || noop;
601606
// IE stupidity!
602607
return fnPtr.apply
603608
? fnPtr.apply(context, args)
@@ -614,10 +619,10 @@ function parser(text, json, $filter){
614619
} while (expect(','));
615620
}
616621
consume(']');
617-
return function(self){
622+
return function(self, locals){
618623
var array = [];
619624
for ( var i = 0; i < elementFns.length; i++) {
620-
array.push(elementFns[i](self));
625+
array.push(elementFns[i](self, locals));
621626
}
622627
return array;
623628
};
@@ -635,11 +640,11 @@ function parser(text, json, $filter){
635640
} while (expect(','));
636641
}
637642
consume('}');
638-
return function(self){
643+
return function(self, locals){
639644
var object = {};
640645
for ( var i = 0; i < keyValues.length; i++) {
641646
var keyValue = keyValues[i];
642-
var value = keyValue.value(self);
647+
var value = keyValue.value(self, locals);
643648
object[keyValue.key] = value;
644649
}
645650
return object;
@@ -700,10 +705,14 @@ function getterFn(path) {
700705
if (fn) return fn;
701706

702707
var code = 'var l, fn, p;\n';
703-
forEach(path.split('.'), function(key) {
708+
forEach(path.split('.'), function(key, index) {
704709
code += 'if(!s) return s;\n' +
705710
'l=s;\n' +
706-
's=s' + '["' + key + '"]' + ';\n' +
711+
's='+ (index
712+
// we simply direference 's' on any .dot notation
713+
? 's'
714+
// but if we are first then we check locals firs, and if so read it first
715+
: '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' +
707716
'if (s && s.then) {\n' +
708717
' if (!("$$v" in s)) {\n' +
709718
' p=s;\n' +
@@ -714,7 +723,7 @@ function getterFn(path) {
714723
'}\n';
715724
});
716725
code += 'return s;';
717-
fn = Function('s', code);
726+
fn = Function('s', 'k', code);
718727
fn.toString = function() { return code; };
719728

720729
return getterFnCache[path] = fn;

test/service/parseSpec.js

+14
Original file line numberDiff line numberDiff line change
@@ -603,4 +603,18 @@ describe('parser', function() {
603603
expect(scope).toEqual({a:123});
604604
}));
605605
});
606+
607+
608+
describe('locals', function() {
609+
it('should expose local variables', inject(function($parse) {
610+
expect($parse('a')({a: 0}, {a: 1})).toEqual(1);
611+
expect($parse('add(a,b)')({b: 1, add: function(a, b) { return a + b; }}, {a: 2})).toEqual(3);
612+
}));
613+
614+
it('should expose traverse locals', inject(function($parse) {
615+
expect($parse('a.b')({a: {b: 0}}, {a: {b:1}})).toEqual(1);
616+
expect($parse('a.b')({a: null}, {a: {b:1}})).toEqual(1);
617+
expect($parse('a.b')({a: {b: 0}}, {a: null})).toEqual(undefined);
618+
}));
619+
});
606620
});

0 commit comments

Comments
 (0)