flatparse-0.1.0.0: src/FlatParse/Examples/BasicLambda/Parser.hs
{-|
This module contains a simple lambda calculus parser. This parser is not optimized for maximum
performance; instead it's written in a style which emulates the look and feel of conventional
monadic parsers. An optimized implementation would use low-level `switch` expressions more often.
-}
{-# language StrictData #-}
module FlatParse.Examples.BasicLambda.Parser where
import Data.Char (ord)
import qualified Data.ByteString as B
import FlatParse.Basic hiding (Parser, runParser, string, char, err, cut)
import FlatParse.Examples.BasicLambda.Lexer
--------------------------------------------------------------------------------
type Name = B.ByteString
{-|
A term in the language. The precedences of different constructs are the following, in decreasing
order of strength:
* Identifiers, literals and parenthesized expressions
* Function application (left assoc)
* Multiplication (left assoc)
* Addition (left assoc)
* Equality, less-than (non-assoc)
* @lam@, @let@, @if@ (right assoc)
-}
data Tm
= Var Name -- ^ @x@
| App Tm Tm -- ^ @t u@
| Lam Name Tm -- ^ @lam x. t@
| Let Name Tm Tm -- ^ @let x = t in u@
| BoolLit Bool -- ^ @true@ or @false@.
| IntLit Int -- ^ A positive `Int` literal.
| If Tm Tm Tm -- ^ @if t then u else v@
| Add Tm Tm -- ^ @t + u@
| Mul Tm Tm -- ^ @t * u@
| Eq Tm Tm -- ^ @t == u@
| Lt Tm Tm -- ^ @t < u@
deriving Show
-- | Parse an identifier. This parser uses `isKeyword` to check that an identifier is not a
-- keyword.
ident :: Parser Name
ident = token $ byteStringOf $
spanned (identStartChar *> many_ identChar) (\_ -> fails . isKeyword)
-- | Parse an identifier, throw a precise error on failure.
cutIdent :: Parser Name
cutIdent = ident `cut'` (Msg "identifier")
digit :: Parser Int
digit = (\c -> ord c - ord '0') <$> satisfyASCII isDigit
int :: Parser Int
int = token $
snd <$> chainr (\n (!place, !acc) -> (place*10,acc+place*n)) digit ((10,) <$> digit)
-- | Parse a literal, identifier or parenthsized expression.
atom :: Parser Tm
atom =
(Var <$> ident)
<|> (BoolLit True <$ $(keyword "true"))
<|> (BoolLit False <$ $(keyword "false"))
<|> (IntLit <$> int)
<|> ($(symbol "(") *> tm <* $(cutSymbol ")"))
-- | Parse an `App`-level expression.
app :: Parser Tm
app = chainl App (atom `cut` [Msg "identifier", Lit "true", Lit "false"]) atom
-- | Parse a `Mul`-level expression.
mul :: Parser Tm
mul = chainl Mul app ($(symbol "*") *> app)
-- | Parse an `Add`-level expression.
add :: Parser Tm
add = chainl Add mul ($(symbol "+") *> mul)
-- | Parse an `Eq` or `Lt`-level expression.
eqLt :: Parser Tm
eqLt =
add >>= \e1 ->
branch $(symbol "==") (Eq e1 <$> add) $
branch $(symbol "<") (Lt e1 <$> add) $
pure e1
-- | Parse a `Let`.
pLet :: Parser Tm
pLet = do
$(keyword "let")
x <- cutIdent
$(cutSymbol "=")
t <- tm
$(cutKeyword "in")
u <- tm
pure $ Let x t u
-- | Parse a `Lam`.
lam :: Parser Tm
lam = do
$(keyword "lam")
x <- cutIdent
$(cutSymbol ".")
t <- tm
pure $ Lam x t
-- | Parse an `If`.
pIf :: Parser Tm
pIf = do
$(keyword "if")
t <- tm
$(cutKeyword "then")
u <- tm
$(cutKeyword "else")
v <- tm
pure $ If t u v
-- | Parse any `Tm`.
tm :: Parser Tm
tm = pLet <|> lam <|> pIf <|> eqLt
-- | Parse a complete source file.
src :: Parser Tm
src = ws *> tm <* eof `cut`
[Msg "end of input", Msg "identifier", Lit "true", Lit "false",
Msg "integer literal", Msg "parenthesized expression"]
-- Examples
--------------------------------------------------------------------------------
-- testParser src p1
-- p1 = unlines [
-- "let f = lam x. lam y. x (x (x y)) in",
-- "let g = if f true then false else true in",
-- "f g g h"
-- ]