Lists, Hashs, and Loops
UCLA bit more on TCL (yes, yes, I've gotta change the name) last night. Added both lists and hashes to the language. These can be created using a literal syntax, which looks pretty much looks how I described it a few days ago:
set list ["a" "b" "c"] set hash ["a":"1" "b":"2" "c":"3"]
I had a bit of trouble working out the grammar for this, I first went with something that looked a little like the following, where the key of an element is optional but the value is mandatory:
list_or_hash --> "[" "]" \# empty list | "[" ":" "]" \# empty hash | "[" elems "]" \# elements elems --> ((arg ":")? arg)* \# elements of a list or hash arg --> <anything that can be a command argument>
But I think this confused the parser a little, where it was greedily
consuming the key arg and expecting the :
to be present to consume the
value.
So I flipped it around, and now the "value" is the optional part:
elems --> (arg (":" arg)?)*
So far this seems to work. I renamed the two fields "left" and "right", instead of key and value. Now a list element will use the "left" part, and a hash element will use "left" for the key and "right" for the value.
You can probably guess that the list and hash are sharing the same AST types. This technically means that hybrid lists are supported, at least in the grammar. But I'm making sure that the evaluator throws an error when a hybrid is detected. I prefer to be strict here, as I don't want to think about how best to support it. Better to just say either a "pure" list, or a "pure" hybrid.
Well, now that we have collections, we need some way to iterate over
them. For that, I've added a foreach
loop, which looks a bit like the
following:
\# Over lists foreach ["a" "b" "c"] { |elem| echo $elem } \# Over hashes foreach ["a":"1" "b":"2"] { |key val| echo $key " = " $val }
What I like about this is that, much like the if
statement, it's
implemented as a macro. It takes a value to iterate over, and a block
with bindable variables: one for list elements, or two for hash keys and
values. This does mean that, unlike most other languages, the loop
variable appears within the block, rather than to the left of the
element, but after getting use to this form of block from my Ruby days,
I can get use to it.
One fun thing about hashes is that they're implemented using Go's map
type. This means that the iteration order is random, by design. This
does make testing a little difficult (I've only got one at the moment,
which features a hash of length one) but I rarely depend on the order of
hash keys so I'm happy to keep it as is.
This loop is only the barest of bones at the moment. It doesn't support
flow control like break
or continue
, and it also needs to support
streams (I'm considering a version with just the block that will accept
the stream from a pipe). But I think it's a reasonably good start.
I also spend some time today integrating this language in the tool I was building it for. I won't talk about it here, but already it's showing quite a bit of promise. I think, once the features are fully baked, that this would be a nice command language to keep in my tool-chest. But more of that in a later post.