This is an example of how to transmogrify a meta-syntax, test.st, into Python and Javascript code.
An 80 minute video shows - in a Twitch-like manner - how this code was built. Since creating the video, the boilerplate code for the Python and Javascript versions has been extended.
./@make creates Python code from test.st.
To see Javascript code, change the line export TARGET="st" to export TARGET="stjs" in @defc and run ./@make.
This is but a simple example, the generated code compiles but does not run (since more surrounding code would be required).
The grammar for the meta-syntax is in st.ohm.
The rewrite rules for converting test.st into Python are in st.rwr.
The rewrite rules for converting test.st into Javascript are in stjs.rwr.
st.ohm is 34 lines of code.
st.rwr is 49 lines of code.
stjs.rwr is 42 lines of code.
Generating Python is the slightly more difficult of the two targets, since Python's indentation syntax requires the use of unicode symbols to specify indents (⤷) and outdents (⤶).
The grammar is written in OhmJS syntax.
The rewrite rules are written in RWR syntax.
The build uses a simple bash script ./@make instead of a Makefile. This calls customizable scripts ./@defc, ./@makec and ./@testc. This build structure is meant for larger PBP projects and is carried over, in a simple manner, into this project.
The transmogrifier code is specialized for this particular example. It is not fully general, but, should serve as a simple, gentle introduction to how to build transmogrifiers using very little code.
A transmogrifier is like a Lisp macro, but used for text-based languages (like Python, etc.). It's akin to a compiler without added type checking. It can be built in only a few minutes/hours and can be used for many purposes and one-off conversions, like using regular expression replacement in various code editors. Using grammars and parsers is a more powerful technique than using regular expressions. Regular expression syntax is more concise (some say unreadable) than Ohm grammars and RWR rewrite rules, but, the grammar approach allows for matching nested and recursive constructs in a straight-forward manner without all of the hoop-jumping required to match such constructs using regular expressions in modern programming languages.
The generated code includes boilerplate code for creating several stack data structures - one stack for container objects and a separate stack for cause objects. The point of this example is to demonstrate techniques - you don't need to understand what containers and causes are, to understand how to use these techniques. These objects are just constructs required for describing the full architecture from which test.st was copied.
In most modern programming languages, one would use pass-through parameters instead of these stacks, i.e parameters that get passed into functions which only pass the same parameter, unused, through to other functions that get called. Pushing the data onto stacks is like wrapping a dynamic scope ("with-...") around certain call chains. This stack-based technique can cause bugs in hand-written code, but is a viable approach for use in generated code.
The "with-..." syntax and dynamic scoping is borrowed from the Lisp world and can be applied to newly-invented meta-syntaxes such as the st syntax shown in this example.