Safe Haskell | None |
---|---|
Language | Haskell2010 |
Text.Cassette
Description
The combinators of this library are all pairs of functions going in opposite directions. These pairs are called cassettes, sporting two tracks (the two functions), one of which is read in one direction, the other of which (accessed by flipping the cassette) is read in the opposite direction.
Example
Consider the data type for abstract syntax trees of the λ-calculus:
>>>
:{
type Ident = String data Term where Var :: Ident -> Term Abs :: Ident -> Term -> Term App :: Term -> Term -> Term deriving instance Show Term makePrisms ''Term :}
Given a few constructor-wrapping prisms (generated by makePrisms
from the
lens library or otherwise) and lifting them to cassette leads (the
definitions are mechanical), ...
>>>
:{
varL = prismL _Var absL = prismL _Abs . pairL appL = prismL _App . pairL :}
... the concrete syntax for terms of the λ-calculus can be defined as follows:
>>>
:{
term :: PP Term term = varL --> ident <> absL --> char '^' . ident . char '.' . optSpace . term <> appL --> parens (term . sepSpace . term) parens p = char '(' . p . char ')' ident = consL --> letter . many alphaNum :}
From this single specification, we can extract a parser, using
parse
, and also a pretty printer, using pretty
.
>>>
parse term "^x. (x x)"
Just (Abs "x" (App (Var "x") (Var "x")))
>>>
pretty term (Abs "x" (App (Var "x") (Var "x")))
Just "^x. (x x)"
Grammar specifications
Specifications are built from primitive and derived combinators, which affect the input string in some way. For each constructor of each datatype, we need to write a lead, which is a pair of a construction function and a destruction function. Leads are pure combinators that do not affect the input string. By convention, we suffix their name with L. A number of leads for standard data types are defined in the Text.Cassette.Lead module.
Documentation
module Text.Cassette.Char
module Text.Cassette.Combinator
module Text.Cassette.Lead
module Text.Cassette.Number
module Text.Cassette.Prim