From 9abfdd171c9601251345cdda35ed558ba928d2ab Mon Sep 17 00:00:00 2001 From: twanvl Date: Wed, 27 Aug 2008 13:06:24 +0000 Subject: [PATCH] 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 --- doc/script/control_structures.txt | 26 ++++++++-- src/script/context.cpp | 14 ++++++ src/script/dependency.cpp | 15 ++++++ src/script/parser.cpp | 80 ++++++++++++++++++------------- src/script/script.cpp | 17 +++++-- src/script/script.hpp | 29 +++++------ src/script/value.cpp | 6 +-- 7 files changed, 128 insertions(+), 59 deletions(-) diff --git a/doc/script/control_structures.txt b/doc/script/control_structures.txt index ef35a0e9..fbca32c0 100644 --- a/doc/script/control_structures.txt +++ b/doc/script/control_structures.txt @@ -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. diff --git a/src/script/context.cpp b/src/script/context.cpp index 00127689..82ef9a96 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -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); diff --git a/src/script/dependency.cpp b/src/script/dependency.cpp index eea37b56..13369e7e 100644 --- a/src/script/dependency.cpp +++ b/src/script/dependency.cpp @@ -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); diff --git a/src/script/parser.cpp b/src/script/parser.cpp index 624fc448..b7b77044 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -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, _("(")); diff --git a/src/script/script.cpp b/src/script/script.cpp index 4b17af3c..cb0c01ef 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) { - 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 diff --git a/src/script/script.hpp b/src/script/script.hpp index 138439ae..f1f667bc 100644 --- a/src/script/script.hpp +++ b/src/script/script.hpp @@ -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; }; }; diff --git a/src/script/value.cpp b/src/script/value.cpp index 9aa76d79..e135024f 100644 --- a/src/script/value.cpp +++ b/src/script/value.cpp @@ -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) {