Skip to content

Commit

Permalink
'and' and 'or' are control structures, can't do depth-first recursion
Browse files Browse the repository at this point in the history
Now that the user can introduce side-effects, it's important that
'and' stops at the first falsy
'or' stops at the first truthy
  • Loading branch information
jwadhams committed Oct 28, 2016
1 parent 9f6f776 commit 25f7486
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 19 deletions.
39 changes: 20 additions & 19 deletions logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,22 +51,6 @@ var jsonLogic = {},
"!!" : function(a){ return jsonLogic.truthy(a); },
"!" : function(a){ return !jsonLogic.truthy(a); },
"%" : function(a,b){ return a % b; },
"and" : function(){ //Return first falsy, or last
for(var i=0 ; i < arguments.length ; i+=1){
if( ! jsonLogic.truthy(arguments[i])){
return arguments[i];
}
}
return arguments[i-1];
},
"or" : function(){ //Return first truthy, or last
for(var i=0 ; i < arguments.length ; i+=1){
if( jsonLogic.truthy(arguments[i])){
return arguments[i];
}
}
return arguments[i-1];
},
"log" : function(a){ console.log(a); return a; },
"in" : function(a, b){
if(typeof b.indexOf === 'undefined') return false;
Expand Down Expand Up @@ -173,12 +157,12 @@ jsonLogic.apply = function(logic, data){

var op = Object.keys(logic)[0],
values = logic[op],
i;
i, current;

//easy syntax for unary operators, like {"var" : "x"} instead of strict {"var" : ["x"]}
if( ! Array.isArray(values)){ values = [values]; }

// 'if' violates the normal rule of depth-first calculating consequents, let it manage recursion
// 'if', 'and', and 'or' violate the normal rule of depth-first calculating consequents, let each manage recursion as needed.
if(op === 'if' || op == '?:'){
/* 'if' should be called with a odd number of parameters, 3 or greater
This works on the pattern:
Expand All @@ -200,7 +184,24 @@ jsonLogic.apply = function(logic, data){
}
if(values.length === i+1) return jsonLogic.apply(values[i], data);
return null;
}
}else if(op === "and"){ //Return first falsy, or last
for(i=0 ; i < values.length ; i+=1){
current = jsonLogic.apply(values[i], data);
if( ! jsonLogic.truthy(current)){
return current;
}
}
return current; //Last
}else if(op === "or"){//Return first truthy, or last
for(i=0 ; i < values.length ; i+=1){
current = jsonLogic.apply(values[i], data);
if( jsonLogic.truthy(current) ){
return current;
}
}
return current; //Last
}



// Everyone else gets immediate depth-first recursion
Expand Down
80 changes: 80 additions & 0 deletions tests/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,83 @@ QUnit.test( "Expanding functionality with method", function( assert) {
assert.equal(a.count, 42); //Happy state change

});


QUnit.test("Control structures don't use depth-first computation", function(assert){
//Depth-first recursion was wasteful but not harmful until we added custom operations that could have side-effects.

//If operations run the condition, if truthy, it runs and returns that consequent.
//Consequents of falsy conditions should not run.
//After one truthy condition, no other condition should run
var conditions = [];
var consequents = [];
jsonLogic.add_operation("push.if", function(v){ conditions.push(v); return v; });
jsonLogic.add_operation("push.then", function(v){ consequents.push(v); return v; });
jsonLogic.add_operation("push.else", function(v){ consequents.push(v); return v; });

jsonLogic.apply({"if":[
{"push.if" : [true] },
{"push.then":["first"]},
{"push.if" : [false]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [true]);
assert.deepEqual(consequents, ["first"]);

conditions = [];
consequents = [];
jsonLogic.apply({"if":[
{"push.if" : [false] },
{"push.then":["first"]},
{"push.if" : [true]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [false,true]);
assert.deepEqual(consequents, ["second"]);

conditions = [];
consequents = [];
jsonLogic.apply({"if":[
{"push.if" : [false] },
{"push.then":["first"]},
{"push.if" : [false]},
{"push.then":["second"]},
{"push.else":["third"]}
]});
assert.deepEqual(conditions, [false,false]);
assert.deepEqual(consequents, ["third"]);


jsonLogic.add_operation("push", function(arg){ i.push(arg); return arg; });
var i = [];

i = [];
jsonLogic.apply({"and":[ {"push":[false]}, {"push":[false]} ]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and":[ {"push":[false]}, {"push":[true]} ]});
assert.deepEqual(i, [false]);
i = [];
jsonLogic.apply({"and":[ {"push":[true]}, {"push":[false]} ]});
assert.deepEqual(i, [true, false]);
i = [];
jsonLogic.apply({"and":[ {"push":[true]}, {"push":[true]} ]});
assert.deepEqual(i, [true, true]);


i = [];
jsonLogic.apply({"or":[ {"push":[false]}, {"push":[false]} ]});
assert.deepEqual(i, [false,false]);
i = [];
jsonLogic.apply({"or":[ {"push":[false]}, {"push":[true]} ]});
assert.deepEqual(i, [false,true]);
i = [];
jsonLogic.apply({"or":[ {"push":[true]}, {"push":[false]} ]});
assert.deepEqual(i, [true]);
i = [];
jsonLogic.apply({"or":[ {"push":[true]}, {"push":[true]} ]});
assert.deepEqual(i, [true]);

});

0 comments on commit 25f7486

Please sign in to comment.