In addition to these grammars, we will also allow block comments like {- this is a comment -}.

We will parse these grammars into an internal representation (abstract syntax tree). (In some applications you skip the internal representation and go straight to evaluation or code generation or... but let's fix just one goal here.) Here is an internal representation:

2.2 Make token parser

When we pass the above record to

makeTokenParser

, the return value is a record of type

TokenParser

. Its fields are little parsers that we can use. They parse and return various tokens (identifiers, operators, reserved things, all sorts of brackets) and skip comments as we have specified. They also eat whitespaces after the tokens. Using them, our expression parser and statement parser can be written at the token level and without worrying about whitespaces. The only caveat: they don't eat whitespaces at the very, very, very beginning, and we have to do that ourselves, but even that is made easy.
There are only a few of the many provided parsers we will use here. Using record pattern matching, we call

We give operator precedence (by ordering them in the table), association, "semantic action", and how to parse an "atomic term" including the parenthesized case (the only place we do a recursive descent, and it's trivial). It is possible to place several operators at the same precedence, though not shown here. The general format for the table is hard to explain but easy to illustrate and copy.

2.4 Statement parser

The statement parser is best done by recursive descent, with special treatment to the semicolon-separated list (easy with

m_semiSep1

), utilizing the handy token parsers and the expression parser. We also remember to skip whitespaces just once at the very, very, very beginning: