Commit 81dcfb00 authored by twanvl's avatar twanvl

added "for each k:v in .. do .." statement

parent 278c950f
...@@ -55,7 +55,24 @@ The results of the expression are combined using the @+@ [[script:operators|oper ...@@ -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 == "abc"
> for each x in ["a","b","c"] do [x+x] == ["aa","bb","cc"] > 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 > 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, ...@@ -64,6 +81,7 @@ The expression is evaluated for each number from begin to end (including begin,
--Summary-- --Summary--
! Syntax Description ! Syntax Description
| @if a then b else c@ If @a@ is @true@ evaluates to @b@, otherwise evaluates to @c@ | @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 each x in list do something@ Does @something@ for each element in a list, adding the results
| @for x from 1 to 100 do something@ Does @something@ for all numbers from 1 to 100 | @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) { ...@@ -101,6 +101,20 @@ ScriptValueP Context::eval(const Script& script, bool useScope) {
} }
break; 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 // Make an object
case I_MAKE_OBJECT: { case I_MAKE_OBJECT: {
makeObject(i.data); makeObject(i.data);
......
...@@ -239,6 +239,21 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script) ...@@ -239,6 +239,21 @@ ScriptValueP Context::dependencies(const Dependency& dep, const Script& script)
} }
break; 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 // Make an object
case I_MAKE_OBJECT: { case I_MAKE_OBJECT: {
makeObject(i.data); makeObject(i.data);
......
...@@ -483,43 +483,57 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { ...@@ -483,43 +483,57 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
} 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
if (input.peek() == _("each")) { bool is_each = input.peek() == _("each");
// for each AAA in BBB do CCC if (is_each) {
input.read(); // each // for each AAA(:BBB) in CCC do EEE
Token name = input.read(); // AAA 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) { if (name != TOK_NAME) {
input.expected(_("name")); input.expected(_("name"));
} }
key = string_to_variable(name.value);
swap(var,key);
}
// iterator
if (is_each) {
expectToken(input, _("in")); // in expectToken(input, _("in")); // in
parseOper(input, script, PREC_AND); // BBB parseOper(input, script, PREC_AND); // CCC
script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection 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 { } else {
// for AAA from BBB to CCC do DDD
Token name = input.read(); // AAA
expectToken(input, _("from")); // from expectToken(input, _("from")); // from
parseOper(input, script, PREC_AND); // BBB
expectToken(input, _("to")); // to
parseOper(input, script, PREC_AND); // CCC 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_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 script.addInstruction(I_PUSH_CONST, script_nil); // push nil
expectToken(input, _("do")); // do unsigned lblStart = script.addInstruction(with_key
script.addInstruction(I_SET_VAR, ? I_LOOP_WITH_KEY // lbl_start: loop_with_key lbl_end
string_to_variable(name.value));// set name : 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_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:
} }
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")) { } else if (token == _("rgb")) {
// rgb(r, g, b) // rgb(r, g, b)
expectToken(input, _("(")); expectToken(input, _("("));
......
...@@ -97,7 +97,7 @@ ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const { ...@@ -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) { 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}}; Instruction i = {t, {INVALID_ADDRESS}};
instructions.push_back(i); instructions.push_back(i);
return getLabel() - 1; return getLabel() - 1;
...@@ -128,7 +128,8 @@ void Script::addInstruction(InstructionType t, const String& s) { ...@@ -128,7 +128,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_LOOP); || instructions.at(pos).instr == I_LOOP
|| instructions.at(pos).instr == I_LOOP_WITH_KEY);
assert( instructions.at(pos).data == INVALID_ADDRESS ); assert( instructions.at(pos).data == INVALID_ADDRESS );
instructions.at(pos).data = (unsigned int)instructions.size(); instructions.at(pos).data = (unsigned int)instructions.size();
} }
...@@ -163,6 +164,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { ...@@ -163,6 +164,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
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;
case I_LOOP: ret += _("loop"); 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_MAKE_OBJECT: ret += _("make object");break;
case I_CALL: ret += _("call"); break; case I_CALL: ret += _("call"); break;
case I_CLOSURE: ret += _("closure"); break; case I_CLOSURE: ret += _("closure"); break;
...@@ -212,7 +214,7 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const { ...@@ -212,7 +214,7 @@ 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_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); 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
...@@ -255,7 +257,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) ...@@ -255,7 +257,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
break; break;
case I_JUMP: { case I_JUMP: {
if (instr->data > initial) { 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; return instr + 1;
} }
// there will be a way not to take this jump // there will be a way not to take this jump
...@@ -275,6 +277,11 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) ...@@ -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 // we need to skip two things (iterator+accumulator) instead of one
to_skip += 1; to_skip += 1;
break; 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) { } else if (instr->instr == I_JUMP_IF_NOT && instr->data == after_jump) {
// code looks like // code looks like
// 1 (nettstack+1) // 1 (nettstack+1)
...@@ -293,7 +300,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) ...@@ -293,7 +300,7 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
++instr; // compensate for the -- in the outer loop ++instr; // compensate for the -- in the outer loop
break; 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 return nullptr; // give up
default: default:
break; // nett stack effect 0 break; // nett stack effect 0
......
...@@ -32,16 +32,17 @@ enum InstructionType ...@@ -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_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) , 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! ///< 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 // 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_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 = 10 ///< arg = int, n*var : construct a call closure object with the given arguments , I_CLOSURE = 11 ///< arg = int, n*var : construct a call closure object with the given arguments
// Simple instructions // Simple instructions
, I_UNARY = 11 ///< arg = 1ary instr : pop 1 value, apply a function, push the result , I_UNARY = 12 ///< 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_BINARY = 13 ///< 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_TERNARY = 14 ///< 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_QUATERNARY = 15 ///< 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_DUP = 16 ///< arg = int : duplicate the k-from-top element of the stack
}; };
/// Types of unary instructions (taking one argument from the stack) /// Types of unary instructions (taking one argument from the stack)
...@@ -96,13 +97,13 @@ enum QuaternaryInstructionType ...@@ -96,13 +97,13 @@ enum QuaternaryInstructionType
* Then the instr? member gives the actual instruction to perform * Then the instr? member gives the actual instruction to perform
*/ */
struct Instruction { struct Instruction {
InstructionType instr : 5; InstructionType instr : 6;
union { union {
unsigned int data : 27; unsigned int data : 26;
UnaryInstructionType instr1 : 27; UnaryInstructionType instr1 : 26;
BinaryInstructionType instr2 : 27; BinaryInstructionType instr2 : 26;
TernaryInstructionType instr3 : 27; TernaryInstructionType instr3 : 26;
QuaternaryInstructionType instr4 : 27; QuaternaryInstructionType instr4 : 26;
}; };
}; };
......
...@@ -128,17 +128,17 @@ class ScriptRangeIterator : public ScriptIterator { ...@@ -128,17 +128,17 @@ class ScriptRangeIterator : public ScriptIterator {
public: public:
// Construct a range iterator with the given bounds (inclusive) // Construct a range iterator with the given bounds (inclusive)
ScriptRangeIterator(int start, int end) ScriptRangeIterator(int start, int end)
: pos(start), end(end) {} : pos(start), start(start), end(end) {}
virtual ScriptValueP next(ScriptValueP* key_out) { virtual ScriptValueP next(ScriptValueP* key_out) {
if (pos <= end) { if (pos <= end) {
if (key_out) *key_out = to_script(pos); if (key_out) *key_out = to_script(pos-start);
return to_script(pos++); return to_script(pos++);
} else { } else {
return ScriptValueP(); return ScriptValueP();
} }
} }
private: private:
int pos, end; int pos, start, end;
}; };
ScriptValueP rangeIterator(int start, int end) { ScriptValueP rangeIterator(int start, int end) {
......
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