3

As a part of my template engine I need to be able to evaluate expressions in JavaScript like: "first || second" in context of some object serving role of global namespace. So properties of the object should be seen as global variables.

So far I came up with this function:

function scopedEval(str, scope) {
   var f = new Function("scope", "with(scope) { return (" + str + "); }");
   return f(scope);  
}

Everything is fine with it and I am able to run it as:

var scope = { first:1, second:2 };
var expr1  = "first || second";
alert( scopedEval(expr1,scope) );

it alerts me 1.

The only problem with variables that were not defined in the scope object. This:

var expr2  = "third || first || second";
alert( scopedEval(expr2,scope) );

generates an error "variable third is not defined". But I would like all unknown variables to be resolved to undefined rather than throwing errors. So "third || first || second" should yield to 1 again.

As for my knowledge such thing is not possible in modern JavaScript but I might miss something so asking. Any ideas?

Here is an example to play with: http://jsfiddle.net/nCCgT/

5
  • 1
    jade does something similar but has an entire compiler build in. Yes it's possible, build a compiler. Commented Jun 14, 2011 at 7:30
  • 3
    Wow: eval and with in one function! Mater! Commented Jun 14, 2011 at 8:07
  • 3
    @Koolinc: It lacks a break <label> (~ goto)... Commented Jun 14, 2011 at 8:20
  • 2
    @Ates: for completeness it should also use document.write to write a <marquee> element to the page, and create a few popups using window.open etc. etc. Sometimes web development is really sisyphean (en.wikipedia.org/wiki/Sisyphus) Commented Jun 14, 2011 at 8:50
  • You CAN do something like this using eval and lexical scoping. I provide a Scope class in my answer, here Commented Nov 9, 2016 at 9:35

3 Answers 3

3

Intercepting access to non-existing properties is non-standard and not possible with most ECMAScript implementations. The Rhino version shipping with the JDK seems to provide this feature, and SpiderMonkey can catch arbitrary method calls, but not general property access.

There's a proxy proposal for ES Harmony, so the need for such a feature has been acknowledged, but right now, you're out of luck.

As a really ugly workaround, you could scan str for (potential) identifiers and add them to scope like this:

var toks = str.match(/[A-Za-z_$][\w$]*/g);
for(var i = 0; i < toks.length; ++i) {
    if(!(toks[i] in scope))
        scope[toks[i]] = undefined;
}
Sign up to request clarification or add additional context in comments.

4 Comments

Probably useful to check if the token is a valid identifier. Something like: (/[a-z_\$]\w+/i).test(toks[i]) . And the \b splits up $foo, so if that matters you'll need to find a workaround for that...
I already upvoted this answer. But, trying to come up with a quick workaround instead of using a full-fledged JS parser is just opening a can of worms. The edge cases that need to be considered will never end. Perhaps the right solution is to bite the bullet and not handle undefined variables/properties gracefully at all. I believe jQuery Templates also fails ungracefully.
@Gijs, Ates: the new regex should be less problematic, but still won't handle unicode escapes...
I've accepted the answer as it in principle allows to achieve the goal. But as you said it is ugly and highly non-effective indeed. eval() combined with the with with regexps on top of that are too much. In my TIScript eval function ( terrainformatica.com/tiscript/Global.whtm ) has signature eval(str[,obj]) that allows to do exactly that - evaluate in obj namespace. And yet TIScript has property undefined(name, val) that allows to catch access to undefined properties.
1

As others have noted, you cannot do this with the current JS implementations, unless you get into at least a basic amount of parsing (for identifiers).

What I'd like to add is: Maybe the right answer is not to do anything and let JS just fail as it fails right now. Any solution you'll attempt to implement will be either too complex and/or slow, or a half-baked implementation that leaves holes. So, it may not be worth the effort and complexity to make your template engine that robust.

You could simply document, "Variable/property references must be valid. Otherwise, you'll get an error." and put the onus on the programmers.

1 Comment

Unfortunately mentioning "Variable/property references must be valid" does not work in my case as in others. Consider the Mustache or Kite templates where you can define {{#one}}..markup..{{/one}}. That can be implemented without eval() at all. And if one does not exist the section is just skipped without error. I am adding there conditional if construct: {{? one && two}}..markup..{{/?}}. Expression one && two should evaluate without throwing exceptions.
0

If I understand your question correctly, this should do what you want:

function scopedEval(str, scope) {
   var toCheck = str.split(' || ');
    for(var i = 0; i < toCheck.length; ++i){
        if(scope[toCheck[i]])
            return scope[toCheck[i]];
    }
}

May need some better checking here and there, but the method works on your fiddle :) http://jsfiddle.net/nCCgT/1/

2 Comments

I believe the || was a generic example and he wanted any reference to undefined variables not throw an error.
first || last is a generic example, unfortunately it can be any valid JS expression there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.