Tsonnet #21 - Making object definitions more dynamic
Extending Tsonnet's object syntax to support local variable definitions next to object fields.
Welcome to the Tsonnet series!
If you're not following the series so far, you can check out how it all started in the first post of the series.
In the previous post, I started covering the Jsonnet tutorials, starting with the syntax example:
Now I'll cover the variables example:
// A regular definition.
local house_rum = 'Banks Rum';
{
// A definition next to fields.
local pour = 1.5,
Daiquiri: {
ingredients: [
{ kind: house_rum, qty: pour },
{ kind: 'Lime', qty: 1 },
{ kind: 'Simple Syrup', qty: 0.5 },
],
served: 'Straight Up',
},
Mojito: {
ingredients: [
{
kind: 'Mint',
action: 'muddle',
qty: 6,
unit: 'leaves',
},
{ kind: house_rum, qty: pour },
{ kind: 'Lime', qty: 0.5 },
{ kind: 'Simple Syrup', qty: 0.5 },
{ kind: 'Soda', qty: 3 },
],
garnish: 'Lime wedge',
served: 'Over crushed ice',
},
}
Everything should be working with the current implementation, except one thing, the definition next to fields.
So far, objects in Tsonnet are simple JSON objects. Let's make them a bit more dynamic.
Parsing local definitions next to object fields
So far, objects in Tsonnet are simple JSON objects. Let's make them a bit more dynamic.
Let's introduce a new type:
The object_entry
is a new variant type that expresses object entries as both object fields and expressions.. We have two shapes for the entries: ObjectField
for regular key-value pairs, and ObjectExpr
to hold the local definitions.
The and keyword is used to declare mutually recursive types in OCaml -- pretty handy! The other option is to make object_entry
parametric, but we don't need types other than expr
.
Then we can apply it to the parser:
The obj_field_list
rule will now contain not just key-value pairs, but also local expressions. Since they are separated by comma, just like object attributes, in the obj_field
rule we need a new rule, single_var
.
We can't simply use the vars
rule because of shift-reduce issues with the parser -- I might write more about this in another post.
Type checking object entries
First, I need a type to represent object_entry
in the type system:
The t_object_entry
translates object_entry
as-is.
Then we translate each entry:
And check for invalid cycles:
Lastly, the to_string
needs to include the new type:
Interpreting object entries
Previously, the object could be returned as-is, but now we need to interpret the entries:
The JSON representation also needs updates:
The only difference is that during interpretation, the ObjectExpr
is thrown away and needs to be ignored here -- if it is found, it must error, as it's not supposed to reach this far.
The Ident
case has been removed -- it was never meant to be representable in JSON anyway.
Testing the definitions next to fields
The cram test:
I missed adding samples/tutorials/syntax.jsonnet
in the previous post, but it belongs here too.
The magic of property-based testing -- I only had to specify how each object entry variant should be generated:
Bonus round: refactoring specialized interpreting functions
The pattern-matching cases in the interpret function are getting bloated. It's a nice idea to start refactoring some code in specialized functions.
Here's the interpret_array
:
Just like with types, we can have mutually recursive functions. Here we need it to allow interpret_array
to call interpret
and vice-versa.
Remember the interpret_object
function, where I worked around the recursive call by passing the interpret
function as a parameter? As a mutually recursive function, there's no need to pass it as a parameter anymore:
Neat, huh?!
Conclusion
With local definitions now working inside objects, Tsonnet is starting to feel more like the dynamic configuration language it's meant to be. The ability to define variables right alongside object fields opens up possibilities for cleaner, more maintainable configuration files -- no more repeating the same values throughout your objects!
The entire diff can be seen here.
Next up in the series, we'll explore Jsonnet references. The cocktail recipes are getting more sophisticated, and so is our language!