0

I have a function that allows strings to be evaluated against an environment

function filt(M, filterString, cb){
    var res
    try{
        res = eval(filterString)
    } catch(me){
        // If in doubt permit               
        res = true
    }
    cb(res)
}

The problem is that I would like this filter to be optimistic. You can see the optimism in the catch, where the response is set to true if anything goes wrong.

If I run filt({ age : 20 }, 'M.age < 21', callback) then my callback returns true. Similarly filt({ age: 21}, 'M.age < 21', callback) returns false as expected.

I would however like the following to return true also: filt({}, 'M.age < 21', callback). However undefined==true is false (similar for all boolean expressions with undefined). I thought about inspecting the string using regular expression (all my variables are passed as fields on the object M). So that if filterString contains M.varname (or M."varname" or M["varname"] then replace with true if there is no such field on M. However prior to coding this I thought I would put the feelers out to see if there is a better approach.

Any ideas? Thanks in advance.

Update:

Thanks to the feedback I can see that my entire approach needs rethinking. To simplify then I am wondering if there is a a method of evaluating expressions with unknown values so that if an expression involves and unknown then the outcome is unknown. For example in pseudocode:

M > 21 = unknown if M == unknown
M > 21 = true if M == 23
M > 21 = false if M == 'fish'
M > 21 | true = true independent of the value of M.
Joe
  • 1,455
  • 2
  • 19
  • 36
  • Could you pass 2 arguments in place of `filterString` as `field` (eg `age`) and `comparison` (eg. `"< 21"`)? – Jamiec Dec 07 '15 at 11:04
  • 2
    What are you using this for? I'm sure there's a better alternative than `eval` that doesn't need a `try/catch`. – Cerbrus Dec 07 '15 at 11:06
  • Not really, I have greatly simplified the function for the sake of a minimal example. The object `M` is in reality going to have a lot of fields, possibly nested. – Joe Dec 07 '15 at 11:06
  • Ok, but the `field` could be `"outerObject.innerObject.someProperty"` and the `comprison` still `"<21"` - the point being if you can separate the "what field" from the "what boolean check` then you can first verify the propery (or nested property) is valid or not before doing the boolean check. Essentially - what im saying is that your title is a quintessential XY Problem. You dont want a hack to make `undefined == true` you want a better solution to your actual problem. – Jamiec Dec 07 '15 at 11:08
  • @Cerbrus I have a directed graph that as it is walked along gathers information that is stored on an object `M`. The nodes of the graph can be traversed if the information does not forbid it according to simple filter expressions. These filter strings need to be able to contain any other javascript `tolower`, `Math.random()` etc. What could I try instead of eval? – Joe Dec 07 '15 at 11:10
  • 2
    You are looking for ES6 Proxies. They are not very cross-browser, and node doesn't support them either. If your environment is Firefox (Spidermonkey) or Edge (Chakra), then it will work. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy – user3459110 Dec 07 '15 at 11:10
  • @Awal_Garg. Thanks for that - very interesting. Do you know of any libs for node that implement this? – Joe Dec 07 '15 at 11:12
  • @Joe It is an internally exposed construction, so libs can't help you here. It is *not possible* for a lib to implement proxies. – user3459110 Dec 07 '15 at 11:13
  • @Jamiec, but what if I have multiple comparisons. For example `filterString = '(M.age < 21 | !M.taken.atest ) & M.height < 3.1 | Math.random() < .5)'` – Joe Dec 07 '15 at 11:14
  • @Joe Yes. Node implements an old style Proxy that can be enabled with command line flag and [shimmed](https://github.com/Swatinem/proxy) to new standard Proxy. – Sheepy Dec 07 '15 at 11:14
  • @Sheepy, thanks - feel like I am stepping into deep water. – Joe Dec 07 '15 at 11:15
  • Why the downvote for the question? Is it off-topic or ill-defined somehow? – Joe Dec 07 '15 at 11:18
  • @Joe I can't say for others, but I am *tempted* to downvote for you are showing some very horrible code and asking for an even more horrible solution, on par with [parsing HTML with regx](http://stackoverflow.com/a/1732454/893578) (if such horrors are comparable). – Sheepy Dec 07 '15 at 11:21
  • @Sheepy. Hmm fair enough I suppose, so a to steer others clear if this sort of problem is a minefield. Enabling harmony-proxies seems like using a sledgehammer to crack a nut so I think I am going to roll an expression parser. Never tried with JS only ANTLR in Java. I guess I was optimistic that I could just hack javascript to provide my DSL for filters. Thanks for your help. – Joe Dec 07 '15 at 11:34
  • I still don't see why you would use `eval` instead of simple functions. If you need multiple comparisons, just use multiple functions (a tree of simple functions). True, JS is still a bit chatty when using anonymous functions (unless you're running with ES6 and arrow functions), but it's quite simple to use. – Luaan Dec 07 '15 at 12:09
  • @Luann The reason I am using eval is because the expression is stored as a string in a database. A string is my starting point. – Joe Dec 07 '15 at 12:13

1 Answers1

0

Partial solution: Does not answer the question

I post here a partial solution that is overly optimistic. This covers my unit tests but it is horrendous and probably dangerous. Please read the comments by other user above before even thinking about doing anything like this.

function(M, filterString, cb){

    var regexp = /M(\."\w+"|\.'\w+'|\.\w+|\["\w+"\]|\['\w+'\])+/g
    /*
        Matches all of the following 
        M."a"
        M.'a'
        M.a["b"].c
        M["a"].b['c']
        M.a.b
    */

    var expectedVariables = filterString.match(regexp)

    // Check that the variables in the filterString are available
    var allMetaDataAvailable = true
    for(var i=0 ; i< expectedVariables.length; i++){
        if (typeof eval(expectedVariables[i]) =='undefined'){
            allMetaDataAvailable = false
        }
    }

    if (!allMetaDataAvailable){
         // Optimistic
         cb(true)
    } else {
         // Evalue the expression
         var res
         try{
              res = eval(filterString)
         } catch(me){
              // something went wrong - allow.
              res = true
         }
         cb(res)

     }
}

The reason that this is only a partial solution is that a string expression M.age < 20 | M.gender=='f' will return true if M = { gender : 'm' }, because it does not know the value of age then it optimistically says that the expression may (in future - once the value is defined) become true - which in this case is clearly not possible because we already know that the value of gender is wrong.

Update: I have now discovered a very lightweight library called JSEP.js for creating the abstract syntax tree of a javascript expression. This would seem an ideal starting point within which to implement a javascript based ternary logic capable of handling unknown values in boolean expressions correctly.

Joe
  • 1,455
  • 2
  • 19
  • 36