Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow negative logic for the is operator. #212

Merged
merged 2 commits into from Nov 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 59 additions & 4 deletions src/formula.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3200,8 +3200,11 @@ namespace {

class IsExpression : public FormulaExpression {
public:
IsExpression(variant_type_ptr type, ExpressionPtr expr)
: FormulaExpression("_is"), type_(type), expression_(expr)
IsExpression(
variant_type_ptr type, ExpressionPtr expr,
bool negative = false)
: FormulaExpression("_is"), type_(type)
, expression_(expr), negative_(negative)
{
}

Expand All @@ -3212,7 +3215,9 @@ namespace {

variant execute(const FormulaCallable& variables) const override {
const variant value = expression_->evaluate(variables);
return variant::from_bool(type_->match(value));
bool matching = type_->match(value);
return variant::from_bool(
negative_ ? !matching : matching);
}

std::vector<ConstExpressionPtr> getChildren() const override {
Expand All @@ -3227,7 +3232,7 @@ namespace {
formula_vm::VirtualMachine vm;
expression_->emitVM(vm);
vm.addLoadConstantInstruction(variant(type_.get()));
vm.addInstruction(OP_IS);
vm.addInstruction(negative_ ? OP_IS_NOT : OP_IS);
return ExpressionPtr(new VMExpression(vm, queryVariantType(), *this));
}
return ExpressionPtr();
Expand All @@ -3243,6 +3248,7 @@ namespace {

variant_type_ptr type_;
ExpressionPtr expression_;
bool negative_;
};

class StaticTypeExpression : public FormulaExpression {
Expand Down Expand Up @@ -4773,6 +4779,11 @@ static std::string debugSubexpressionTypes(ConstFormulaPtr & fml)
int consume_backwards = 0;
std::string op_name(op->begin,op->end);

if (op_name == "is" && op + 1 > i1 && op + 1 < i2 &&
std::string((op + 1)->begin, (op + 1)->end) == "not") {
op_name = "is not";
}

if(op_name == "in" && op > i1 && op-1 > i1 && std::string((op-1)->begin, (op-1)->end) == "not") {
op_name = "not in";
consume_backwards = 1;
Expand All @@ -4791,6 +4802,19 @@ static std::string debugSubexpressionTypes(ConstFormulaPtr & fml)
}
}

if (op_name == "is not") {
const Token* type_tok = op + 2;
variant_type_ptr type = parse_variant_type(
formula_str, type_tok, i2);
ASSERT_LOG(type_tok == i2, "Unexpected tokens after type: " << pinpoint_location(formula_str, type_tok->begin, (i2-1)->end));

ExpressionPtr left(parse_expression(
formula_str, i1, op, symbols,
callable_def, can_optimize));
return ExpressionPtr(new IsExpression(
type, left, true));
}

if(op_name == "is") {
const Token* type_tok = op+1;
variant_type_ptr type = parse_variant_type(formula_str, type_tok, i2);
Expand Down Expand Up @@ -5383,6 +5407,37 @@ UNIT_TEST(formula_in) {
CHECK(Formula(variant("8 not in [4,5,6]")).execute() == variant::from_bool(true), "test failed");
}

// 'is [not] null'.
UNIT_TEST(formula_is) {
CHECK(Formula(variant("a is null where a = null")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is int where a = null")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is list where a = null")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is null where a = 0")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is int where a = 0")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is list where a = 0")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is null where a = [0]")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is int where a = [0]")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is list where a = [0]")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is null where a = null")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("not a is int where a = null")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is list where a = null")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is null where a = 0")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is int where a = 0")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("not a is list where a = 0")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is null where a = [0]")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is int where a = [0]")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("not a is list where a = [0]")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is not null where a = null")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is not int where a = null")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not list where a = null")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not null where a = 0")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not int where a = 0")).execute() == variant::from_bool(false), "test failed");
CHECK(Formula(variant("a is not list where a = 0")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not null where a = [0]")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not int where a = [0]")).execute() == variant::from_bool(true), "test failed");
CHECK(Formula(variant("a is not list where a = [0]")).execute() == variant::from_bool(false), "test failed");
}

UNIT_TEST(formula_fn) {
FunctionSymbolTable symbols;
CHECK(Formula(variant("def f(g) g(5) + 1; def fn(n) n*n; f(fn)"), &symbols).execute() == variant(26), "test failed");
Expand Down
9 changes: 8 additions & 1 deletion src/formula_vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,18 @@ void VirtualMachine::executeInternal(const FormulaCallable& variables, std::vect
break;
}

case OP_IS_NOT:
case OP_IS: {
variant& left = stack[stack.size()-2];
variant& right = stack[stack.size()-1];

variant_type_ptr t(right.convert_to<variant_type>());
left = variant::from_bool(t->match(left));
if (*p == OP_IS) {
left = variant::from_bool(t->match(left));
} else {
assert(*p == OP_IS_NOT);
left = variant::from_bool(!t->match(left));
}
stack.pop_back();
break;
}
Expand Down Expand Up @@ -1154,6 +1160,7 @@ const char* getOpName(VirtualMachine::InstructionType op) {

switch(op) {
DEF_OP(OP_IN) DEF_OP(OP_NOT_IN) DEF_OP(OP_AND) DEF_OP(OP_OR) DEF_OP(OP_NEQ) DEF_OP(OP_LTE) DEF_OP(OP_GTE) DEF_OP(OP_IS)
DEF_OP(OP_IS_NOT)

DEF_OP(OP_UNARY_NOT) DEF_OP(OP_UNARY_SUB) DEF_OP(OP_UNARY_STR) DEF_OP(OP_UNARY_NUM_ELEMENTS)

Expand Down
8 changes: 7 additions & 1 deletion src/formula_vm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ enum OP {
// PUSH: 1
// ARGS: NONE
OP_IN, OP_NOT_IN, OP_AND, OP_OR, OP_NEQ, OP_LTE, OP_GTE, OP_IS,
OP_IS_NOT,

//Unary operations which operate on the top item on
//the stack replacing it with the result
Expand Down Expand Up @@ -179,7 +180,12 @@ enum OP {
// POP: 1
// PUSH: 0
// ARGS: NONE
OP_POP,
// Previously at position 36, now listed at position 37.
// Because enum ordinal 37 is already taken explicity by OP_MOD
// (because of '%' being the character number 37), this is
// getting an explicit value too, in order to avoid collisions
// in C++ `switch` constructions.
OP_POP = 38,

//Binary operators
// POP: 2
Expand Down