Tsonnet #17 - Indexing a String: From copy-paste to unification
How Tsonnet learned to index strings
Welcome to the Tsonnet series!
If you're just joining, you can check out how it all started in the first post of the series.
In the previous post, we added support for late binding, and a little bit of Jsonnet's inconsistency rant:
We made array access by index possible, but we are still missing another indexable expr
variant: String.
Let's add index access capability to it.
Getting string characters
The simplest test sample:
local name = "Tsonnet";
name[0]
$ jsonnet samples/strings/get_char_zero.jsonnet
"T"
The index out of bounds case:
local name = "Tsonnet";
name[1234]
The non-indexable index value:
local str = "Tsonnet";
local index = false;
str[index]
The Jsonnet error messages hurt my eyes:
$ jsonnet samples/errors/string_index_out_of_bounds.jsonnet
RUNTIME ERROR: Index 1234 out of bounds, not within [0, 7)
samples/errors/string_index_out_of_bounds.jsonnet:2:1-11 $
During evaluation
$ jsonnet samples/errors/string_index_not_int.jsonnet
RUNTIME ERROR: Unexpected type boolean, expected number
samples/errors/string_index_not_int.jsonnet:3:1-11 $
During evaluation
Let's get this working:
$ dune exec -- tsonnet samples/strings/get_char_zero.jsonnet
samples/strings/get_char_zero.jsonnet:2:0 Expected "Array", found "String"
2: name[0]
^^^^^^^
With a bit of copy and paste from the array implementation, it's easily adaptable. It's just a matter of using the String
equivalent functions accordingly to get the values given an index:
And now it works like a charm. Simple as that:
$ dune exec -- tsonnet samples/strings/get_char_zero.jsonnet
"T"
$ dune exec -- tsonnet samples/errors/string_index_out_of_bounds.jsonnet
samples/errors/string_index_out_of_bounds.jsonnet:2:0 Index out of bounds. Trying to access index 1234 but "name" length is 7
2: name[1234]
^^^^^^^^^^
$ dune exec -- tsonnet samples/errors/string_index_not_int.jsonnet
samples/errors/string_index_not_int.jsonnet:3:0 Expected Integer index, got Bool
3: str[index]
^^^^^^^^^^
The cram tests:
Looking good, but we can make it even better.
The indexable abstraction
There is a lot of duplication that we can encapsulate in a well crafted Indexable
module:
This module provides polymorphic functions (length
, nth
, and get
) that operate on indexable expression types, specifically the Array
and String
variants of the expr
type.
And with that we can remove all the duplicated code in the interpreter:
The code is now cleaner and easier to understand, isn't it?
Conclusion
With string indexing now implemented, Tsonnet can handle both arrays and strings as indexable types. The refactoring into an Indexable
module not only eliminated code duplication but also created a clean abstraction that will make it easier to add support for other indexable types in the future. The error handling remains consistent and informative, helping developers understand what went wrong when things don't work as expected.
The diff with all the changes can be seen here.
There's still something bothering me about testing, but I will leave it as a surprise for the next post.