mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Added "case-of" control structure
This commit is contained in:
@@ -22,6 +22,7 @@ Scripting:
|
|||||||
* nil != "", so missing values are no longer equal to the empty string
|
* nil != "", so missing values are no longer equal to the empty string
|
||||||
* The `=` operator is now deprecated, use `==` for comparisons, `:=` for assignment.
|
* 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.
|
* 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:
|
Internal:
|
||||||
* Switch build system to to CMake
|
* Switch build system to to CMake
|
||||||
|
|||||||
@@ -42,6 +42,26 @@ To use multiple statements in the then or else branches you must use parentheses
|
|||||||
> y := z
|
> 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@)--
|
--Loop statement (@for each@)--
|
||||||
|
|
||||||
To iterate over all elements in a [[type:list]] the @for each@ construct can be used
|
To iterate over all elements in a [[type:list]] the @for each@ construct can be used
|
||||||
|
|||||||
+50
-8
@@ -381,11 +381,12 @@ enum Precedence
|
|||||||
, PREC_NONE
|
, PREC_NONE
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Type of expressions. Broadly: LHS/exprssion/statement
|
/// Type of expressions. Broadly: LHS/expression/statement
|
||||||
|
/// Lower is more restrictive
|
||||||
enum ExprType
|
enum ExprType
|
||||||
{ EXPR_VAR // A single variable, which could be converted to the left hand side of an assignment
|
{ 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_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
|
, EXPR_FAILED
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -508,10 +509,10 @@ ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
|
|||||||
} else if (token == _("if")) {
|
} else if (token == _("if")) {
|
||||||
// if AAA then BBB else CCC
|
// if AAA then BBB else CCC
|
||||||
parseOper(input, script, PREC_AND); // AAA
|
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
|
expectToken(input, _("then")); // then
|
||||||
ExprType type1 = parseOper(input, script, PREC_SET); // BBB
|
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:
|
script.comeFrom(jmpElse); // lbl_else:
|
||||||
ExprType type2 = EXPR_STATEMENT;
|
ExprType type2 = EXPR_STATEMENT;
|
||||||
if (input.peek() == _("else")) { // else
|
if (input.peek() == _("else")) { // else
|
||||||
@@ -520,8 +521,49 @@ ExprType parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
|
|||||||
} else {
|
} else {
|
||||||
script.addInstruction(I_PUSH_CONST, script_nil);
|
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;
|
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<Addr> 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")) {
|
} else if (token == _("for")) {
|
||||||
// the loop body should have a net stack effect of 0, but the entire expression of +1
|
// 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
|
// 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_BINARY, I_ITERATOR_R); // iterator_range
|
||||||
}
|
}
|
||||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
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_WITH_KEY // lbl_start: loop_with_key lbl_end
|
||||||
: I_LOOP); // lbl_start: loop lbl_end
|
: I_LOOP); // lbl_start: loop lbl_end
|
||||||
expectToken(input, _("do")); // do
|
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
|
// I_JUMP_SC_AND after # if top==false then goto after else pop
|
||||||
// YYY
|
// YYY
|
||||||
// after:
|
// after:
|
||||||
unsigned jmpSC = script.addInstruction(I_JUMP_SC_AND);
|
Addr jmpSC = script.addInstruction(I_JUMP_SC_AND);
|
||||||
parseOper(input, script, PREC_CMP);
|
parseOper(input, script, PREC_CMP);
|
||||||
script.comeFrom(jmpSC);
|
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);
|
parseOper(input, script, PREC_ADD, I_BINARY, I_OR_ELSE);
|
||||||
} else {
|
} else {
|
||||||
// short-circuiting or
|
// short-circuiting or
|
||||||
unsigned jmpSC = script.addInstruction(I_JUMP_SC_OR);
|
Addr jmpSC = script.addInstruction(I_JUMP_SC_OR);
|
||||||
parseOper(input, script, PREC_CMP);
|
parseOper(input, script, PREC_CMP);
|
||||||
script.comeFrom(jmpSC);
|
script.comeFrom(jmpSC);
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-13
@@ -97,7 +97,7 @@ ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const {
|
|||||||
|
|
||||||
static const unsigned int INVALID_ADDRESS = 0x03FFFFFF;
|
static const unsigned int INVALID_ADDRESS = 0x03FFFFFF;
|
||||||
|
|
||||||
unsigned int Script::addInstruction(InstructionType t) {
|
Addr 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_AND
|
||||||
@@ -107,7 +107,7 @@ unsigned int Script::addInstruction(InstructionType t) {
|
|||||||
|| t == I_POP);
|
|| t == I_POP);
|
||||||
Instruction i = {t, {INVALID_ADDRESS}};
|
Instruction i = {t, {INVALID_ADDRESS}};
|
||||||
instructions.push_back(i);
|
instructions.push_back(i);
|
||||||
return getLabel() - 1;
|
return Addr{getLabel().addr - 1};
|
||||||
}
|
}
|
||||||
void Script::addInstruction(InstructionType t, unsigned int d) {
|
void Script::addInstruction(InstructionType t, unsigned int d) {
|
||||||
// Don't optimize ...I_PUSH_CONST x; I_MEMBER... to I_MEMBER_C
|
// 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}};
|
Instruction i = {t, {d}};
|
||||||
instructions.push_back(i);
|
instructions.push_back(i);
|
||||||
}
|
}
|
||||||
|
void Script::addInstruction(InstructionType t, Addr d) {
|
||||||
|
addInstruction(t, d.addr);
|
||||||
|
}
|
||||||
void Script::addInstruction(InstructionType t, const ScriptValueP& c) {
|
void Script::addInstruction(InstructionType t, const ScriptValueP& c) {
|
||||||
constants.push_back(c);
|
constants.push_back(c);
|
||||||
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
||||||
@@ -132,19 +135,19 @@ void Script::addInstruction(InstructionType t, const String& s) {
|
|||||||
instructions.push_back(i);
|
instructions.push_back(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Script::comeFrom(unsigned int pos) {
|
void Script::comeFrom(Addr pos) {
|
||||||
assert( instructions.at(pos).instr == I_JUMP
|
assert( instructions.at(pos.addr).instr == I_JUMP
|
||||||
|| instructions.at(pos).instr == I_JUMP_IF_NOT
|
|| instructions.at(pos.addr).instr == I_JUMP_IF_NOT
|
||||||
|| instructions.at(pos).instr == I_JUMP_SC_AND
|
|| instructions.at(pos.addr).instr == I_JUMP_SC_AND
|
||||||
|| instructions.at(pos).instr == I_JUMP_SC_OR
|
|| instructions.at(pos.addr).instr == I_JUMP_SC_OR
|
||||||
|| instructions.at(pos).instr == I_LOOP
|
|| instructions.at(pos.addr).instr == I_LOOP
|
||||||
|| instructions.at(pos).instr == I_LOOP_WITH_KEY);
|
|| instructions.at(pos.addr).instr == I_LOOP_WITH_KEY);
|
||||||
assert( instructions.at(pos).data == INVALID_ADDRESS );
|
assert( instructions.at(pos.addr).data == INVALID_ADDRESS );
|
||||||
instructions.at(pos).data = (unsigned int)instructions.size();
|
instructions.at(pos.addr).data = (unsigned int)instructions.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int Script::getLabel() const {
|
Addr Script::getLabel() const {
|
||||||
return (unsigned int)instructions.size();
|
return Addr{ (unsigned int)instructions.size() };
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _DEBUG // debugging
|
#ifdef _DEBUG // debugging
|
||||||
@@ -223,6 +226,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
|||||||
switch (i.instr) {
|
switch (i.instr) {
|
||||||
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();
|
||||||
|
ret += _("\t") + constants[i.data]->toCode();
|
||||||
break;
|
break;
|
||||||
case I_JUMP: case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR:
|
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_LOOP: case I_LOOP_WITH_KEY:
|
||||||
|
|||||||
+10
-3
@@ -110,6 +110,11 @@ struct Instruction {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An address/position in a script
|
||||||
|
struct Addr {
|
||||||
|
unsigned int addr;
|
||||||
|
};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------- : Variables
|
// ----------------------------------------------------------------------------- : Variables
|
||||||
|
|
||||||
// for faster lookup from code
|
// for faster lookup from code
|
||||||
@@ -170,9 +175,11 @@ public:
|
|||||||
ScriptValueP dependencies(Context& ctx, const Dependency&) const override;
|
ScriptValueP dependencies(Context& ctx, const Dependency&) const override;
|
||||||
|
|
||||||
/// Add a jump instruction, later comeFrom should be called on the returned value
|
/// 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
|
/// Add an instruction with integer data
|
||||||
void addInstruction(InstructionType t, unsigned int d);
|
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
|
/// Add an instruction with constant data
|
||||||
void addInstruction(InstructionType t, const ScriptValueP& c);
|
void addInstruction(InstructionType t, const ScriptValueP& c);
|
||||||
/// Add an instruction with string data
|
/// 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
|
/** 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).
|
* 'comes from' pos (in addition to other control flow).
|
||||||
*/
|
*/
|
||||||
void comeFrom(unsigned int pos);
|
void comeFrom(Addr pos);
|
||||||
/// Get the current instruction position
|
/// Get the current instruction position
|
||||||
unsigned int getLabel() const;
|
Addr getLabel() const;
|
||||||
|
|
||||||
/// Get access to the vector of instructions
|
/// Get access to the vector of instructions
|
||||||
inline vector<Instruction>& getInstructions() { return instructions; }
|
inline vector<Instruction>& getInstructions() { return instructions; }
|
||||||
|
|||||||
Reference in New Issue
Block a user