... | ... | @@ -25,13 +25,13 @@ Gonads") how the powerful concept of |
|
|
"Monad") can be implemented in JavaScript.
|
|
|
|
|
|
So I was wondering whether a decent monadic parser could be written in
|
|
|
CoffeeScript. The result is Comparse, a monadic parsing library, which
|
|
|
is described in the rest of this text. Frankly, I didn't expect to get
|
|
|
something nearly as elegant as the Haskell parsers mentioned above,
|
|
|
primarily because Haskell has special syntactic sugar for monad
|
|
|
programming (the “do” notation). Much to my surprise, monadic
|
|
|
functions and their combinations can be written quite nicely in the
|
|
|
CoffeeScript syntax (but maybe I am just weird and biased).
|
|
|
CoffeeScript. The result is _Comparse_, a monadic parsing
|
|
|
library. Frankly, I didn't expect to get something nearly as elegant
|
|
|
as the Haskell parsers mentioned above, primarily because Haskell has
|
|
|
special syntactic sugar for monadic programming (the “do”
|
|
|
notation). Much to my surprise, monadic functions and their
|
|
|
combinations can be written quite nicely in the CoffeeScript syntax
|
|
|
(but maybe I am just weird and biased).
|
|
|
|
|
|
The implementation takes into account the lack of tail recursion
|
|
|
optimization in JavaScript, so recursive functions are mostly
|
... | ... | @@ -39,6 +39,55 @@ rewritten using loops. Also, since JavaScript is _not_ a pure |
|
|
functional language, side effects and non-local variables are used in
|
|
|
a few places for improving efficiency.
|
|
|
|
|
|
Getting Started
|
|
|
===============
|
|
|
|
|
|
It is not necessary to understand
|
|
|
[monads](http://en.wikipedia.org/wiki/Monad_%28functional_programming%29)
|
|
|
in order to be able to use the _Comparse_ library. It is just
|
|
|
sufficient to remember that a _parser_ constructed with this library
|
|
|
is a function in a context, where the context is simply an offset in
|
|
|
the input string where the function is supposed to start parsing.
|
|
|
|
|
|
The essential pattern in parser construction is connected with the
|
|
|
`bind` method and looks like this:
|
|
|
|
|
|
p.bind f
|
|
|
|
|
|
The `bind` method returns a new parser that applies two parsers
|
|
|
sequentially. The first parser is the receiver `p` of the `bind`
|
|
|
method. However, the argument of `bind`, denoted `f`, is _not_ the
|
|
|
second parser: instead, `f` is a _function_ that returns a parser. Why
|
|
|
is that? It allows us to use the result of `p` in further processing,
|
|
|
and indeed, the argument of `f` is exactly the result of the parser `p`.
|
|
|
|
|
|
The `bind` method works like this:
|
|
|
|
|
|
1. The receiver (parser `p`) is applied first. If it fails (its result
|
|
|
is `null`), then the combined parser returned by `bind` fails as
|
|
|
well.
|
|
|
|
|
|
1. Otherwise, the result of parser `p` is fed as the argument to
|
|
|
function `f` that returns a parser, say `q`. The result of the
|
|
|
combined parser is then the result of `q`.
|
|
|
|
|
|
The argument of `bind`, function `f`, may of course be defined in a
|
|
|
separate assignment. Quite often, however, the argument `f` is
|
|
|
specified inline. The bind expression then looks in the CoffeeScript
|
|
|
syntax like this:
|
|
|
|
|
|
p.bind (res) -> fbody
|
|
|
|
|
|
Of course, the argument function of `bind` may completely ignore the
|
|
|
result of parser `p`. In this case, the `bind` expression can be
|
|
|
written simply as follows:
|
|
|
|
|
|
p.bind -> fbody
|
|
|
|
|
|
Other parser combinators and predefined parsers provided by the
|
|
|
_Comparse_ library are described in the Literate CoffeeScript
|
|
|
[source](comparse).
|
|
|
|
|
|
* [Annotated source code](comparse)
|
|
|
|
|
|
* [Example parsers](examples) |