Skip to content

Commit 745707c

Browse files
authored
Merge pull request #4 from gdevanla/parser-iteration-1
Parsing Expression - Chapter 5 and 6
2 parents 18a26dc + 573c606 commit 745707c

File tree

4 files changed

+225
-1
lines changed

4 files changed

+225
-1
lines changed

package.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,19 @@ tests:
110110
- -threaded
111111
- -rtsopts
112112
- -with-rtsopts=-N
113+
114+
haskell-lox-test-2:
115+
main: test_parser.hs
116+
source-dirs: test
117+
dependencies:
118+
- haskell-lox
119+
- tasty
120+
- HUnit
121+
- tasty-hunit
122+
- hspec
123+
- tasty
124+
125+
ghc-options:
126+
- -threaded
127+
- -rtsopts
128+
- -with-rtsopts=-N

src/ExprParser.hs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
{-# LANGUAGE OverloadedStrings #-}
2+
{-# LANGUAGE NoImplicitPrelude #-}
3+
4+
module ExprParser where
5+
6+
import Data.Text as T
7+
import Import hiding (many, try, (<|>))
8+
import Scanner
9+
import Text.Parsec
10+
11+
-- https://craftinginterpreters.com/parsing-expressions.html
12+
-- expression → equality ;
13+
-- equality → comparison ( ( "!=" | "==" ) comparison )* ;
14+
-- comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ;
15+
-- term → factor ( ( "-" | "+" ) factor )* ;
16+
-- factor → unary ( ( "/" | "*" ) unary )* ;
17+
-- unary → ( "!" | "-" ) unary
18+
-- | primary ;
19+
-- primary → NUMBER | STRING | "true" | "false" | "nil"
20+
-- | "(" expression ")" ;
21+
22+
-- expression → literal
23+
-- | unary
24+
-- | binary
25+
-- | grouping ;
26+
27+
-- literal → NUMBER | STRING | "true" | "false" | "nil" ;
28+
-- grouping → "(" expression ")" ;
29+
-- unary → ( "-" | "!" ) expression ;
30+
-- binary → expression operator expression ;
31+
-- operator → "==" | "!=" | "<" | "<=" | ">" | ">="
32+
-- | "+" | "-" | "*" | "/" ;
33+
34+
type LoxParserResult = Either ParseError Expr
35+
36+
data BinOp = NotEqual | EqualEqual | Gt | Gte | Lt | Lte | Plus | Minus | Star | Slash
37+
deriving (Show, Eq)
38+
39+
data UnaryOp = UnaryMinus | UnaryBang deriving (Show, Eq)
40+
41+
data Expr
42+
= Number Double
43+
| Literal T.Text
44+
| LoxBool Bool
45+
| LoxNil
46+
| Paren Expr
47+
| Unary UnaryOp Expr
48+
| Binary Expr BinOp Expr
49+
deriving (Show, Eq)
50+
51+
-- satisfy = tokenPrim (t -> String) (SourcePos -> t -> s -> SourcePos) (t -> Maybe a)
52+
53+
-- satisfy :: (Stream s m Char) => (Char -> Bool) -> ParsecT s u m Char
54+
-- satisfy f = tokenPrim (\c -> show [c])
55+
-- (\pos c _cs -> updatePosChar pos c)
56+
-- (\c -> if f c then Just c else Nothing)
57+
58+
type Parser a = ParsecT [LoxTokInfo] () Identity a
59+
60+
satisfyT :: (LoxTokInfo -> Maybe a) -> Parser a
61+
satisfyT f = tokenPrim showTok updateTokPos match
62+
where
63+
showTok ti = show $ tokinfo_type ti
64+
updateTokPos _ _ (s : _) = tok_position s
65+
updateTokPos pos _ [] = pos
66+
match t = f t
67+
68+
-- this is similar to chainl in `Text.Parsec` but works on `BinOp`
69+
-- adopted from https://jakewheat.github.io/intro_to_parsing/
70+
leftChain :: Parser Expr -> Parser BinOp -> Parser Expr
71+
leftChain p op = do
72+
expr <- p
73+
maybeAddSuffix expr
74+
where
75+
addSuffix e0 = do
76+
op' <- op
77+
e1 <- p
78+
maybeAddSuffix (Binary e0 op' e1)
79+
80+
maybeAddSuffix e = addSuffix e <|> return e
81+
82+
83+
-- primary
84+
number :: Parser Expr
85+
number = satisfyT f
86+
where
87+
f (LoxTokInfo (NUMBER x) _ _ _) = Just (Number x)
88+
f _ = Nothing
89+
90+
literal :: Parser Expr
91+
literal = satisfyT f
92+
where
93+
f (LoxTokInfo (STRING x) _ _ _) = Just (Literal $ T.pack x)
94+
f _ = Nothing
95+
96+
97+
loxBool :: Parser Expr
98+
loxBool = satisfyT f
99+
where
100+
f (LoxTokInfo TRUE _ _ _) = Just (LoxBool True)
101+
f (LoxTokInfo FALSE _ _ _) = Just (LoxBool False)
102+
f _ = Nothing
103+
104+
105+
loxNil :: Parser Expr
106+
loxNil = satisfyT f
107+
where
108+
f (LoxTokInfo NIL _ _ _) = Just LoxNil
109+
f _ = Nothing
110+
111+
loxParenExpr :: Parser Expr
112+
loxParenExpr = do
113+
satisfyT parenOpen *> loxExpr <* satisfyT parenClose
114+
where
115+
-- use LoxNil as placeholder, since we do not have an equilivalent Expr for Paren
116+
parenOpen (LoxTokInfo LEFT_PAREN _ _ _) = Just ()
117+
parenOpen _ = Nothing
118+
119+
parenClose (LoxTokInfo RIGHT_PAREN _ _ _) = Just ()
120+
parenClose _ = Nothing
121+
122+
loxPrimary :: Parser Expr
123+
loxPrimary = number <|> literal <|> loxBool <|> loxNil <|> loxParenExpr
124+
125+
unary' :: Parser Expr
126+
unary' = Unary <$> satisfyT f <*> unary
127+
where
128+
f (LoxTokInfo BANG _ _ _) = Just UnaryBang
129+
f (LoxTokInfo MINUS _ _ _) = Just UnaryMinus
130+
f _ = Nothing
131+
132+
133+
unary :: Parser Expr
134+
unary = unary' <|> loxPrimary
135+
136+
factor :: Parser Expr
137+
factor = leftChain unary (satisfyT f)
138+
where
139+
f x = case tokinfo_type x of
140+
STAR -> Just Star
141+
SLASH -> Just Slash
142+
_ -> Nothing
143+
144+
term :: Parser Expr
145+
term = leftChain factor (satisfyT f)
146+
where
147+
f x = case tokinfo_type x of
148+
MINUS -> Just Minus
149+
PLUS -> Just Plus
150+
_ -> Nothing
151+
152+
153+
comparison :: Parser Expr
154+
comparison = leftChain term (satisfyT f)
155+
where
156+
f x = case tokinfo_type x of
157+
GREATER -> Just Gt
158+
GREATER_EQUAL -> Just Gte
159+
LESS -> Just Lt
160+
LESS_EQUAL -> Just Lte
161+
_ -> Nothing
162+
163+
equality :: Parser Expr
164+
equality = leftChain comparison (satisfyT f)
165+
where
166+
f x = case tokinfo_type x of
167+
BANG_EQUAL -> Just NotEqual
168+
EQUAL_EQUAL -> Just EqualEqual
169+
_ -> Nothing
170+
171+
172+
loxExpr :: Parser Expr
173+
loxExpr = equality
174+
175+
scannerLoxTokens :: [LoxTokInfo] -> LoxParserResult
176+
scannerLoxTokens = parse loxExpr ""

src/Scanner.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{-# LANGUAGE OverloadedStrings #-}
22
{-# LANGUAGE NoImplicitPrelude #-}
33

4-
module Scanner where
4+
module Scanner(LoxTok(..), LoxTokInfo(..), scanner) where
55

66
import Import hiding (many, (<|>), try)
77
import Data.Text as T

test/test_parser.hs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{-# LANGUAGE OverloadedStrings#-}
2+
{-# OPTIONS_GHC -Wno-missing-signatures #-}
3+
import RIO
4+
import Test.Tasty
5+
import Test.Tasty.HUnit
6+
import Scanner
7+
import ExprParser
8+
import Text.Parsec as P
9+
10+
test_parser input expected = testCase input $ do
11+
let result = P.parse equality "" $ fromRight [] (scanner input)
12+
expected @=? result
13+
14+
test_equality = [
15+
test_parser "1>5<=8;" (Right (Binary (Binary (Number 1.0) Gt (Number 5.0)) Lte (Number 8.0))),
16+
test_parser "1+1/2>5<=8;" (Right (Binary
17+
(Binary
18+
(Binary (Number 1.0) Plus
19+
(Binary (Number 1.0) Slash (Number 2.0)))
20+
Gt (Number 5.0)) Lte (Number 8.0))),
21+
test_parser "\"test\";" $ Right (Literal "test"),
22+
test_parser "true" $ Right $ LoxBool True,
23+
test_parser "nil" $ Right LoxNil,
24+
test_parser "(1+2)/2;" $ Right (Binary
25+
(Binary (Number 1.0) Plus (Number 2.0))
26+
Slash
27+
(Number 2.0))
28+
]
29+
30+
main = do
31+
defaultMain $ testGroup "test_parser" test_equality
32+
--defaultMain tests

0 commit comments

Comments
 (0)