Introduction
As of 10.0, DekiScript supports a single loop construct, "foreach". It is intentionally limited to iterating over a set of data (no unbounded loops) to protect the server, but within that limitation there is remarkable flexibility. This page will show you everything you can do with the foreach loop, and while we're at it we'll cover list and map constructors, which are very closely related.
Much of the information in this article overlaps the "What's New in DekiScript for Lyons" article. The "List and Map Constructors" section is complementary to the KB article "How to do just about everything with Lists and Maps".
The foreach loop
Basics
The basic syntax of foreach is:
foreach (GENERATOR) {
inner loop code
}
The GENERATOR is the expression that defines the loop variable and the iterations. The simplest and most common one is:
foreach (var loopVar in Set) {
inner loop code
}
This isn't too different from what other languages do. There are some important details, though:
- the loop must always declare its own loop variable (so that var must always be there)
- for each iteration, loopVariable is assigned to the next item in set. Set must be a list or map; we'll focus on lists now, and get to maps later.
- You can only iterate over a set. You cannot loop until a condition is true or false, or anything like that (no while loops for now, although you can sort of simulate them). Therefore the loop is always bounded: there is always a predetermined, finite number of iterations.
- You cannot alter the set inside the loop. Well, you can alter it if it is a variable, but it won't affect the operation of the loop.
Let's look at some examples.
Basic syntax:
| | Code | Result | Reason |
| Right | {{ foreach (var i in [ 1, 2, 3 ]) { i .. " "; } }} | 1 2 3 | This is your basic loop with a simple generator. |
| Wrong | {{ var i; foreach (i in [ 1, 2, 3]) { i .. " "; } }} | "var" expected: /content/body/div[2]/div[1]/table[1]/tbody/tr[3]/td[3]/span, line 2, column 13 (click for details)MindTouch.Deki.Script.Compiler.DekiScriptParserException: "var" expected: /content/body/div[2]/div[1]/table[1]/tbody/tr[3]/td[3]/span, line 2, column 13
at MindTouch.Deki.Script.Compiler.Errors.SynErr (System.String origin, Int32 line, Int32 col, Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.SynErr (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expect (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.GeneratorHead (MindTouch.Deki.Script.Expr.DekiScriptGenerator& gen) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ForeachStatement (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.DekiScript () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Parse () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (Location location, System.IO.Stream source) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (System.Xml.XmlElement current, System.Collections.Generic.List`1 list) [0x00000] in <filename unknown>:0 | You must always declare your loop variable inside the generator. |
| Wrong | {{ for (var i in [ 1, 2, 3 ]) { i .. " "; } }} | ")" expected: /content/body/div[2]/div[1]/table[1]/tbody/tr[4]/td[3]/span, line 1, column 6 (click for details)MindTouch.Deki.Script.Compiler.DekiScriptParserException: ")" expected: /content/body/div[2]/div[1]/table[1]/tbody/tr[4]/td[3]/span, line 1, column 6
at MindTouch.Deki.Script.Compiler.Errors.SynErr (System.String origin, Int32 line, Int32 col, Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.SynErr (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expect (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ArgList (MindTouch.Deki.Script.Expr.Location& location, MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Primary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Unary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.MulExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AddExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ConcatExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.RelExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.EqlExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AndExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.OrExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.NullCoalescingExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.TernaryExpression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.DekiScript () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Parse () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (Location location, System.IO.Stream source) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (System.Xml.XmlElement current, System.Collections.Generic.List`1 list) [0x00000] in <filename unknown>:0 | Should be foreach, not for. You will make this mistake at some point, maybe lots of times (or perhaps it's just me). |
The iteration set:
| | Code | Result | Reason |
| Wrong | {{ var set = [ 1, 2, 3 ]; foreach (var i in set) { let set ..= [ i+3 ]; i .. " "; } }} | 1 2 3 | Changing the set inside the loop has no effect on the loop operation. |
| Right | {{ var start = 5; var end = 10; foreach (var i in num.series(start, end)) { i .. " "; } }} | 5 6 7 8 9 10 | Use num.series() to define a simple set of sequential numbers. The C equivalent is: for (var i = start; i < end; i++) |
| Right | {{ var set = { zero:0, one:1 }; foreach (var i in set) { i .. " "; } }} | 0 1 | You can iterate over a map; DekiScript will give you the values of the map. See below for more details. |
Implicit Loop variables: __count and __index
Every foreach loop creates two implicit variables that are valid within the loop: __count and __index (note each of these has exactly two leading underscores). They are subtly different.
__index is the more straightforward of the two. It is always set equal to the current position in the set over which you're iterating.
__count, meanwhile, is the count of times you've actually executed the code inside the loop. That means that __count will not include any iteration that is suppressed due to a where or if clause.
We'll cover where and if and other modifiers in more detail later, but in the meantime here are a couple of straightforward examples to illustrate __count and __index.
| | Code | Result | Reason |
| Right | {{ var myList = [ "A", "b", "C", "d" ]; foreach (var letter in myList where string.isUpper(letter)) <div> "letter=" .. letter; "; __count=" .. __count; "; __index=" .. __index; </div>; }} | letter=A; __count=0; __index=0 letter=C; __count=1; __index=2 | where clause suppresses iterations where letter is not uppercase. __count shows number of iterations that are executed. __index shows position in list of current iteration. |
| Right | {{ <ul> foreach (var i in [ 1,2,3 ]) <li> "Outer: __index="..__index; "; __count="..__count; <ul> foreach (var j in [10,11,12]) <li> "Inner: __index="..__index; "; __count="..__count; </li>; </ul>; </li>; </ul>; }} | - Outer: __index=0; __count=0
- Inner: __index=0; __count=0
- Inner: __index=1; __count=1
- Inner: __index=2; __count=2
- Outer: __index=1; __count=1
- Inner: __index=0; __count=0
- Inner: __index=1; __count=1
- Inner: __index=2; __count=2
- Outer: __index=2; __count=2
- Inner: __index=0; __count=0
- Inner: __index=1; __count=1
- Inner: __index=2; __count=2
| __count and __index are defined for the innermost containing loop. |
break and continue
You can override the program flow from inside the loop with the break and continue statements. These work pretty much exactly like their C counterparts: break immediately exits out of the loop, whereas continue triggers the next loop iteration:
| | Code | Result | Reason |
| Right | {{ foreach (var i in num.series(0,10)) { if (__index >= 5) break; i; <br />; } }} | 0 1 2 3 4
| Process first five elements of a list. You can generally get equivalent functionality with a where clause. |
| Right | {{ foreach (var i in num.series(0,10)) { if (i%2) continue; i; <br />; } }} | 0 2 4 6 8 10
| Print even numbers. There are a zillion ways to do this.
|
Iterating over Maps
Thus far we've focused on iterating over a list. Quite often, however, we wish to iterate over a map (e.g., page.subpages). DekiScript gives you plenty of convenient flexibility here. First, if you use a map as your set, DekiScript will automatically apply map.values() to the map to create a list. This does what you want about 90% of the time (especially when iterating over wiki data structures). The ordering of the list is undefined, however, so if you want to control the order then you'll have to do it yourself:
| | Code | Result | Reason |
| Right | {{ var myMap={key1:"val1",key2:"val2",key0:"val0"}; foreach (var v in myMap) { <div> v </div>; } }} | val1 val2 val0 | The generator automatically applies map.values() to a map. Output sequence is undefined. |
| Right | {{ var myMap={key1:"val1",key2:"val2",key0:"val0"}; foreach (var v in map.values(myMap)) { <div> v </div>; } }} | val0 val1 val2 | This is equivalent to the above, but the sequence is different (if you want to know the sequence, control it!) |
| Right | {{ var myMap={key1:"val1",key2:"val2",key0:"val0"}; foreach (var v in list.sort(map.values(myMap))) { <div> v </div>; } }} | val0 val1 val2 | Forcing ascending order. |
If you want to iterate over the keys and values in the map, there's a nifty shortcut generator notation:
foreach (var key:value in myMap)
This essentially defines two loop variables at once, for the key and value of each element in the map. Once again, the sequence is undefined.
| | Code | Result | Reason |
| Right | {{ var myMap={key1:"val1",key2:"val2",key0:"val0"}; foreach (var key:value in myMap) { <div> key..":"..value </div>; } }} | key1:val1 key2:val2 key0:val0 | Iterating over the key and value of each map element. |
| Wrong | {{ var myList =["val1", "val2", "val0"]; foreach (var key:value in myList) { <div> key..":"..value </div>; } }} | list is not valid; expected map or nil: /content/body/div[2]/div[4]/div[2]/table/tbody/tr[3]/td[3]/span, line 2, column 13 (click for details)Callstack:
at en/docs/DekiScript/Beginner's_Guide/Loops_and_Constructors
MindTouch.Deki.Script.Runtime.DekiScriptBadTypeException: list is not valid; expected map or nil: /content/body/div[2]/div[4]/div[2]/table/tbody/tr[3]/td[3]/span, line 2, column 13
at MindTouch.Deki.Script.Compiler.DekiScriptGeneratorEvaluation.Visit (MindTouch.Deki.Script.Expr.DekiScriptGeneratorForeachKeyValue expr, DekiScriptGeneratorEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Expr.DekiScriptGeneratorForeachKeyValue.VisitWith[DekiScriptGeneratorEvaluationState,Empty] (IDekiScriptGeneratorVisitor`2 visitor, DekiScriptGeneratorEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptGeneratorEvaluation.Generate (MindTouch.Deki.Script.Expr.DekiScriptGenerator expr, MindTouch.Deki.Script.Compiler.DekiScriptEval callback, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptExpressionEvaluation.Visit (MindTouch.Deki.Script.Expr.DekiScriptForeach expr, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Expr.DekiScriptForeach.VisitWith[DekiScriptExpressionEvaluationState,Range] (IDekiScriptExpressionVisitor`2 visitor, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptExpressionEvaluation.Visit (MindTouch.Deki.Script.Expr.DekiScriptSequence expr, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Expr.DekiScriptSequence.VisitWith[DekiScriptExpressionEvaluationState,Range] (IDekiScriptExpressionVisitor`2 visitor, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptExpressionEvaluation.Visit (MindTouch.Deki.Script.Expr.DekiScriptReturnScope expr, DekiScriptExpressionEvaluationState state) [0x00000] in <filename unknown>:0 | If you use this notation, make sure your set is really a map! |
Modifiers
Here's where things get fun. You can tack on modifiers to the end of your generator to add functionality. Let's have a look.
Nested loops
DekiScript lets you create nested loops all within the confines of a single generator. Just add a comma and another var loopVariable in set to create an inner loop:
| | Code | Result | Reason |
| Right | {{ foreach (var i in [0,1,2], var j in ["a","b","c"]) <div> i.." "..j </div>; }} | 0 a 0 b 0 c 1 a 1 b 1 c 2 a 2 b 2 c | Equivalent to foreach (var i in [0,1,2]) {
foreach (var j in ["a","b","c"] {
<div> i.." "..j </div>;
}
} |
| Right | {{ foreach (var i in [0,1,2], var j in [i+10,i+20,i+30]) <div> i.." "..j </div>; }} | 0 10 0 20 0 30 1 11 1 21 1 31 2 12 2 22 2 32 | "Inner" loops can see the "outer" loop variables, as you would expect. |
| Right | {{ foreach (var i in [0,1,2], var j in ["a","b","c"]) <div> i.." "..j; "; __count=" .. __count; "; __index=" .. __index; </div>; }} | 0 a; __count = 0; __index = 0 0 b; __count = 1; __index = 1 0 c; __count = 2; __index = 2 1 a; __count = 3; __index = 0 1 b; __count = 4; __index = 1 1 c; __count = 5; __index = 2 2 a; __count = 6; __index = 0 2 b; __count = 7; __index = 1 2 c; __count = 8; __index = 2 | Inside the loop, __count reflects the number of iterations of the entire loop, while __index reflects the position in innermost set. There's no way to access the __index variable for anything but the innermost loop. (or is there?) |
| Right | {{ foreach (var i in [0,1], var j in [0,1], var k in [0,1], var l in [0,1]) <div> i..j..k..l; </div>; }} | 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 | Next as many loops as you like! Here's a delightfully stupid way to build a 4-bit binary counter. |
Variable Assignments
Once you add a comma to the end of the generator, you can also add variable assignments. Why put them here instead of properly inside the loop? In fact, there's often there's no advantage, especially with general-purpose foreach loops. However, this becomes very useful in list and map constructors, so we'll cover it now, but you'll have to wait until later to make best use of it.
Let's say I want to process all the files attached to all of my subpages. I could then do this:
foreach (var p in page.subpages, var files = map.values(p.files)) {
// process files
}
Again, I could just as easily put that assignment inside the loop, but trust me that when it comes to list and map constructors this will come in handy.
Note that I can string as many assignments out there as I want:
foreach (var p in page.subpages, var files = map.values(p.files), var numFiles = #files) {
// process files
}
This is exactly equivalent to:
foreach (var p in page.subpages) {
var files = map.values(p.files);
var numFiles = #files;
// process files
}
One nifty use of this is to regain access __index variables from outer loops:
| | Code | Result | Reason |
| Right | {{ foreach (var i in [0,1,2], var i_index = __index, var j in ["a","b","c"]) { <div> i.." "..j; "; __count=" .. __count; "; i_index=" .. i_index; "; __index=" .. __index; </div>; } }} | 0 a; __count=0; i_index=0; __index=0 0 b; __count=1; i_index=0; __index=1 0 c; __count=2; i_index=0; __index=2 1 a; __count=3; i_index=1; __index=0 1 b; __count=4; i_index=1; __index=1 1 c; __count=5; i_index=1; __index=2 2 a; __count=6; i_index=2; __index=0 2 b; __count=7; i_index=2; __index=1 2 c; __count=8; i_index=2; __index=2 | Setting i_index inside the generator gives us access to the __index variable of the outer loop. |
where and if
We already got a taste of where in an example above, now let's formalize it. Add where condition to limit which iterations are processed. The loop will simply skip over any iteration where the condition is false. __count will increment only on iterations where the condition is true, but __index will always increment, reflecting the position in the set.
OK, that's a bunch of gobbledygook. Examples tell all:
| | Code | Result | Reason |
| Right | {{ foreach (var i in num.series(0,20) where i%2 == 0) { "i="..i; "; __index="..__index; "; __count="..__count; <br />; } }} | i=0; __index=0; __count=0 i=2; __index=2; __count=1 i=4; __index=4; __count=2 i=6; __index=6; __count=3 i=8; __index=8; __count=4 i=10; __index=10; __count=5 i=12; __index=12; __count=6 i=14; __index=14; __count=7 i=16; __index=16; __count=8 i=18; __index=18; __count=9 i=20; __index=20; __count=10
| Execute even numbers. The where clause is inside the loop context, so the loop variable is accessible here (good thing!) |
| Right | {{ var set = num.series(0,20); foreach (var i in set where __index < 5) { i; <br />; } }} | 0 1 2 3 4
| Easy way to show the first n items of a list or map. __index is available for the condition. |
| Right | {{ var set = num.series(0,20); foreach (var i in set where __index >= #set-5) { i; <br />; } }} | 16 17 18 19 20
| Iterate over the last n items of a list or map. |
If is almost the same as where; the only difference is that it must be preceded by a comma. This frees it to go anywhere after loop specifier, so you can put it after some variable assignments. This is useful on occasion when an extra variable assignment or two makes the if condition cleaner or more efficient:
| | Code | Result | Reason |
| Right | {{ "what's a good example???"; }} | what's a good example??? | What's a good example here? |
Putting it all together
When you put these elements together, you end up with the ability to put most of your loop code right inside the generator. It's not always a good idea, but you can:
// In a group of subpages, list the authors of all comments posted within the last day
foreach (var p in page.subpages, // Loop over subpages
var pageName = p.title,
var c in p.comments where date.diffdays(date.now, c.date) <= 1, // inner loop over new page comments
var userName = c.author.name,
var commentNum = __index+1)
{
<div> web.link(p.uri, "Comment "..commentNum.." on "..pageName.." by "..userName) </div>;
}
List and Map Constructors
List and map constructors were first introduced in the Lyons release of Mindtouch, and provide an efficient and powerful method to build lists and maps. They are very closely related to foreach loops, because they use the same generators.
The first question that comes to mind is: why use constructors to build maps or lists?
- The code is compact and elegant
- They execute more efficiently
- They can be more easily embedded as an expression
Once you start using them you will like them, so let's get started right now.
Basic List Constructor Syntax
Here's your basic list constructor:
var myList = [ PRODUCTION foreach GENERATOR ];
The production is simply the value of each element in the constructed list. In terms of its behavior, this is exactly the same as:
var myList = [];
foreach (GENERATOR) {
let myList ..= [ PRODUCTION ];
}
And that's it. As you can see, the constructor code is much cleaner; the fact that it's also more efficient is a bonus. We also said above that the constructors can more easily be embedded as an expression, as in this example to create a sorted list of subpage id's:
var mySortedList = list.sort(
[ p.id foreach var p in page.subpages ] // Construct list of page IDs
);
Here we create the list right on the spot, with no need to assign it to a temp variable or anything. Of course, there are other ways to accomplish this particular trivial task, but it works for illustration.
Some examples:
| | Code | Result | Reason |
| Right | {{ [ i*2 foreach var i in num.series(0,9) ] }} | [ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18 ] | Some even numbers again |
| Wrong | {{ [ i*2 foreach i in num.series(0,10) ]; }} | "var" expected: /content/body/div[3]/div[1]/table/tbody/tr[3]/td[3]/span, line 1, column 15 (click for details)MindTouch.Deki.Script.Compiler.DekiScriptParserException: "var" expected: /content/body/div[3]/div[1]/table/tbody/tr[3]/td[3]/span, line 1, column 15
at MindTouch.Deki.Script.Compiler.Errors.SynErr (System.String origin, Int32 line, Int32 col, Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.SynErr (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expect (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.GeneratorHead (MindTouch.Deki.Script.Expr.DekiScriptGenerator& gen) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.List (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Literal (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Primary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Unary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.MulExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AddExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ConcatExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.RelExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.EqlExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AndExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.OrExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.NullCoalescingExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.TernaryExpression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.DekiScript () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Parse () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (Location location, System.IO.Stream source) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (System.Xml.XmlElement current, System.Collections.Generic.List`1 list) [0x00000] in <filename unknown>:0 | Don't forget the var! |
| Wrong | {{ [ i*2 foreach (var i in num.series(0,10)) ]; }} | "var" expected: /content/body/div[3]/div[1]/table/tbody/tr[4]/td[3]/span, line 1, column 15 (click for details)MindTouch.Deki.Script.Compiler.DekiScriptParserException: "var" expected: /content/body/div[3]/div[1]/table/tbody/tr[4]/td[3]/span, line 1, column 15
at MindTouch.Deki.Script.Compiler.Errors.SynErr (System.String origin, Int32 line, Int32 col, Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.SynErr (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expect (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.GeneratorHead (MindTouch.Deki.Script.Expr.DekiScriptGenerator& gen) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.List (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Literal (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Primary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Unary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.MulExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AddExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ConcatExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.RelExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.EqlExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AndExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.OrExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.NullCoalescingExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.TernaryExpression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.DekiScript () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Parse () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (Location location, System.IO.Stream source) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (System.Xml.XmlElement current, System.Collections.Generic.List`1 list) [0x00000] in <filename unknown>:0 | No parentheses around the generator, unlike the normal foreach loop. |
| Right | {{ [ { n: i, nSquared: i*i } foreach var i in [ 3, 4, 5 ] ]; }} | [ { n : 3, nSquared : 9 }, { n : 4, nSquared : 16 }, { n : 5, nSquared : 25 } ] | The production can be anything, including a map. |
| Right | {{ [ [ j foreach var j in num.series(0, i) ] foreach var i in num.series(0,4) ]; }} | [ [ 0 ], [ 0, 1 ], [ 0, 1, 2 ], [ 0, 1, 2, 3 ], [ 0, 1, 2, 3, 4 ] ] | Nesting constructors is fine. |
Map Constructors
Map constructors are nearly identical to list constructors, except they (duh) construct a map. So, we surround the constructor with curly braces, and the production must be a map element:
var myMap = { PRODUCTION foreach GENERATOR };
Again, this is equivalent to:
var myMap = {};
foreach (var loopVariable in set) {
let myMap ..= { PRODUCTION };
}
The production is the usual key:value pair. Here's a simple example:
var myMap = { (p.id) : p foreach var p in page.subpages };
This will create a map of a page's subpages that can be indexed by their id (rather than by title). Note that the key is wrapped in parentheses to ensure it is evaluated as a DekiScript expression. All of your map constructors will use this notation.
Examples:
| | Code | Result | Reason |
| Wrong | {{ { i.."_squared" : i*i foreach var i in [ 1,2,3,4,5 ] } }} | ":" expected: /content/body/div[3]/div[2]/table/tbody/tr[2]/td[3]/span, line 1, column 4 (click for details)MindTouch.Deki.Script.Compiler.DekiScriptParserException: ":" expected: /content/body/div[3]/div[2]/table/tbody/tr[2]/td[3]/span, line 1, column 4
at MindTouch.Deki.Script.Compiler.Errors.SynErr (System.String origin, Int32 line, Int32 col, Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.SynErr (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expect (Int32 n) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Field (MindTouch.Deki.Script.Expr.FieldConstructor& field) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Map (MindTouch.Deki.Script.Expr.Location& location, MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Literal (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Primary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Unary (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.MulExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AddExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.ConcatExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.RelExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.EqlExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.AndExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.OrExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.NullCoalescingExpr (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.TernaryExpression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Expression (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Statements (MindTouch.Deki.Script.Expr.DekiScriptExpression& expr) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.DekiScript () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.Parser.Parse () [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (Location location, System.IO.Stream source) [0x00000] in <filename unknown>:0
at MindTouch.Deki.Script.Compiler.DekiScriptParser.Parse (System.Xml.XmlElement current, System.Collections.Generic.List`1 list) [0x00000] in <filename unknown>:0 | A simple map constructor. The key value in the production must be wrapped in parentheses! |
| Right | {{ { (i.."_squared"): i*i foreach var i in [ 1,2,3,4,5 ] } }} | { "1_squared" : 1, "2_squared" : 4, "3_squared" : 9, "4_squared" : 16, "5_squared" : 25 } |
Modifiers
Constructors support the exact generators as foreach loops, so you can use all the same modifiers. Here is also where it becomes very useful to put additional variable assignments inside the foreach. Here's a real-world example to illustrate this. This is an actual map constructor from the Tableparse extension (slightly reformatted for presentation here). I was amazed when it actually worked!
{
(k): (type == "both" ? d : d[type])
foreach var k in map.keys($format),
var f = $format[k],
if (f is list && f[0] is num && f[1] is num),
var d = tbl[num.int(f[0])][num.int(f[1])],
var type = list.contains(["text","xml","both"],f[2]) ? f[2] : "text"
}
Here, setting the d and type variables at the end of the generator keeps the production from being a giant mess.
The astute reader might notice that there's a slightly cleaner way to write the above constructor:
{
(k): (type == "both" ? d : d[type])
foreach var k:f in $format
where (f is list && f[0] is num && f[1] is num),
var d = tbl[num.int(f[0])][num.int(f[1])],
var type = list.contains(["text","xml","both"],f[2]) ? f[2] : "text"
}
Odds and Ends
Simulating a while loop
We said at the beginning that there is no while loop in DekiScript. Fortunately, you really don't need them very often. If you really do need them, you can fake it like this:
foreach (var i in num.series(0,largeNumber)) {
if (!condition) break;
// inner loop code
}
Which does almost the same thing as:
while (condition) {
// inner loop code
}
Two important notes:
- largeNumber is some arbitrary value designed to place and upper bound on the number of loop iterations. It can be tricky to get this number right: too small and you may run out of iterations before the condition fails; too large and your page performance will suffer. So try to set is as large as necessary but no larger.
- We chose to apply the condition using an if() break statement rather than a where or if clause in the generator. Because largeNumber might be much larger than the number of iterations that satisfy the condition, we want to break out of the loop as soon as we're finished. Remember that a where or if is evaluated on every single iteration, so that might be a lot of wasted processing when we already know we're finished.
Here's a little example to illustrate: let's generate all Fibonacci numbers less than 100. We don't really know how many iterations we're going to need for that, but we certainly know it'll be less than 100. So:
| | Code | Result | Reason |
| Right | {{ var f1 = 0; var f2 = 1; foreach (var i in num.series(0,100)) { if (f2 >= 100) break; <div> f2 </div>; var newF = f1 + f2; let f1 = f2; let f2 = newF; } }} | 1 1 2 3 5 8 13 21 34 55 89 | Just display them. This works fine. |
| Right | {{ var fibList = [ 1, 1 ]; foreach (var i in num.series(2,100)) { var newF = fibList[i-1]+fibList[i-2]; if (newF >= 100) break; let fibList ..= [ newF ]; } fibList; }} | [ 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 ] | This is a reasonable way to generate a list of Fibonacci numbers. We didn't use a constructor because - you can't use let statements inside the generator (so we couldn't replicate the algorithm in the previous example)
- it's not possible to reference the list as you're building it, so this algorithm wouldn't have worked.
Can you think of a way to build a list of Fibonacci numbers using a list constructor? |