-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathParser.hs
More file actions
221 lines (178 loc) · 6.74 KB
/
Copy pathParser.hs
File metadata and controls
221 lines (178 loc) · 6.74 KB
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
{-# LANGUAGE PatternSynonyms #-}
{-|
Module : Olifant.Parser
Description : First phase of the compilation
Grammar:
A Program is a series of top level blocks
Blocks are separated by new lines or ;
Blocks contains a list of indented statements optionally separated by ;
-}
-- It's ok to throw away results of do notation in a parser. Disable the warning
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
{-# OPTIONS_GHC -fno-warn-type-defaults #-}
module Olifant.Parser where
import Olifant.Core
import Prelude (Char, String)
import Protolude hiding (bool, handle, many, some, try, (<|>))
import Data.Char (isAlpha)
import Control.Monad (fail)
import Text.Megaparsec
import Text.Megaparsec.Char
import Text.Megaparsec.Debug
import qualified Text.Megaparsec.Char.Lexer as L
-- | Parser type alias
type Parser = Parsec Void String
-- | Pattern match a constant without repeating it.
pattern Eql :: Calculus
pattern Eql = CVar TUnit "__EQUAL__"
-- | Comments, the Haskell way
comment :: Parser ()
comment = L.skipLineComment "--"
-- | Space consumer, with newlines
scn :: Parser ()
scn = L.space space1 comment empty
-- | Space consumer, without newlines
sc :: Parser ()
sc = L.space (void $ takeWhile1P Nothing f) comment empty
where
f x = x == ' ' || x == '\t'
-- | Lexeme; consume the spaces after a token, but not before it
lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc
-- | Parse a constant string literal
symbol :: String -> Parser String
symbol = L.symbol sc
-- | 'parens' parses something between parenthesis.
parens :: Parser a -> Parser a
parens = between (symbol "(") (symbol ")")
-- | Parse a signed integer
--
-- The `signed` combinator from Megaparsec accepts spaces b/w the sign and
-- number; so that is not what I want.
number :: Parser Calculus
number = CLit . Number <$> ps
where
ps :: Parser Int
ps = try $ do
sign <- optional (char '-')
d <- lexeme L.decimal
return $ if sign == Just '-' then negate d else d
-- | Parse scheme style boolean
--
-- Try is required on the left side of <|> to prevent eagerly consuming #
bool :: Parser Calculus
bool = CLit . Bool . (== "#t") <$> (try (symbol "#t") <|> symbol "#f")
-- | Parse an identifier
identifier :: Parser Text
identifier = toS <$> some (satisfy ok)
where
ok :: Char -> Bool
ok c = (isAlpha c || c `elem` allowed) && (c `notElem` specials)
-- | Special symbols
specials :: String
specials = [':', 'λ', '#', '\\', '/', ';', '\n']
-- | Special symbols allowed in identifiers
allowed :: String
allowed = ['?', '!', '_', '+', '-', '/', '*', '^', '<', '>', '$']
-- | Parse a word as an identifier
var :: Parser Calculus
var = do
n <- identifier
t <- try ty
return $ CVar t n
where
-- | Parse a type
ty :: Parser Ty
ty = do
t <- optional $ try (char ':') *> (char 'i' <|> char 'b')
return $ case t of
Just 'b' -> TBool
Just 'i' -> TInt
Just _ -> TUnit
Nothing -> TUnit
-- [TODO] - Add support for Haskell style type declaration
-- [TODO] - Treat type declarations without body as extern
-- | Parse an assignment; @literal = symbol@
--
-- Using magic constants kind of suck; find some other approach
equals :: Parser Calculus
equals = CVar TUnit . toS <$> symbol "=" *> return Eql
-- | A single term; the atomic unit in the grammar
term1 :: Parser Calculus
term1 = lexeme $ bool <|> number <|> var <|> equals
-- | A sequence of terms, optionally parenthesized
terms :: Parser [Calculus]
terms = parens terms <|> many term1
-- | A sequence of terms; reduced to a single term
term :: Parser Calculus
term = terms >>= handle
-- | A single expression in the language
--
calculus :: Parser Calculus
calculus = L.nonIndented sc (L.indentBlock scn fn)
where
fn :: Parser (L.IndentOpt Parser Calculus Calculus)
fn = do
-- Header is the unintended block, which could be a simple expression or
-- function header
header <- terms
return $ L.IndentMany Nothing (f header) term
-- This function was the kind of pain impossible to explain. Megaparsec is a
-- horrible PITA to understand and use correctly, which I'll avoid at all
-- costs from now. A stream of tokens produced by Alex or happy is the way
-- to go. I really should have been dealing with a list of tokens
-- *INCLUDING* newlines and indentations.
f :: [Calculus] -> [Calculus] -> Parser Calculus
f header body =
case break (Eql ==) header of
-- _ = \n
(_, [Eql]) -> handle $ header ++ body
-- _ = ...
(left, Eql: right) -> do
f' <- handle right
handle $ left ++ [Eql, f'] ++ body
_ -> handle $ header ++ body
-- | Parse the whole program; split by new line
parser :: Parser [Calculus]
parser = someTill calculus eof
-- | Convert a series of terms into a Calculus expression
-- [FIX] - Error is reported after the line, be more specific
handle :: [Calculus] -> Parser Calculus
handle [] = fail "Unexpected end of input!!"
handle [x] = return x
handle ts = case break (Eql ==) ts of
-- ` = ..` should be a parse error, but handle it here too
([], _) -> fail "Missing left hand side of = operator"
-- Assignment to literals is silly
([CLit _], _) -> fail "Illegal assignment to literals"
-- Literals are not functions
(CLit _: _, _) -> fail "Function names cannot be literals"
-- Assignment; `a = 42` or `x = sum 1 2`
([CVar t variable], Eql: rhs) -> handle rhs >>= return . CLet t variable
-- Assignment to non text value; `3 = 4`
([_], [Eql, _]) -> fail "Illegal Assignment"
-- Function definition
(CVar _ f: as, Eql: body) -> do
-- Ensure all arguments are typed
args <- mapM mkArgs as
return $ CLam f args body
where
mkArgs :: Calculus -> Parser (Ty, Text)
mkArgs (CVar t val) = return (t, val)
mkArgs _ = fail "Expected typed variable as argument"
-- A sequence without a = should be an application
(f:args, []) -> return $ CApp f args
-- Fail for anything we don't explicitly handle
_ -> fail $ "Unable to parse " <> show ts
parse' :: Parser [Calculus] -> Text -> Either Error [Calculus]
parse' _ "" = Right []
parse' p' input =
case runParser p' "" (toS input) of
Left err -> Left $ ParseError $ toS $ errorBundlePretty err
Right val -> Right val
-- | Parse source and return AST
parse :: Text -> Either Error [Calculus]
parse = parse' parser
-- | Parse source and return AST with tracing output
debug :: Text -> Either Error [Calculus]
debug = parse' $ dbg "TEST" parser