mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
added "for each k:v in .. do .." statement
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1174 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -55,7 +55,24 @@ The results of the expression are combined using the @+@ [[script:operators|oper
|
||||
> for each x in ["a","b","c"] do x == "abc"
|
||||
> for each x in ["a","b","c"] do [x+x] == ["aa","bb","cc"]
|
||||
|
||||
It is also possible to iterate over a range of values
|
||||
|
||||
When iterating over a [[type:map]] the key is also available:
|
||||
|
||||
>> for each key:variable in list do expression
|
||||
|
||||
For example:
|
||||
|
||||
> (for each k:v in [name:"Thing", rank:"High"] do
|
||||
> "Its {k} is {v}. "
|
||||
> ) == "Its name is Thing. Its rank is High. "
|
||||
|
||||
When iterating over a list, the key is the index in the list, starting with @0@:
|
||||
> (for each k:v in ["a","b","c"] do
|
||||
> ["element {k} is {v}"]
|
||||
> ) == ["element 0 is a","element 1 is b","element 2 is c"]
|
||||
|
||||
|
||||
It is also possible to iterate over a range of values:
|
||||
|
||||
> for variable from begin to end do expression
|
||||
|
||||
@@ -64,6 +81,7 @@ The expression is evaluated for each number from begin to end (including begin,
|
||||
--Summary--
|
||||
|
||||
! Syntax Description
|
||||
| @if a then b else c@ If @a@ is @true@ evaluates to @b@, otherwise evaluates to @c@
|
||||
| @for each x in list do something@ Does @something@ for each element in a list
|
||||
| @for x from 1 to 100 do something@ Does @something@ for all numbers from 1 to 100
|
||||
| @if a then b else c@ If @a@ is @true@ evaluates to @b@, otherwise evaluates to @c@.
|
||||
| @for each x in list do something@ Does @something@ for each element in a list, adding the results
|
||||
| @for each k:v in list do something@ Does @something@ for each element in a map using the key/index in the map.
|
||||
| @for x from 1 to 100 do something@ Does @something@ for all numbers from 1 to 100.
|
||||
|
||||
@@ -101,6 +101,20 @@ ScriptValueP Context::eval(const Script& script, bool useScope) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next key;next value or jump
|
||||
case I_LOOP_WITH_KEY: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP key;
|
||||
ScriptValueP val = it->next(&key);
|
||||
if (val) {
|
||||
stack.push_back(val);
|
||||
stack.push_back(key);
|
||||
} else {
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make an object
|
||||
case I_MAKE_OBJECT: {
|
||||
makeObject(i.data);
|
||||
|
||||
@@ -239,6 +239,21 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script)
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next value or jump (almost as normal)
|
||||
case I_LOOP_WITH_KEY: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP key;
|
||||
ScriptValueP val = it->next(&key);
|
||||
if (val) {
|
||||
it = dependency_dummy; // invalidate iterator, so we loop only once
|
||||
stack.push_back(val);
|
||||
stack.push_back(key);
|
||||
} else {
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make an object
|
||||
case I_MAKE_OBJECT: {
|
||||
makeObject(i.data);
|
||||
|
||||
+47
-33
@@ -483,43 +483,57 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
|
||||
} 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
|
||||
if (input.peek() == _("each")) {
|
||||
// for each AAA in BBB do CCC
|
||||
input.read(); // each
|
||||
Token name = input.read(); // AAA
|
||||
bool is_each = input.peek() == _("each");
|
||||
if (is_each) {
|
||||
// for each AAA(:BBB) in CCC do EEE
|
||||
input.read(); // each?
|
||||
} else {
|
||||
// for AAA(:BBB) from CCC to DDD do EEE
|
||||
}
|
||||
// name
|
||||
Token name = input.read(); // AAA
|
||||
if (name != TOK_NAME) {
|
||||
input.expected(_("name"));
|
||||
}
|
||||
Variable var = string_to_variable(name.value);
|
||||
// key:value?
|
||||
bool with_key = input.peek() == _(":");
|
||||
Variable key = (Variable)-1;
|
||||
if (with_key) {
|
||||
input.read(); // :
|
||||
name = input.read(); // BBB
|
||||
if (name != TOK_NAME) {
|
||||
input.expected(_("name"));
|
||||
}
|
||||
expectToken(input, _("in")); // in
|
||||
parseOper(input, script, PREC_AND); // BBB
|
||||
script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection
|
||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
||||
unsigned lblStart = script.addInstruction(I_LOOP); // lbl_start: loop lbl_end
|
||||
expectToken(input, _("do")); // do
|
||||
script.addInstruction(I_SET_VAR,
|
||||
string_to_variable(name.value));// set name
|
||||
script.addInstruction(I_BINARY, I_POP); // pop
|
||||
parseOper(input, script, PREC_SET, I_BINARY, I_ADD);// CCC; add
|
||||
script.addInstruction(I_JUMP, lblStart); // jump lbl_start
|
||||
script.comeFrom(lblStart); // lbl_end:
|
||||
} else {
|
||||
// for AAA from BBB to CCC do DDD
|
||||
Token name = input.read(); // AAA
|
||||
expectToken(input, _("from")); // from
|
||||
parseOper(input, script, PREC_AND); // BBB
|
||||
expectToken(input, _("to")); // to
|
||||
parseOper(input, script, PREC_AND); // CCC
|
||||
script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range
|
||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
||||
unsigned lblStart = script.addInstruction(I_LOOP); // lbl_start: loop lbl_end
|
||||
expectToken(input, _("do")); // do
|
||||
script.addInstruction(I_SET_VAR,
|
||||
string_to_variable(name.value));// set name
|
||||
script.addInstruction(I_BINARY, I_POP); // pop
|
||||
parseOper(input, script, PREC_SET, I_BINARY, I_ADD);// DDD; add
|
||||
script.addInstruction(I_JUMP, lblStart); // jump lbl_start
|
||||
script.comeFrom(lblStart); // lbl_end:
|
||||
key = string_to_variable(name.value);
|
||||
swap(var,key);
|
||||
}
|
||||
// iterator
|
||||
if (is_each) {
|
||||
expectToken(input, _("in")); // in
|
||||
parseOper(input, script, PREC_AND); // CCC
|
||||
script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection
|
||||
} else {
|
||||
expectToken(input, _("from")); // from
|
||||
parseOper(input, script, PREC_AND); // CCC
|
||||
expectToken(input, _("to")); // to
|
||||
parseOper(input, script, PREC_AND); // DDD
|
||||
script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range
|
||||
}
|
||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
||||
unsigned 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
|
||||
if (with_key) {
|
||||
script.addInstruction(I_SET_VAR, key); // set key_name
|
||||
script.addInstruction(I_BINARY, I_POP); // pop
|
||||
}
|
||||
script.addInstruction(I_SET_VAR, var); // set name
|
||||
script.addInstruction(I_BINARY, I_POP); // pop
|
||||
parseOper(input, script, PREC_SET, I_BINARY, I_ADD); // EEE; add
|
||||
script.addInstruction(I_JUMP, lblStart); // jump lbl_start
|
||||
script.comeFrom(lblStart); // lbl_end:
|
||||
} else if (token == _("rgb")) {
|
||||
// rgb(r, g, b)
|
||||
expectToken(input, _("("));
|
||||
|
||||
+12
-5
@@ -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) {
|
||||
assert( t == I_JUMP || t == I_JUMP_IF_NOT || t == I_LOOP);
|
||||
assert( t == I_JUMP || t == I_JUMP_IF_NOT || t == I_LOOP || t == I_LOOP_WITH_KEY);
|
||||
Instruction i = {t, {INVALID_ADDRESS}};
|
||||
instructions.push_back(i);
|
||||
return getLabel() - 1;
|
||||
@@ -128,7 +128,8 @@ void Script::addInstruction(InstructionType t, const String& s) {
|
||||
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_LOOP);
|
||||
|| 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();
|
||||
}
|
||||
@@ -163,6 +164,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
||||
case I_SET_VAR: ret += _("set"); break;
|
||||
case I_MEMBER_C: ret += _("member_c"); break;
|
||||
case I_LOOP: ret += _("loop"); break;
|
||||
case I_LOOP_WITH_KEY:ret += _("loop with key"); break;
|
||||
case I_MAKE_OBJECT: ret += _("make object");break;
|
||||
case I_CALL: ret += _("call"); break;
|
||||
case I_CLOSURE: ret += _("closure"); break;
|
||||
@@ -212,7 +214,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
||||
case I_PUSH_CONST: case I_MEMBER_C: // const
|
||||
ret += _("\t") + constants[i.data]->typeName();
|
||||
break;
|
||||
case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_MAKE_OBJECT: case I_CALL: case I_CLOSURE: case I_DUP: // int
|
||||
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
|
||||
ret += String::Format(_("\t%d"), i.data);
|
||||
break;
|
||||
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
|
||||
@@ -255,7 +257,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
|
||||
break;
|
||||
case I_JUMP: {
|
||||
if (instr->data > initial) {
|
||||
// we were in an else branch all along, ignore this jump
|
||||
// forward jump, so we were in an else branch all along, ignore this jump
|
||||
return instr + 1;
|
||||
}
|
||||
// there will be a way not to take this jump
|
||||
@@ -275,6 +277,11 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_LOOP_WITH_KEY && instr->data == after_jump) {
|
||||
// same as above,
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_JUMP_IF_NOT && instr->data == after_jump) {
|
||||
// code looks like
|
||||
// 1 (nettstack+1)
|
||||
@@ -293,7 +300,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
|
||||
++instr; // compensate for the -- in the outer loop
|
||||
break;
|
||||
}
|
||||
case I_JUMP_IF_NOT: case I_LOOP:
|
||||
case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY:
|
||||
return nullptr; // give up
|
||||
default:
|
||||
break; // nett stack effect 0
|
||||
|
||||
+15
-14
@@ -32,16 +32,17 @@ enum InstructionType
|
||||
, I_MEMBER_C = 6 ///< arg = const name : finds a member of the top of the stack replaces the top of the stack with the member
|
||||
, I_LOOP = 7 ///< arg = address : loop over the elements of an iterator, which is the *second* element of the stack (this allows for combing the results of multiple iterations)
|
||||
///< at the end performs a jump and pops the iterator. note: The second element of the stack must be an iterator!
|
||||
, I_MAKE_OBJECT = 8 ///< arg = int : make a list/map with n elements, pops 2n values of the stack, n key/value pairs
|
||||
, I_LOOP_WITH_KEY = 8 ///< arg = address : loop, but also pushing the key
|
||||
, I_MAKE_OBJECT = 9 ///< arg = int : make a list/map with n elements, pops 2n values of the stack, n key/value pairs
|
||||
// Functions
|
||||
, I_CALL = 9 ///< arg = int, n*var : call the top item of the stack, with the given number of arguments (set with SET_VAR, but in the activation record of the call)
|
||||
, I_CLOSURE = 10 ///< arg = int, n*var : construct a call closure object with the given arguments
|
||||
, I_CALL = 10 ///< arg = int, n*var : call the top item of the stack, with the given number of arguments (set with SET_VAR, but in the activation record of the call)
|
||||
, I_CLOSURE = 11 ///< arg = int, n*var : construct a call closure object with the given arguments
|
||||
// Simple instructions
|
||||
, I_UNARY = 11 ///< arg = 1ary instr : pop 1 value, apply a function, push the result
|
||||
, I_BINARY = 12 ///< arg = 2ary instr : pop 2 values, apply a function, push the result
|
||||
, I_TERNARY = 13 ///< arg = 3ary instr : pop 3 values, apply a function, push the result
|
||||
, I_QUATERNARY = 14 ///< arg = 4ary instr : pop 4 values, apply a function, push the result
|
||||
, I_DUP = 15 ///< arg = int : duplicate the k-from-top element of the stack
|
||||
, I_UNARY = 12 ///< arg = 1ary instr : pop 1 value, apply a function, push the result
|
||||
, I_BINARY = 13 ///< arg = 2ary instr : pop 2 values, apply a function, push the result
|
||||
, I_TERNARY = 14 ///< arg = 3ary instr : pop 3 values, apply a function, push the result
|
||||
, I_QUATERNARY = 15 ///< arg = 4ary instr : pop 4 values, apply a function, push the result
|
||||
, I_DUP = 16 ///< arg = int : duplicate the k-from-top element of the stack
|
||||
};
|
||||
|
||||
/// Types of unary instructions (taking one argument from the stack)
|
||||
@@ -96,13 +97,13 @@ enum QuaternaryInstructionType
|
||||
* Then the instr? member gives the actual instruction to perform
|
||||
*/
|
||||
struct Instruction {
|
||||
InstructionType instr : 5;
|
||||
InstructionType instr : 6;
|
||||
union {
|
||||
unsigned int data : 27;
|
||||
UnaryInstructionType instr1 : 27;
|
||||
BinaryInstructionType instr2 : 27;
|
||||
TernaryInstructionType instr3 : 27;
|
||||
QuaternaryInstructionType instr4 : 27;
|
||||
unsigned int data : 26;
|
||||
UnaryInstructionType instr1 : 26;
|
||||
BinaryInstructionType instr2 : 26;
|
||||
TernaryInstructionType instr3 : 26;
|
||||
QuaternaryInstructionType instr4 : 26;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -128,17 +128,17 @@ class ScriptRangeIterator : public ScriptIterator {
|
||||
public:
|
||||
// Construct a range iterator with the given bounds (inclusive)
|
||||
ScriptRangeIterator(int start, int end)
|
||||
: pos(start), end(end) {}
|
||||
: pos(start), start(start), end(end) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos <= end) {
|
||||
if (key_out) *key_out = to_script(pos);
|
||||
if (key_out) *key_out = to_script(pos-start);
|
||||
return to_script(pos++);
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
private:
|
||||
int pos, end;
|
||||
int pos, start, end;
|
||||
};
|
||||
|
||||
ScriptValueP rangeIterator(int start, int end) {
|
||||
|
||||
Reference in New Issue
Block a user