Some brief notest on ML
ML is a macro processor. Like M4 it processes any type of file. Unlike these it is not line orientated and not character based. It processes groups of characters as a group (called "atoms" in the docs). In essence it looks for patterns of them (described as "delimiters" and "arguments") and replaces whichever ones you tell it to. It will then (depending on what you tell it to do) go over the replacement text and replace any macro calls it finds (or not, if you tell it to skip over it). Atoms can be single characters including punctuation.
An m4 macro def looks like a programming language function definition and its call much like a programming language function call, sometimes with parameters.
ML is much more free format. Once it finds the first macro delimiter (IE the macro name, which can be multiple atoms) it then looks for the argument (which can also be multi atom) and the next delimiter and so on, then replaces it by the what follows "AS" in the definition (including the start and end markers, or not as the case may be).
This flexibility does mean that you need to tell it what the "skip" and "insert" atoms (1 or more characters) are to begin with. That means you can process virtually any kind of file contents. The patterns can internally repeat and things like counts of the the number of delimiters and arguments are held in internal (accessible) variables. Including setting up the insert and skip delimiters it can do an infix -> Polish notation conversion in 4 lines.
Downsides. AFAIK its 1 character variable names and the Windows version can only chew on 5 input files at at time (I/O for different OS's is described in different appendices of the user manual, which are separate files). It's flow control is more GOTO/label than anything modern (it dates from the late 60's). It's graph node notation is especially tricky to get used to and it's case sensitive on input, which is starting to look like the big low level design mistake of otherwise excellent languages and tools.
The big one is it's so flat out different from M4 you may have trouble getting your head round it. Effectively you draw a template of what it has to look for. Normally it then takes what it finds in the gaps and drops that into whatever you have defined as the output. I'd say you can come close to re-modelling virtually any language into any other with it if you can figure out how to tell it what to do with the input.
And it's interactive by default, so you can play around quite a lot.
Bottom line. Lots of opportunity for mayhem.