Commit 6d5474aa authored by twanvl's avatar twanvl

Short-circuiting "and" and "or" operators

parent b1c5c006
...@@ -56,6 +56,11 @@ In a table: ...@@ -56,6 +56,11 @@ In a table:
| @true@ @false@ @true@ @false@ @true@ | @true@ @false@ @true@ @false@ @true@
| @true@ @true@ @true@ @true@ @false@ | @true@ @true@ @true@ @true@ @false@
The @and@ and @or@ operators use [[http://en.wikipedia.org/wiki/Short-circuit_evaluation|short-circuit evaluation]], which means that the second argument is only evaluated if the first argument does not suffice to determine the value of the expression.
For example
> true or card.field_that_does_not_exist
evaluates to @true@ instead of giving an error.
--Grouping and order-- --Grouping and order--
Operators are ordered as usual, so Operators are ordered as usual, so
......
...@@ -74,6 +74,25 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { ...@@ -74,6 +74,25 @@ ScriptValueP Context::eval(const Script& script, bool useScope) {
} }
break; break;
} }
// Short-circuiting and/or = conditional jump without pop
case I_JUMP_SC_AND: {
bool condition = *stack.back();
if (!condition) {
instr = &script.instructions[0] + i.data;
} else {
stack.pop_back();
}
break;
}
case I_JUMP_SC_OR: {
bool condition = *stack.back();
if (condition) {
instr = &script.instructions[0] + i.data;
} else {
stack.pop_back();
}
break;
}
// Get a variable // Get a variable
case I_GET_VAR: { case I_GET_VAR: {
......
...@@ -207,8 +207,10 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script) ...@@ -207,8 +207,10 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script)
break; break;
} }
// Conditional jump // Conditional jump
case I_JUMP_IF_NOT: { case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR: {
stack.pop_back(); // condition if (i.instr == I_JUMP_IF_NOT) {
stack.pop_back(); // pop condition
}
// create jump record // create jump record
Jump* jump = new Jump; Jump* jump = new Jump;
jump->target = &script.instructions[i.data]; jump->target = &script.instructions[i.data];
...@@ -217,6 +219,9 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script) ...@@ -217,6 +219,9 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script)
getBindings(scope, jump->bindings); getBindings(scope, jump->bindings);
jumps.push(jump); jumps.push(jump);
// just fall through for the case that the condition holds // just fall through for the case that the condition holds
if (i.instr != I_JUMP_IF_NOT) {
stack.pop_back(); // pop condition afterwards, so it is not poped when jump is taken
}
break; break;
} }
...@@ -231,9 +236,11 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script) ...@@ -231,9 +236,11 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script)
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
ScriptValueP val = it->next(); ScriptValueP val = it->next();
if (val) { if (val) {
// we have not been through the body
it = dependency_dummy; // invalidate iterator, so we loop only once it = dependency_dummy; // invalidate iterator, so we loop only once
stack.push_back(val); stack.push_back(val);
} else { } else {
// we have been through the body once already
stack.erase(stack.end() - 2); // remove iterator stack.erase(stack.end() - 2); // remove iterator
instr = &script.instructions[i.data]; instr = &script.instructions[i.data];
} }
......
...@@ -663,15 +663,29 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc ...@@ -663,15 +663,29 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc
parseOper(input, script, PREC_SET, I_SET_VAR, instr.data); parseOper(input, script, PREC_SET, I_SET_VAR, instr.data);
} }
else if (minPrec <= PREC_AND && token==_("orelse"))parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE); else if (minPrec <= PREC_AND && token==_("orelse"))parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE);
else if (minPrec <= PREC_AND && token==_("and")) parseOper(input, script, PREC_CMP, I_BINARY, I_AND); else if (minPrec <= PREC_AND && token==_("and")) {
// short-circuiting and:
// "XXX and YYY"
// becomes
// XXX
// I_JUMP_SC_AND after # if top==false then goto after else pop
// YYY
// after:
unsigned jmpSC = script.addInstruction(I_JUMP_SC_AND);
parseOper(input, script, PREC_CMP);
script.comeFrom(jmpSC);
}
else if (minPrec <= PREC_AND && token==_("or" )) { else if (minPrec <= PREC_AND && token==_("or" )) {
Token t = input.peek(); Token t = input.peek();
if (t == _("else")) {// or else if (t == _("else")) {// or else
input.read(); // skip else input.read(); // skip else
// TODO: deprecate "or else" in favor of "orelse" // TODO: deprecate "or else" in favor of "orelse"
parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE); parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE);
} else { } else {
parseOper(input, script, PREC_CMP, I_BINARY, I_OR); // short-circuiting or
unsigned jmpSC = script.addInstruction(I_JUMP_SC_OR);
parseOper(input, script, PREC_CMP);
script.comeFrom(jmpSC);
} }
} }
else if (minPrec <= PREC_AND && token==_("xor")) parseOper(input, script, PREC_CMP, I_BINARY, I_XOR); else if (minPrec <= PREC_AND && token==_("xor")) parseOper(input, script, PREC_CMP, I_BINARY, I_XOR);
......
...@@ -101,6 +101,8 @@ static const unsigned int INVALID_ADDRESS = 0x03FFFFFF; ...@@ -101,6 +101,8 @@ static const unsigned int INVALID_ADDRESS = 0x03FFFFFF;
unsigned int Script::addInstruction(InstructionType t) { unsigned int Script::addInstruction(InstructionType t) {
assert( t == I_JUMP assert( t == I_JUMP
|| t == I_JUMP_IF_NOT || t == I_JUMP_IF_NOT
|| t == I_JUMP_SC_AND
|| t == I_JUMP_SC_OR
|| t == I_LOOP || t == I_LOOP
|| t == I_LOOP_WITH_KEY || t == I_LOOP_WITH_KEY
|| t == I_POP); || t == I_POP);
...@@ -134,6 +136,8 @@ void Script::addInstruction(InstructionType t, const String& s) { ...@@ -134,6 +136,8 @@ void Script::addInstruction(InstructionType t, const String& s) {
void Script::comeFrom(unsigned int pos) { void Script::comeFrom(unsigned int pos) {
assert( instructions.at(pos).instr == I_JUMP assert( instructions.at(pos).instr == I_JUMP
|| instructions.at(pos).instr == I_JUMP_IF_NOT || instructions.at(pos).instr == I_JUMP_IF_NOT
|| instructions.at(pos).instr == I_JUMP_SC_AND
|| instructions.at(pos).instr == I_JUMP_SC_OR
|| instructions.at(pos).instr == I_LOOP || instructions.at(pos).instr == I_LOOP
|| instructions.at(pos).instr == I_LOOP_WITH_KEY); || instructions.at(pos).instr == I_LOOP_WITH_KEY);
assert( instructions.at(pos).data == INVALID_ADDRESS ); assert( instructions.at(pos).data == INVALID_ADDRESS );
...@@ -166,6 +170,8 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { ...@@ -166,6 +170,8 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
case I_PUSH_CONST: ret += _("push"); break; case I_PUSH_CONST: ret += _("push"); break;
case I_JUMP: ret += _("jump"); break; case I_JUMP: ret += _("jump"); break;
case I_JUMP_IF_NOT: ret += _("jnz"); break; case I_JUMP_IF_NOT: ret += _("jnz"); break;
case I_JUMP_SC_AND: ret += _("jump sc and");break;
case I_JUMP_SC_OR: ret += _("jump sc or"); break;
case I_GET_VAR: ret += _("get"); break; case I_GET_VAR: ret += _("get"); break;
case I_SET_VAR: ret += _("set"); break; case I_SET_VAR: ret += _("set"); break;
case I_MEMBER_C: ret += _("member_c"); break; case I_MEMBER_C: ret += _("member_c"); break;
...@@ -221,7 +227,10 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { ...@@ -221,7 +227,10 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
case I_PUSH_CONST: case I_MEMBER_C: // const case I_PUSH_CONST: case I_MEMBER_C: // const
ret += _("\t") + constants[i.data]->typeName(); ret += _("\t") + constants[i.data]->typeName();
break; break;
case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY: case I_MAKE_OBJECT: case I_CALL: case I_CLOSURE: case I_DUP: // int case I_JUMP: case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR:
case I_LOOP: case I_LOOP_WITH_KEY:
case I_MAKE_OBJECT:
case I_CALL: case I_CLOSURE: case I_DUP: // int
ret += String::Format(_("\t%d"), i.data); ret += String::Format(_("\t%d"), i.data);
break; break;
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
...@@ -310,6 +319,9 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) ...@@ -310,6 +319,9 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
} }
case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY: case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY:
return nullptr; // give up return nullptr; // give up
case I_JUMP_SC_AND: case I_JUMP_SC_OR:
// assume the fallthrough case, in which case we compared and poped the top of the stack
to_skip += 1; break;
default: default:
break; // nett stack effect 0 break; // nett stack effect 0
} }
......
...@@ -26,6 +26,8 @@ enum InstructionType ...@@ -26,6 +26,8 @@ enum InstructionType
, I_PUSH_CONST = 1 ///< arg = const val : push a constant onto the stack , I_PUSH_CONST = 1 ///< arg = const val : push a constant onto the stack
, I_JUMP = 2 ///< arg = address : move the instruction pointer to the given position , I_JUMP = 2 ///< arg = address : move the instruction pointer to the given position
, I_JUMP_IF_NOT = 3 ///< arg = address : move the instruction pointer if the top of the stack is false , I_JUMP_IF_NOT = 3 ///< arg = address : move the instruction pointer if the top of the stack is false
, I_JUMP_SC_AND = 19 ///< arg = address : (short-circuiting and) jump and don't pop if the top of the stack is false
, I_JUMP_SC_OR = 20 ///< arg = address : (short-circuiting or) jump and don't pop if the top of the stack is true
// Variables // Variables
, I_GET_VAR = 4 ///< arg = var : find a variable, push its value onto the stack, it is an error if the variable is not found , I_GET_VAR = 4 ///< arg = var : find a variable, push its value onto the stack, it is an error if the variable is not found
, I_SET_VAR = 5 ///< arg = var : assign the top value from the stack to a variable (doesn't pop) , I_SET_VAR = 5 ///< arg = var : assign the top value from the stack to a variable (doesn't pop)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment