-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
119 lines (103 loc) · 3.06 KB
/
Main.hs
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
module Main (main) where
import Data.Char (isSpace)
import Data.List.NonEmpty (NonEmpty ((:|)), toList)
import Harness (tests)
import Parss
import System.Exit (exitFailure, exitSuccess)
import Test.HUnit (Test, errors, failures, runTestTT)
import Text.Read (readMaybe)
main :: IO ()
main = do
counts <- runTestTT testProgram
if errors counts + failures counts == 0 then exitSuccess else exitFailure
testProgram :: Test
testProgram =
tests
parseProgram
[ ("", ExpErr ExpectedTree ("", "", "")),
(" ", ExpErr ExpectedTree ("", "", " ")),
("(", ExpErr ParenNotClosed ("", "(", "")),
("(()", ExpErr ParenNotClosed ("", "(()", "")),
("x", ExpErr ExpectedNum ("", "x", "")),
("()", ExpErr BadOp ("", "()", "")),
("(())", ExpErr ExpectedOp ("", "(())", "")),
("(*)", ExpErr UndefinedOp ("(", "*", ")")),
( "(+ 1 2)",
Add
[ Num 1 (reverse "(+ ", "1", " 2)"),
Num 2 (reverse "(+ 1 ", "2", ")")
]
(reverse "", "(+ 1 2)", "")
),
( "(+ 1 (- 2 3))",
Add
[ Num 1 (reverse "(+ ", "1", " (- 2 3))"),
Sub
[ Num 2 (reverse "(+ 1 (- ", "2", " 3))"),
Num 3 (reverse "(+ 1 (- 2 ", "3", "))")
]
(reverse "(+ 1 ", "(- 2 3)", ")")
]
(reverse "", "(+ 1 (- 2 3))", "")
)
]
type Match = String
type Source = (String, String)
type Loc = (String, String, String)
data Err
= ParenNotClosed
| ExpectedTree
| ExpectedNum
| BadOp
| ExpectedOp
| UndefinedOp
deriving (Eq, Show)
data Exp
= Num Int Loc
| Add [Exp] Loc
| Sub [Exp] Loc
| ExpErr Err Loc
deriving (Eq, Show)
parseProgram :: String -> Exp
parseProgram = parseExp . parse parseTree . ("",)
parseExp :: Tree -> Exp
parseExp = \case
Word s loc ->
case readMaybe $ toList s of
Just n -> Num n loc
Nothing -> ExpErr ExpectedNum loc
Parens trees loc ->
case trees of
op : args -> case op of
Word op opLoc -> case op of
'+' :| "" -> Add (map parseExp args) loc
'-' :| "" -> Sub (map parseExp args) loc
_ -> ExpErr UndefinedOp opLoc
_ -> ExpErr ExpectedOp loc
_ -> ExpErr BadOp loc
TreeErr err loc -> ExpErr err loc
data Tree
= Word (NonEmpty Char) Loc
| Parens [Tree] Loc
| TreeErr Err Loc
deriving (Eq, Show)
parseTree :: Parser Match Source Tree
parseTree = try parseSubTree |> locate (pure $ TreeErr ExpectedTree)
parseSubTree :: Parser Match Source (Maybe Tree)
parseSubTree = do
skipSpace
try parseParens <|> parseWord
parseParens :: Parser Match Source (Maybe Tree)
parseParens = locateM . fallible $ do
need $ try $ is '('
trees <- ok $ many parseSubTree
ok skipSpace
fallback (TreeErr ParenNotClosed) $ try $ is ')'
pure $ Parens trees
parseWord :: Parser Match Source (Maybe Tree)
parseWord = locateM . fallible $ do
chars <- need . some . try . satisfy $ \char ->
not (isSpace char) && char `notElem` "()"
pure $ Word chars
skipSpace :: Parser Match Source String
skipSpace = many $ try $ satisfy isSpace