-
Notifications
You must be signed in to change notification settings - Fork 35
/
PrettyPrinting0.lhs
163 lines (140 loc) · 5.87 KB
/
PrettyPrinting0.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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
[[pretty-printing]]
= Pretty printing
Here is a pretty printer for the parser version 0.
It uses Text.PrettyPrint module. I think this module is a bit out of
date these days, it would be nice to update this code to a more
fashionable pretty printing library. I haven't put much
comments/explanation here.
> module PrettyPrinting0 where
> import Prelude hiding ((<>))
> import SimpleSQLQueryParser0 (ValueExpr(..), QueryExpr(..), TableRef(..)
> ,JoinType(..), JoinCondition(..))
> import qualified SimpleSQLQueryParser0 as S
> import Text.PrettyPrint (render, vcat, text, (<>), (<+>), empty, parens,
> nest, Doc, punctuate, comma, sep, quotes,
> doubleQuotes, hsep)
> import Data.Maybe (maybeToList, catMaybes)
> import FunctionsAndTypesForParsing
> --import Text.Parsec.String.Combinator (eof)
> import qualified Test.HUnit as H
> import Text.Parsec (parse, ParseError)
> import Control.Applicative ((<$>),(<*), (*>),(<*>), (<$), (<|>), many)
> --import Text.Parsec.String (Parser)
The basic concept in this pretty printer is we convert the ast into
`Doc` values, then convert these into a string. We have a bunch of
functions to convert `String` to `Doc`s, and combine these docs with
different layouts.
By using a pretty printer library, we can get human readable source
very easily compared with trying to convert directly to strings
ourselves.
== api
> prettyQueryExpr :: QueryExpr -> String
> prettyQueryExpr = render . queryExpr
== value expressions
> valueExpr :: ValueExpr -> Doc
> valueExpr (StringLit s) = quotes $ text s
> valueExpr (NumLit i) = text $ show i
> valueExpr (Iden s) = text s
> valueExpr (DIden q i) = text q <> text "." <> text i
> valueExpr Star = text "*"
> valueExpr (DStar q) = text q <> text "." <> text "*"
> valueExpr (App f es) = text f <> parens (commaSep $ map valueExpr es)
> valueExpr (PrefOp op e) = sep[text op, valueExpr e]
> valueExpr (BinOp e0 op e1) = sep [valueExpr e0, text op, valueExpr e1]
> valueExpr (Case test whens els) =
> sep [text "case" <+> maybe empty valueExpr test
> ,nest 5 $ sep (map wh whens
> ++ [maybe empty
> (\e -> text "else" <+> valueExpr e)
> els])
> ,text "end"]
> where wh (w,t) = sep [text "when" <+> valueExpr w
> ,text "then" <+> valueExpr t]
> valueExpr (Parens e) = parens $ valueExpr e
== query expressions
> queryExpr :: QueryExpr -> Doc
> queryExpr (Select sl fr wh gb hv ob) = sep
> [text "select" <+> nest 7 (commaSep $ map selectItem sl)
> -- from
> ,ml fr $ \f -> text "from" <+> nest 7 (commaSep $ map tref f)
> -- where
> ,me wh $ \w -> text "where" <+> nest 6 (valueExpr w)
> -- group by
> ,ml gb $ \g -> text "group by" <+> nest 9 (commaSep $ map valueExpr g)
> -- having
> ,me hv $ \h -> text "having" <+> nest 6 (valueExpr h)
> -- order by
> ,ml ob $ \o -> text "order by" <+> nest 9 (commaSep $ map valueExpr o)
> ]
> where
> selectItem (e,a) = valueExpr e <+> me a (\a' -> text "as" <+> text a')
>
> tref (TRSimple t) = text t
> tref (TRParens t) = parens $ tref t
> tref (TRAlias t a) = tref t <+> text "as" <+> text a
> tref (TRQueryExpr q) = parens $ queryExpr q
> tref (TRJoin t0 jt t1 jc) = sep
> [tref t0
> ,joinName jt jc <+> tref t1
> ,case jc of
> Just (JoinOn e) -> text "on" <+> valueExpr e
> Just (JoinUsing is) -> text "using" <+> parens (commaSep $ map text is)
> Just JoinNatural -> empty
> Nothing -> empty]
> joinName jt jc =
> hsep [case jc of
> Just JoinNatural -> text "natural"
> _ -> empty
> ,case jt of
> JoinInner -> text "inner join"
> JoinCross -> text "cross join"
> JoinLeft -> text "left join"
> JoinRight -> text "right join"
> JoinFull -> text "full join"]
> me e r = maybe empty r e
> ml [] _ = empty
> ml l r = r l
== helpers
> commaSep :: [Doc] -> Doc
> commaSep = sep . punctuate comma
Have a look at the haddock for this module and see if you can work out
how the code above works.
```
*PrettyPrinting0> either (error . show) valueExpr (parseWithEof (S.valueExpr []) "a and b")
a and b
*PrettyPrinting0> either (error . show) queryExpr (parseWithEof S.queryExpr "select a from t inner join u using(a,b)")
select a from t inner join u using (a, b)
```
== tests
Now we can do some tests: we take the previous test data, and for each
test add an additional test which pretty prints then parses the
results to see that it is unchanged.
> makeTest :: (Eq a, Show a) =>
> (String -> Either ParseError a)
> -> (a -> String)
> -> (String,a)
> -> H.Test
> makeTest parser pretty (src,expected) = H.TestLabel src $ H.TestCase $ do
> let gote = parser src
> case gote of
> Left e -> H.assertFailure $ show e
> Right got -> do
> H.assertEqual src expected got
> let prsql = pretty got
> gotpretty = parser prsql
> case gotpretty of
> Left e -> H.assertFailure $ "pretty: " ++ prsql ++ "\n" ++ show e
> Right gotp -> H.assertEqual ("pretty: " ++ prsql) expected gotp
TODO: fix parsing issue
```
*PrettyPrinting0> H.runTestTT $ H.TestList $ map (makeTest S.parseQueryExpr (render . queryExpr)) S.allQueryExprTests
Cases: 26 Tried: 26 Errors: 0 Failures: 0
Counts {cases = 26, tried = 26, errors = 0, failures = 0}
*PrettyPrinting0> H.runTestTT $ H.TestList $ map (makeTest S.parseValueExpr (render . valueExpr)) S.allValueExprTests
### Failure in: 34:case when a=1 then 2 when a=3 then 4 else 5 end
(line 1, column 11):
unexpected "a"
expecting "--" or "/*"
Cases: 35 Tried: 35 Errors: 0 Failures: 1
Counts {cases = 35, tried = 35, errors = 0, failures = 1}
```