diff --git a/CHANGES.txt b/CHANGES.txt index 93511e88..478039e2 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -22,6 +22,7 @@ Scripting: * nil != "", so missing values are no longer equal to the empty string * The `=` operator is now deprecated, use `==` for comparisons, `:=` for assignment. * if statements without an else will now produce a warning if their result is used. + * Added case-of control structure, for comparing a value against multiple alternatives Internal: * Switch build system to to CMake diff --git a/doc/script/control_structures.txt b/doc/script/control_structures.txt index fbca32c0..59dd56c3 100644 --- a/doc/script/control_structures.txt +++ b/doc/script/control_structures.txt @@ -42,6 +42,26 @@ To use multiple statements in the then or else branches you must use parentheses > y := z > ) +--Case analysis (@case of@)-- + +To compare multiple alternatives, you can use a case expression + +> case thing of alt1: result1, alt2: result2, alt3: result3, else: otherwise + +The expression @thing@ is compared against all the alternatives, and if it is equal to one then the corresponding result is returned + +For example: +> case 1+1 of +> 1: "one", +> 2: "two", +> 3: "three", +> else: "big" +Will evaluate to @"two"@. + +Like if-then-else, case-of is an expression, and it can be used almost everywhere. + +To use multiple statements in the casess you must use parentheses. + --Loop statement (@for each@)-- To iterate over all elements in a [[type:list]] the @for each@ construct can be used diff --git a/src/script/parser.cpp b/src/script/parser.cpp index c6a727d4..3e9cdff3 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -381,11 +381,12 @@ enum Precedence , PREC_NONE }; -/// Type of expressions. Broadly: LHS/exprssion/statement +/// Type of expressions. Broadly: LHS/expression/statement +/// Lower is more restrictive enum ExprType { EXPR_VAR // A single variable, which could be converted to the left hand side of an assignment -, EXPR_STATEMENT // A 'statement', i.e. an expression that doesn't produce a result, and shouldn't be the last one in a block , EXPR_OTHER +, EXPR_STATEMENT // A 'statement', i.e. an expression that doesn't produce a result, and shouldn't be the last one in a block , EXPR_FAILED }; @@ -508,10 +509,10 @@ ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { } else if (token == _("if")) { // if AAA then BBB else CCC parseOper(input, script, PREC_AND); // AAA - unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else + Addr jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else expectToken(input, _("then")); // then ExprType type1 = parseOper(input, script, PREC_SET); // BBB - unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end + Addr jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end script.comeFrom(jmpElse); // lbl_else: ExprType type2 = EXPR_STATEMENT; if (input.peek() == _("else")) { // else @@ -520,8 +521,49 @@ ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { } else { script.addInstruction(I_PUSH_CONST, script_nil); } - script.comeFrom(jmpEnd); // lbl_end: + script.comeFrom(jmpEnd); // lbl_end: return type1 == EXPR_STATEMENT || type2 == EXPR_STATEMENT ? EXPR_STATEMENT : EXPR_OTHER; + } else if (token == _("case")) { + // case AAA of BBB: CCC[,] DDD: EEE ... else FFF + parseOper(input, script, PREC_AND); + expectToken(input, _("of")); + vector jmpsEnd; + bool wasElse = false; + ExprType type = EXPR_OTHER; + Token t = input.peek(); + while (t != TOK_RPAREN && t != TOK_EOF) { + Addr jmp; + if (input.peek() == _("else")) { + // else: + wasElse = true; + input.read(); + expectToken(input, _(":")); + } else { + parseOper(input, script, PREC_AND); + expectToken(input, _(":")); + script.addInstruction(I_DUP, 1); + script.addInstruction(I_BINARY, I_EQ); + jmp = script.addInstruction(I_JUMP_IF_NOT); + } + script.addInstruction(I_POP); + ExprType type1 = parseOper(input, script, PREC_SET); + type = max(type, type1); + if (wasElse) { + break; + } else { + if (input.peek() == _(",")) input.read(); // optional comma + jmpsEnd.push_back(script.addInstruction(I_JUMP)); + script.comeFrom(jmp); + } + t = input.peek(); + } + if (!wasElse) { + type = max(type, EXPR_STATEMENT); + script.addInstruction(I_POP); + script.addInstruction(I_PUSH_CONST, script_nil); + } + for (Addr jmp : jmpsEnd) script.comeFrom(jmp); + return type; } else if (token == _("for")) { // the loop body should have a net stack effect of 0, but the entire expression of +1 // solution: add all results from the body, start with nil @@ -565,7 +607,7 @@ ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range } script.addInstruction(I_PUSH_CONST, script_nil); // push nil - unsigned lblStart = script.addInstruction(with_key + Addr lblStart = script.addInstruction(with_key ? I_LOOP_WITH_KEY // lbl_start: loop_with_key lbl_end : I_LOOP); // lbl_start: loop lbl_end expectToken(input, _("do")); // do @@ -713,7 +755,7 @@ ExprType parseOper(TokenIterator& input, Script& script, Precedence minPrec, Ins // I_JUMP_SC_AND after # if top==false then goto after else pop // YYY // after: - unsigned jmpSC = script.addInstruction(I_JUMP_SC_AND); + Addr jmpSC = script.addInstruction(I_JUMP_SC_AND); parseOper(input, script, PREC_CMP); script.comeFrom(jmpSC); } @@ -725,7 +767,7 @@ ExprType parseOper(TokenIterator& input, Script& script, Precedence minPrec, Ins parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE); } else { // short-circuiting or - unsigned jmpSC = script.addInstruction(I_JUMP_SC_OR); + Addr jmpSC = script.addInstruction(I_JUMP_SC_OR); parseOper(input, script, PREC_CMP); script.comeFrom(jmpSC); } diff --git a/src/script/script.cpp b/src/script/script.cpp index ea0c9ee7..976f449c 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -97,7 +97,7 @@ ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const { static const unsigned int INVALID_ADDRESS = 0x03FFFFFF; -unsigned int Script::addInstruction(InstructionType t) { +Addr Script::addInstruction(InstructionType t) { assert( t == I_JUMP || t == I_JUMP_IF_NOT || t == I_JUMP_SC_AND @@ -107,7 +107,7 @@ unsigned int Script::addInstruction(InstructionType t) { || t == I_POP); Instruction i = {t, {INVALID_ADDRESS}}; instructions.push_back(i); - return getLabel() - 1; + return Addr{getLabel().addr - 1}; } void Script::addInstruction(InstructionType t, unsigned int d) { // Don't optimize ...I_PUSH_CONST x; I_MEMBER... to I_MEMBER_C @@ -121,6 +121,9 @@ void Script::addInstruction(InstructionType t, unsigned int d) { Instruction i = {t, {d}}; instructions.push_back(i); } +void Script::addInstruction(InstructionType t, Addr d) { + addInstruction(t, d.addr); +} void Script::addInstruction(InstructionType t, const ScriptValueP& c) { constants.push_back(c); Instruction i = {t, {(unsigned int)constants.size() - 1}}; @@ -132,19 +135,19 @@ void Script::addInstruction(InstructionType t, const String& s) { instructions.push_back(i); } -void Script::comeFrom(unsigned int pos) { - assert( instructions.at(pos).instr == I_JUMP - || 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_WITH_KEY); - assert( instructions.at(pos).data == INVALID_ADDRESS ); - instructions.at(pos).data = (unsigned int)instructions.size(); +void Script::comeFrom(Addr pos) { + assert( instructions.at(pos.addr).instr == I_JUMP + || instructions.at(pos.addr).instr == I_JUMP_IF_NOT + || instructions.at(pos.addr).instr == I_JUMP_SC_AND + || instructions.at(pos.addr).instr == I_JUMP_SC_OR + || instructions.at(pos.addr).instr == I_LOOP + || instructions.at(pos.addr).instr == I_LOOP_WITH_KEY); + assert( instructions.at(pos.addr).data == INVALID_ADDRESS ); + instructions.at(pos.addr).data = (unsigned int)instructions.size(); } -unsigned int Script::getLabel() const { - return (unsigned int)instructions.size(); +Addr Script::getLabel() const { + return Addr{ (unsigned int)instructions.size() }; } #ifdef _DEBUG // debugging @@ -223,6 +226,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { switch (i.instr) { case I_PUSH_CONST: case I_MEMBER_C: // const ret += _("\t") + constants[i.data]->typeName(); + ret += _("\t") + constants[i.data]->toCode(); break; 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: diff --git a/src/script/script.hpp b/src/script/script.hpp index 43f6cc55..96a71e68 100644 --- a/src/script/script.hpp +++ b/src/script/script.hpp @@ -110,6 +110,11 @@ struct Instruction { }; }; +/// An address/position in a script +struct Addr { + unsigned int addr; +}; + // ----------------------------------------------------------------------------- : Variables // for faster lookup from code @@ -170,9 +175,11 @@ public: ScriptValueP dependencies(Context& ctx, const Dependency&) const override; /// Add a jump instruction, later comeFrom should be called on the returned value - unsigned int addInstruction(InstructionType t); + Addr addInstruction(InstructionType t); /// Add an instruction with integer data void addInstruction(InstructionType t, unsigned int d); + /// Add an instruction with integer data + void addInstruction(InstructionType t, Addr d); /// Add an instruction with constant data void addInstruction(InstructionType t, const ScriptValueP& c); /// Add an instruction with string data @@ -182,9 +189,9 @@ public: /** The instruction at pos must be a jumping instruction, it is changed so the current position * 'comes from' pos (in addition to other control flow). */ - void comeFrom(unsigned int pos); + void comeFrom(Addr pos); /// Get the current instruction position - unsigned int getLabel() const; + Addr getLabel() const; /// Get access to the vector of instructions inline vector& getInstructions() { return instructions; }