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!












