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
> 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);
......
......@@ -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"));
}
key = string_to_variable(name.value);
swap(var,key);
}
// iterator
if (is_each) {
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_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
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(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_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
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")) {
// rgb(r, g, b)
expectToken(input, _("("));
......
......@@ -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
......
......@@ -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) {
......
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