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 comments to Tsonnet:
Now, let's implement unary operations.
The simplest unary operation
Starting with a new sample Jsonnet file:
Which interprets to โ well, itself:
This is a boring example, but let's stay with it for now. Baby steps!
Let's add the cram test:
We need a new type to represent unary operations:
When it comes to the lexer, I put the cart before the horses earlier, including the minus sign as part of number patterns. However, it can be simplified by applying the -
and +
unary operations:
I renamed the ADD and SUBTRACT tokens to PLUS and MINUS. The ADD and SUBTRACT tokens added semantic meaning, and since +
and -
are used for both unary and binary operations, it makes sense to reduce them to their names rather than their arithmetic semantic meanings.
We don't need to drop the specific types for arithmetic operations, however. In the parser, we can disambiguate them โ the more specific, the better:
By having types for each specific operation, we can make our compiler semantics stronger.
The only thing left now is a pattern match on the new operation in the interpreter:
And voilร :
So far, Tsonnet is missing parentheses to delineate operation precedence. Let's add them.
Parentheses and precedence
For example:
Updating the cram test to include the operation precedence test (and consolidate the operations test in a single file):
The lexer will parse the symbols, and the parser will evaluate the expression wrapped by the symbols and throw away the parentheses:
Result:
So far, so good. But there's more.
To be or not to be
We need to support negation and the bitwise negation, too. It's pretty easy to add them now.
Let's add the sample files and tests:
Now, we add the new unary types and translate the expressions:
Unlike the Plus
and Minus
operations, the Not
and BitwiseNot
operations work on booleans rather than numbers. This meant refactoring interpret_unary_op
to accept an expression, thus moving the error-handling there. But the pattern-matching on UnaryOp could be simplified to a one-liner. It's important to emphasize that we need to evaluate the expression before handing it over to `interpret_unary_op` as this way we can deal with evaluated types, such as Number
or Bool
-- calling interpret
from interpret_unary_op
wouldn't compile since the OCaml compiler requires that definitions should be declared from top to bottom.
Conclusion
With unary operations now fully implemented in Tsonnet, our language has taken another step toward feature completeness. We've added support for plus (+), minus (-), logical not (!), and bitwise not (~) operators, along with proper parentheses for expression grouping.
In the next post, we'll explore adding even more language features to make Tsonnet even more powerful and expressive.