From 7880d73201315b4d35db122590e89fd550210353 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 29 Dec 2018 17:23:31 -0500 Subject: [PATCH] Add some documentation for looping and the loop macro. Also add :pairs verb to the loop macro and some more tests. --- README.md | 2 +- doc/Loop.md | 148 ++++++++++++++++++++++++++++++++++++++++++++ src/core/core.janet | 53 +++++++++++----- src/core/specials.c | 4 ++ test/suite3.janet | 17 +++++ 5 files changed, 208 insertions(+), 16 deletions(-) create mode 100644 doc/Loop.md diff --git a/README.md b/README.md index 7a3db805..906a45b2 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,6 @@ See the examples directory for some example janet code. ## Why Janet -Janet is named after the omniscient and friendly artificial being in [The Good Place](https://en.wikipedia.org/wiki/The_Good_Place). +Janet is named after the almost omniscient and friendly artificial being in [The Good Place](https://en.wikipedia.org/wiki/The_Good_Place). Janet logo diff --git a/doc/Loop.md b/doc/Loop.md new file mode 100644 index 00000000..0c9de3b8 --- /dev/null +++ b/doc/Loop.md @@ -0,0 +1,148 @@ +# Loops in Janet + +A very common and essential operation in all programming is looping. Most +languages support looping of some kind, either with explicit loops or recursion. +Janet supports both recursion and a primitve `while` loop. While recursion is +useful in many cases, sometimes is more convenient to use a explicit loop to +iterate over a collection like an array. + +## An Example - Iterating a Range + +Supose you want to calculate the sum of the first 10 natural numbers +0 through 9. There are many ways to carry out this explicit calculation +even with taking shortcuts. A succinct way in janet is + +``` +(+ ;(range 10)) +``` + +We will limit ourselves however to using explicit looping and no funcitions +like `(range n)` which generate a list of natural numbers for us. + +For our first version, we will use only the while macro to iterate, similar +to how one might sum natural numbers in a language such as C. + +``` +(var sum 0) +(var i 0) +(while (< i 10) + (+= sum i) + (++ i)) +(print sum) # prints 45 +``` +This is a very imperative style program which can grow very large very quickly. +We are manually updating a counter `i` in a loop. Using the macros `+=` and `++`, this +style code is similar in density to C code. +It is recommended to use either macros (such as the loop macro) or a functional +style in janet. + +Since this is such a common pattern, Janet has a macro for this exact purpose. The +`(for x start end body)` captures exactly this behavior of incrementing a counter +in a loop. + +``` +(var sum 0) +(for i 0 10 (+= sum i)) +(print sum) # prints 45 +``` + +We have completely wrapped the imperative counter in a macro. The for macro, while not +very flexible, is very terse and covers a common case of iteration, iterating over an integer range. The for macro will be expanded to something very similar to our original +version with a while loop. + +We can do something similar with the more flexible `loop` macro. + +``` +(var sum 0) +(loop [i :range [0 10]] (+= sum i)) +(print sum) # prints 45 +``` + +This is slightly more verbose than the for macro, but can be more easily extended. +Let's say that we wanted to only count even numbers towards the sum. We can do this +easily with the loop macro. + +``` +(var sum 0) +(loop [i :range [0 10] :when (even? i)] (+= sum i)) +(print sum) # prints 20 +``` + +The loop macro has several verbs (:range) and modifiers (:when) that let +the programmer more easily generate common looping idioms. The loop macro +is similar to the Common Lips loop macro, but smaller in scope and with a much +simpler syntax. As with the `for` macro, the loop macro expands to similar +code as our original while expression. + +## Another Example - Iterating an Indexed Data Structure + +Another common usage for iteration in any language is iterating over the items in +some data structure, like items in an array, characters in a string, or key value +pairs in a table. + +Say we have an array of names that we want to print out. We will +again start with a simple while loop which we will refine into +more idiomatic expressions. + +First, we will define our array of names +``` +(def names @["Jean-Paul Sartre" "Bob Dylan" "Augusta Ada King" "Frida Kahlo" "Harriet Tubman") +``` + +With our array of names, we can use a while loop to iterate through the indices of names, get the +values, and the print them. + +``` +(var i 0) +(def len (length names)) +(while (< i len) + (print (get names i)) + (++ i)) +``` + +This is rather verbose. janet provides the `each` macro for iterating through the items in a tuple or +array, or the bytes in a buffer, symbol, or string. + +``` +(each name names (print name)) +``` + +We can also use the `loop` macro for this case as well using the `:in` verb. + +``` +(loop [name :in names] (print name)) +``` + +## Iterating a Dictionary + +In the previous example, we iterated over the values in an array. Another common +use of looping in a Janet program is iterating over the keys or values in a table. +We cannot use the same method as iterating over an array because a table or struct does +not contain a known integer range of keys. Instead we rely on a function `next`, which allows +us to visit each of the keys in a struct or table. Note that iterating over a table will not +visit the prototype table. + +As an example, lets iterate over a table of letters to a word that starts with that letter. We +will print out the words to our simple children's book. + +``` +(def alphabook + @{"A" "Apple" + "B" "Banana" + "C" "Cat" + "D" "Dog" + "E" "Elephant" }) +``` + +As before, we can evaluate this loop using only a while loop and the `next` function. + +``` +(var key (next alphabook nil)) +(while (not= nil key) + (print key " is for " (get alphabook key)) + (set key (next alphabook key)) +``` + +For ou + + diff --git a/src/core/core.janet b/src/core/core.janet index e0770383..f0680f24 100644 --- a/src/core/core.janet +++ b/src/core/core.janet @@ -165,7 +165,7 @@ (defmacro if-not "Shorthand for (if (not ... " - [condition exp-1 exp-2] + [condition exp-1 exp-2 &] ~(if ,condition ,exp-2 ,exp-1)) (defmacro when @@ -290,6 +290,7 @@ \t:range - loop over a range. The object should be two element tuple with a start and end value. The range is half open, [start, end).\n \t:keys - Iterate over the keys in a data structure.\n + \t:pairs - Iterate over the keys value pairs in a data structure.\n \t:in - Iterate over the values in an indexed data structure or byte sequence.\n \t:generate - Iterate over values yielded from a fiber. Can be paired with the generator function for the producer/consumer pattern.\n\n @@ -376,6 +377,22 @@ (tuple 'def bindings $iter) subloop (tuple 'set $iter (tuple next $dict $iter))))) + :pairs (do + (def sym? (symbol? bindings)) + (def $dict (gensym)) + (def $iter (gensym)) + (def preds @['and (tuple not= nil $iter)]) + (def subloop (doone (+ i 3) preds)) + (tuple 'do + (tuple 'def $dict object) + (tuple 'var $iter (tuple next $dict nil)) + (tuple 'while (tuple/slice preds) + (if sym? + (tuple 'def bindings (tuple tuple $iter (tuple get $dict $iter)))) + (if-not sym? (tuple 'def (get bindings 0) $iter)) + (if-not sym? (tuple 'def (get bindings 1) (tuple get $dict $iter))) + subloop + (tuple 'set $iter (tuple next $dict $iter))))) :in (do (def $len (gensym)) (def $i (gensym)) @@ -809,28 +826,34 @@ ~(let [,sym ,last] (if ,sym ,(tuple/slice parts 0)))) (reduce fop x forms)) +(defn walk-ind [f form] + (def len (length form)) + (def ret (array/new len)) + (each x form (array/push ret (f x))) + ret) + +(defn walk-dict [f form] + (def ret @{}) + (loop [k :keys form] + (put ret (f k) (f form.k))) + ret) + (defn walk "Iterate over the values in ast and apply f to them. Collect the results in a data structure . If ast is not a table, struct, array, or tuple, - behaves as the identity function." + returns form." [f form] - (defn walk-ind [] - (def ret @[]) - (each x form (array/push ret (f x))) - ret) - (defn walk-dict [] - (def ret @[]) - (loop [k :keys form] - (array/push ret (f k) (f form.k))) - ret) (case (type form) - :table (table ;(walk-dict)) - :struct (struct ;(walk-dict)) - :array (walk-ind) - :tuple (tuple ;(walk-ind)) + :table (walk-dict f form) + :struct (table/to-struct (walk-dict f form)) + :array (walk-ind f form) + :tuple (tuple/slice (walk-ind f form)) form)) +(put _env 'walk-ind nil) +(put _env 'walk-dict nil) + (defn post-walk "Do a post-order traversal of a data sructure and call (f x) on every visitation." diff --git a/src/core/specials.c b/src/core/specials.c index 3a40c89b..39511ea1 100644 --- a/src/core/specials.c +++ b/src/core/specials.c @@ -253,6 +253,8 @@ static int varleaf( const uint8_t *sym, JanetSlot s, JanetTable *attr) { + if (sym[0] == ':') + janetc_cerror(c, "cannot create binding to keyword symbol"); if (c->scope->flags & JANET_SCOPE_TOP) { /* Global var, generate var */ JanetSlot refslot; @@ -287,6 +289,8 @@ static int defleaf( const uint8_t *sym, JanetSlot s, JanetTable *attr) { + if (sym[0] == ':') + janetc_cerror(c, "cannot create binding to keyword symbol"); if (c->scope->flags & JANET_SCOPE_TOP) { JanetTable *tab = janet_table(2); janet_table_put(tab, janet_csymbolv(":source-map"), diff --git a/test/suite3.janet b/test/suite3.janet index f22798ea..89855e66 100644 --- a/test/suite3.janet +++ b/test/suite3.janet @@ -53,4 +53,21 @@ (assert (= var-b "hello") "regression 1") +# Some macros + +(assert (= 2 (if-not 1 3 2)) "if-not 1") +(assert (= 3 (if-not false 3)) "if-not 2") +(assert (= 3 (if-not nil 3 2)) "if-not 3") +(assert (= nil (if-not true 3)) "if-not 4") + +(assert (= 4 (unless false (+ 1 2 3) 4)) "unless") + +(def res @{}) +(loop [[k v] :pairs @{1 2 3 4 5 6}] + (put res k v)) +(assert (and + (= (get res 1) 2) + (= (get res 3) 4) + (= (get res 5) 6)) "loop :pairs") + (end-suite)