-
Notifications
You must be signed in to change notification settings - Fork 5
/
Example.lhs
111 lines (79 loc) · 2.77 KB
/
Example.lhs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
Boomerang is a DSL for creating parsers and pretty-printers using a
single specification. Instead of writing a parser, and then writing a
separate pretty-printer, both are created at once. This saves time,
and ensures that the parser and pretty-printer are inverses and stay
in-sync with each other.
Boomerang is a generalized derivative of the Zwaluw library created by
Sjoerd Visscher and Martijn van Steenbergen:
<http://hackage.haskell.org/package/Zwaluw>
Boomerang is similar in purpose, but different in implementation to:
<http://hackage.haskell.org/package/invertible-syntax>
Here is a simple example. First we enable three language extensions:
@ {\-\# LANGUAGE TemplateHaskell, TypeOperators, OverloadedStrings \#-\} @
In the imports, note that we hide @((.), id)@ from 'Prelude' and use
@((.), id)@ from "Control.Category" instead.
> {-# LANGUAGE TemplateHaskell, TypeOperators, OverloadedStrings #-}
> module Main where
>
> import Prelude hiding ((.), id)
> import Control.Category ((.), id)
> import Control.Monad (forever)
> import Text.Boomerang
> import Text.Boomerang.String
> import Text.Boomerang.TH
> import System.IO (hFlush, stdout)
Next we define a type that we want to be able to pretty-print and define parsers for:
> data Foo
> = Bar
> | Baz Int Char
> deriving (Eq, Show)
Then we generate some combinators for the type:
> $(makeBoomerangs ''Foo)
The combinators will be named after the constructors, but with an r prefixed to them. In this case, @rBar@ and @rBaz@.
Now we can define a grammar:
> foo :: StringBoomerang () (Foo :- ())
> foo =
> ( rBar
> <> rBaz . "baz-" . int . "-" . alpha
> )
@.@ is used to compose parsers together. '<>' is used for choice.
Now we can use @foo@ as a printer or a parser.
Here is an example of a successful parse:
> test1 = parseString foo "baz-2-c"
@
*Main> test1
Right (Baz 2 'c')
@
And another example:
> test2 = parseString foo ""
@
*Main> test2
Right Bar
@
Here is an example of a parse error:
> test3 = parseString foo "baz-2-3"
@
*Main> test3
Left parse error at (0, 6): unexpected '3'; expecting an alphabetic Unicode character
@
we can also use @foo@ to pretty-print a value:
> test4 = unparseString foo (Baz 1 'z')
@
*Main> test4
Just "baz-1-z"
@
Here is a little app that allows you to interactively test @foo@.
> testInvert :: String -> IO ()
> testInvert str =
> case parseString foo str of
> (Left e) -> print e
> (Right f') ->
> do putStrLn $ "Parsed: " ++ show f'
> case unparseString foo f' of
> Nothing -> putStrLn "unparseString failed to produce a value."
> (Just s) -> putStrLn $ "Pretty: " ++ s
> main = forever $
> do putStr "Enter a string to parse: "
> hFlush stdout
> l <- getLine
> testInvert l