A few years ago I wrote a code generator for Jsonnet (crdsonnet [1]) and found templating insufficient. This lead me to create a JSON Schema for the language along with a library[2] that can manifest the expressions reliably. I was unaware that this was an intermediate representation (in the world of interpreters). Fun fact: while cdrsonnet uses astsonnet to generate code, astsonnet is partially generated by crdsonnet.
Last year I got curious and wrote a parser, first without a lexer (I didn't knew I needed it) and later with a lexer (Oh, did my code got way more manageable). The code gets parsed into this intermediate representation, with other words using JSON Schema as a type system for Jsonnet.
This year I got curious again and tried to execute the code from within Jsonnet as well, past weekend I finally reached a point where I could reliably execute most of the test cases from the go-jsonnet repository.
It was an interesting learning experience and it gave me a much deeper understanding of how programming languages work in general.