1
Fork 0

Minor clean up of final implementation.

Convert initial encoding with better `Result`.
main
Joshua Potter 2021-12-24 08:16:06 -05:00
parent cef32ad12b
commit 6f1e8de814
9 changed files with 319 additions and 290 deletions

View File

@ -2,52 +2,62 @@
module Main where
import qualified Options.Applicative as O
import qualified Text.Megaparsec as M
import Data.Text (Text)
import Data.Text.IO (hGetContents)
import Options.Applicative
import Options.Applicative ((<**>))
import Parser.Initial
import Parser.Utils
import System.Environment (getArgs)
import System.IO (IOMode(ReadMode), openFile)
-- ========================================
-- Arguments
-- ========================================
data Args = Args {argsFileName :: !FilePath, argsMethod :: !Text}
args :: Parser Args
args :: O.Parser Args
args = Args
<$> strArgument (metavar "FILENAME" <> help "The file we want to parse.")
<*> strOption
( short 'm'
<> long "method"
<> metavar "METHOD"
<> showDefault
<> value "naive"
<> help "The parse strategy we want to try. Should be one of \
\\"naive\", \"mul_pass\", or \"mem_cons\"."
)
<$> O.strArgument
( O.metavar "FILENAME" <> O.help "The file we want to parse."
)
<*> O.strOption
( O.short 'm'
<> O.long "method"
<> O.metavar "METHOD"
<> O.showDefault
<> O.value "naive"
<> O.help "The parse strategy we want to try. Should be one of 'naive', \
\'single', 'strict', or 'gadt'."
)
runExpr :: (Text -> Either Text Expr) -> Text -> IO ()
runExpr f input = case f input >>= eval of
Right (EInt e) -> print e
Right (EBool e) -> print e
_ -> error "Could not evaluate expression fully."
-- ========================================
-- Main
-- ========================================
run :: Args -> IO ()
run args = do
handle <- openFile (argsFileName args) ReadMode
contents <- hGetContents handle
input <- hGetContents handle
case argsMethod args of
"naive" -> runExpr runNaive contents
"mul_pass" -> runExpr runMulPass contents
"mem_cons" -> runExpr runMemCons contents
"gadt" -> case runGadt contents of
Left e -> print e
Right (Wrapper a) -> print $ gadtEval a
_ -> error "Encountered an invalid parsing strategy."
"naive" -> runExpr parseNaive input
"single" -> runExpr parseSingle input
"strict" -> runExpr parseStrict input
"gadt" -> case runParser parseGadt input of
Left e -> print e
Right (Wrapper a) -> print $ eval a
_ -> error "Encountered an invalid parsing strategy."
where
runExpr p input = either print print (runParser p input)
main :: IO ()
main = run =<< execParser opts
main = run =<< O.execParser opts
where
opts = info (args <**> helper)
( fullDesc
<> progDesc "Different parsing strategies using initial encoding"
<> header "Initial encoding parsing"
opts = O.info (args <**> O.helper)
( O.fullDesc
<> O.progDesc "Different parsing strategies using initial encoding"
<> O.header "Initial encoding parsing"
)

View File

@ -9,6 +9,7 @@ executable initial-encoding
megaparsec,
mtl,
optparse-applicative,
parser-utils,
text,
hs-source-dirs: app
default-language: Haskell2010

View File

@ -8,13 +8,14 @@
module Parser.Initial
( Expr(..)
, GExpr(..)
, Result(..)
, Wrapper(..)
, eval
, gadtEval
, runGadt
, runMemCons
, runMulPass
, runNaive
, parseGadt
, parseNaive
, parseSingle
, parseStrict
, toResult
) where
import qualified Control.Monad.Combinators.Expr as E
@ -43,132 +44,112 @@ data Expr
| EOr Expr Expr
deriving (Eq, Show)
eval :: Expr -> Either Text Expr
eval e@(EInt _) = pure e
eval e@(EBool _) = pure e
eval (EAdd lhs rhs) = do
(lhs', rhs') <- binInt lhs rhs
pure $ EInt (lhs' + rhs')
eval (ESub lhs rhs) = do
(lhs', rhs') <- binInt lhs rhs
pure $ EInt (lhs' - rhs')
eval (EAnd lhs rhs) = do
(lhs', rhs') <- binBool lhs rhs
pure $ EBool (lhs' && rhs')
eval (EOr lhs rhs) = do
(lhs', rhs') <- binBool lhs rhs
pure $ EBool (lhs' || rhs')
data Result = RInt Integer | RBool Bool deriving (Eq)
binInt :: Expr -> Expr -> Either Text (Integer, Integer)
binInt lhs rhs = do
lhs' <- eval lhs
rhs' <- eval rhs
case (lhs', rhs') of
(EInt lhs'', EInt rhs'') -> pure (lhs'', rhs'')
_ -> Left "Expected two integers."
instance Show Result where
show (RInt e) = show e
show (RBool e) = show e
binBool :: Expr -> Expr -> Either Text (Bool, Bool)
binBool lhs rhs = do
lhs' <- eval lhs
rhs' <- eval rhs
case (lhs', rhs') of
(EBool lhs'', EBool rhs'') -> pure (lhs'', rhs'')
_ -> Left "Expected two booleans."
asInt :: Result -> Either Text Integer
asInt (RInt e) = pure e
asInt _ = Left "Could not cast integer."
asBool :: Result -> Either Text Bool
asBool (RBool e) = pure e
asBool _ = Left "Could not cast boolean."
toResult :: Expr -> Either Text Result
toResult (EInt e) = pure $ RInt e
toResult (EBool e) = pure $ RBool e
toResult (EAdd lhs rhs) = do
lhs' <- toResult lhs >>= asInt
rhs' <- toResult rhs >>= asInt
pure $ RInt (lhs' + rhs')
toResult (ESub lhs rhs) = do
lhs' <- toResult lhs >>= asInt
rhs' <- toResult rhs >>= asInt
pure $ RInt (lhs' - rhs')
toResult (EAnd lhs rhs) = do
lhs' <- toResult lhs >>= asBool
rhs' <- toResult rhs >>= asBool
pure $ RBool (lhs' && rhs')
toResult (EOr lhs rhs) = do
lhs' <- toResult lhs >>= asBool
rhs' <- toResult rhs >>= asBool
pure $ RBool (lhs' || rhs')
-- ========================================
-- Naive attempt
-- ========================================
naiveExpr :: forall m. ParserT m Expr
naiveExpr = E.makeExprParser term
[ [binary "+" EAdd, binary "-" ESub]
, [binary "&&" EAnd, binary "||" EOr]
]
where
binary name f = E.InfixL (f <$ symbol name)
term = parens naiveExpr <|>
EInt <$> integer <|>
EBool <$> boolean
runNaive :: Text -> Either Text Expr
runNaive input =
let res = M.parse (naiveExpr <* M.eof) "" input
in join $ bimap (pack . M.errorBundlePretty) eval res
-- ========================================
-- Multiple passes
-- ========================================
mulPassExpr :: forall m. MonadError Text m => ParserT m Expr
mulPassExpr = expr >>= either (fail . unpack) pure
parseNaive :: Parser Result
parseNaive = expr >>= either (fail . unpack) pure . toResult
where
expr = E.makeExprParser term
[ [ binary "+" binInt EInt EAdd
, binary "-" binInt EInt ESub
]
, [ binary "&&" binBool EBool EAnd
, binary "||" binBool EBool EOr
]
[ [binary "+" EAdd, binary "-" ESub]
, [binary "&&" EAnd, binary "||" EOr]
]
binary name f = E.InfixL (f <$ symbol name)
term = parens expr <|> EInt <$> integer <|> EBool <$> boolean
-- ========================================
-- Single pass
-- ========================================
parseSingle :: Parser Result
parseSingle = expr >>= either (fail . unpack) pure
where
expr = E.makeExprParser term
[ [binary "+" asInt EInt EAdd, binary "-" asInt EInt ESub]
, [binary "&&" asBool EBool EAnd, binary "||" asBool EBool EOr ]
]
binary name cast f bin = E.InfixL do
void $ symbol name
pure $ \lhs rhs -> do
lhs' <- lhs
rhs' <- rhs
(lhs', rhs') <- cast lhs' rhs'
eval $ bin (f lhs') (f rhs')
lhs' <- lhs >>= cast
rhs' <- rhs >>= cast
toResult $ bin (f lhs') (f rhs')
term = parens expr <|>
Right . EInt <$> integer <|>
Right . EBool <$> boolean
runMulPass :: Text -> Either Text Expr
runMulPass input =
let res = M.runParserT (mulPassExpr <* M.eof) "" input
in res >>= join . bimap (pack . M.errorBundlePretty) eval
term = parens expr <|> Right . RInt <$> integer <|> Right . RBool <$> boolean
-- ========================================
-- Memory consumption
-- Strict
-- ========================================
instance NFData Expr where
rnf (EInt e) = rnf e
rnf (EBool e) = rnf e
rnf (EAdd lhs rhs) = rnf lhs `seq` rnf rhs
rnf (ESub lhs rhs) = rnf lhs `seq` rnf rhs
rnf (EAnd lhs rhs) = rnf lhs `seq` rnf rhs
rnf (EOr lhs rhs) = rnf lhs `seq` rnf rhs
memConsExpr :: forall m. MonadError Text m => ParserT m Expr
memConsExpr = term >>= expr
parseStrict :: Parser Result
parseStrict = term >>= expr
where
expr t = do
op <- M.option Nothing $ Just <$> ops
case op of
Just OpAdd -> nest t EAdd
Just OpSub -> nest t ESub
Just OpAnd -> nest t EAnd
Just OpOr -> nest t EOr
Just OpAdd -> nest t asInt EInt EAdd
Just OpSub -> nest t asInt EInt ESub
Just OpAnd -> nest t asBool EBool EAnd
Just OpOr -> nest t asBool EBool EOr
_ -> pure t
nest :: Expr -> (Expr -> Expr -> Expr) -> ParserT m Expr
nest t bin = do
nest
:: forall a
. Result
-> (Result -> Either Text a)
-> (a -> Expr)
-> (Expr -> Expr -> Expr)
-> Parser Result
nest t cast f bin = do
t' <- term
case eval (bin t t') of
Left e -> throwError e
Right a -> a `deepseq` expr a
a <- either (fail . unpack) pure do
lhs <- cast t
rhs <- cast t'
toResult $ bin (f lhs) (f rhs)
a `seq` expr a
term = do
p <- M.option Nothing $ Just <$> symbol "("
if isJust p then (term >>= expr) <* symbol ")" else
EInt <$> integer <|> EBool <$> boolean
runMemCons :: Text -> Either Text Expr
runMemCons input =
let res = M.runParserT (memConsExpr <* M.eof) "" input
in res >>= join . bimap (pack . M.errorBundlePretty) eval
RInt <$> integer <|> RBool <$> boolean
-- ========================================
-- GADTs
@ -176,52 +157,44 @@ runMemCons input =
data GExpr a where
GInt :: Integer -> GExpr Integer
GBool :: Bool -> GExpr Bool
GBool :: Bool -> GExpr Bool
GAdd :: GExpr Integer -> GExpr Integer -> GExpr Integer
GSub :: GExpr Integer -> GExpr Integer -> GExpr Integer
GAnd :: GExpr Bool -> GExpr Bool -> GExpr Bool
GOr :: GExpr Bool -> GExpr Bool -> GExpr Bool
instance NFData (GExpr a) where
rnf (GInt e) = rnf e
rnf (GBool e) = rnf e
rnf (GAdd lhs rhs) = rnf lhs `seq` rnf rhs
rnf (GSub lhs rhs) = rnf lhs `seq` rnf rhs
rnf (GAnd lhs rhs) = rnf lhs `seq` rnf rhs
rnf (GOr lhs rhs) = rnf lhs `seq` rnf rhs
GAnd :: GExpr Bool -> GExpr Bool -> GExpr Bool
GOr :: GExpr Bool -> GExpr Bool -> GExpr Bool
data Wrapper = forall a. Show a => Wrapper (GExpr a)
fromInt :: GExpr a -> Either Text (GExpr Integer)
fromInt a@(GInt _ ) = pure a
fromInt a@(GAdd _ _) = pure a
fromInt a@(GSub _ _) = pure a
fromInt _ = Left "Expected an integer type."
eval :: GExpr a -> a
eval (GInt a) = a
eval (GBool a) = a
eval (GAdd lhs rhs) = eval lhs + eval rhs
eval (GSub lhs rhs) = eval lhs - eval rhs
eval (GAnd lhs rhs) = eval lhs && eval rhs
eval (GOr lhs rhs) = eval lhs || eval rhs
fromBool :: GExpr a -> Either Text (GExpr Bool)
fromBool a@(GBool _ ) = pure a
fromBool a@(GAnd _ _) = pure a
fromBool a@(GOr _ _) = pure a
fromBool _ = Left "Expected a boolean type."
asInt' :: GExpr a -> Either Text (GExpr Integer)
asInt' a@(GInt _ ) = pure a
asInt' a@(GAdd _ _) = pure a
asInt' a@(GSub _ _) = pure a
asInt' _ = Left "Expected an integer type."
gadtEval :: GExpr a -> a
gadtEval (GInt a) = a
gadtEval (GBool a) = a
gadtEval (GAdd lhs rhs) = gadtEval lhs + gadtEval rhs
gadtEval (GSub lhs rhs) = gadtEval lhs - gadtEval rhs
gadtEval (GAnd lhs rhs) = gadtEval lhs && gadtEval rhs
gadtEval (GOr lhs rhs) = gadtEval lhs || gadtEval rhs
asBool' :: GExpr a -> Either Text (GExpr Bool)
asBool' a@(GBool _ ) = pure a
asBool' a@(GAnd _ _) = pure a
asBool' a@(GOr _ _) = pure a
asBool' _ = Left "Expected a boolean type."
gadtExpr :: forall m. MonadError Text m => ParserT m Wrapper
gadtExpr = term >>= expr
parseGadt :: Parser Wrapper
parseGadt = term >>= expr
where
expr t = do
op <- M.option Nothing $ Just <$> ops
case op of
Just OpAdd -> nest t fromInt GAdd GInt
Just OpSub -> nest t fromInt GSub GInt
Just OpAnd -> nest t fromBool GAnd GBool
Just OpOr -> nest t fromBool GOr GBool
Just OpAdd -> nest t asInt' GInt GAdd
Just OpSub -> nest t asInt' GInt GSub
Just OpAnd -> nest t asBool' GBool GAnd
Just OpOr -> nest t asBool' GBool GOr
_ -> pure t
nest
@ -229,24 +202,19 @@ gadtExpr = term >>= expr
. Show b
=> Wrapper
-> (forall a. GExpr a -> Either Text (GExpr b))
-> (GExpr b -> GExpr b -> GExpr b)
-> (b -> GExpr b)
-> ParserT m Wrapper
nest (Wrapper t) cast bin f = do
-> (GExpr b -> GExpr b -> GExpr b)
-> Parser Wrapper
nest (Wrapper t) cast f bin = do
Wrapper t' <- term
case (cast t, cast t') of
(Right lhs, Right rhs) -> do
let z = f . gadtEval $ bin lhs rhs
z `deepseq` expr (Wrapper z)
(Left e, _) -> throwError e
(_, Left e) -> throwError e
let z = eval $ bin lhs rhs
z `seq` expr (Wrapper $ f z)
(Left e, _) -> fail $ unpack e
(_, Left e) -> fail $ unpack e
term = do
p <- M.option Nothing $ Just <$> symbol "("
if isJust p then (term >>= expr) <* symbol ")" else
Wrapper . GInt <$> integer <|> Wrapper . GBool <$> boolean
runGadt :: Text -> Either Text Wrapper
runGadt input =
let res = M.runParserT (gadtExpr <* M.eof) "" input
in res >>= first (pack . M.errorBundlePretty)

View File

@ -13,9 +13,13 @@ import Data.Bifunctor (first)
import Data.Functor.Identity (Identity(..))
import Data.Text (Text, pack)
import Parser.Initial
import Parser.Utils (Parser)
import Parser.Utils (Parser, allEqual, runParser)
import Test.Hspec (Expectation, Spec, describe, it, shouldBe)
-- ========================================
-- Utility
-- ========================================
convert :: GExpr a -> Expr
convert (GInt a) = EInt a
convert (GBool a) = EBool a
@ -24,20 +28,23 @@ convert (GSub lhs rhs) = ESub (convert lhs) (convert rhs)
convert (GAnd lhs rhs) = EAnd (convert lhs) (convert rhs)
convert (GOr lhs rhs) = EOr (convert lhs) (convert rhs)
runParsers :: Text -> [Either Text Expr]
runParsers input = [runNaive, runMulPass, runMemCons, runGadt'] <*> [input]
runParsers :: Text -> [Either Text Result]
runParsers input =
[ runParser parseNaive
, runParser parseSingle
, runParser parseStrict
, runGadt
] <*> [input]
where
runGadt' i = do
Wrapper res <- runGadt i
pure $ convert res
runGadt i = do
Wrapper res <- runParser parseGadt i
toResult $ convert res
allEqual :: forall a. Eq a => [a] -> Bool
allEqual [] = True
allEqual [x] = True
allEqual [x, y] = x == y
allEqual (x:y:xs) = x == y && allEqual (y : xs)
-- ========================================
-- Assertions
-- ========================================
shouldParse :: Text -> Expr -> Expectation
shouldParse :: Text -> Result -> Expectation
shouldParse input expected = do
let res@(x : _) = runParsers input
shouldBe x $ Right expected
@ -48,30 +55,36 @@ shouldNotParse input expected = do
let res@(x : _) = runParsers input
shouldBe x $ Left expected
-- ========================================
-- Tests
-- ========================================
spec_parser :: Spec
spec_parser = do
describe "literals" do
it "1" do
shouldParse "1" (EInt 1)
shouldParse "1" (RInt 1)
it "true" do
shouldParse "true" (EBool True)
shouldParse "true" (RBool True)
it "false" do
shouldParse "false" (EBool False)
shouldParse "false" (RBool False)
describe "addition/subtraction" do
it "binary" do
shouldParse "1 + 1" (EInt 2)
shouldParse "1 + 1" (RInt 2)
it "left associative" do
shouldParse "1 - 3 + 4" (EInt 2)
shouldParse "1 - 3 + 4" (RInt 2)
it "precedence" do
shouldParse "1 - (3 + 4)" (EInt (-6))
shouldParse "1 - (3 + 4)" (RInt (-6))
describe "conjunction/disjunction" do
it "binary" do
shouldParse "true && false" (EBool False)
shouldParse "true && true" (EBool True)
shouldParse "true || true" (EBool True)
shouldParse "true || false" (EBool True)
shouldParse "false || false" (EBool False)
shouldParse "true && false" (RBool False)
shouldParse "true && true" (RBool True)
shouldParse "true || true" (RBool True)
shouldParse "true || false" (RBool True)
shouldParse "false || false" (RBool False)
describe "invalid types" do
it "mismatch" do
shouldNotParse "true && 1" "Expected two booleans."
shouldNotParse "1 + true" "Expected two integers."
shouldNotParse "true && 1"
"1:10:\n |\n1 | true && 1\n | ^\nCould not cast boolean.\n"
shouldNotParse "1 + true"
"1:9:\n |\n1 | 1 + true\n | ^\nCould not cast integer.\n"

View File

@ -5,11 +5,13 @@ module Parser.Utils
( Op(..)
, Parser
, ParserT
, allEqual
, boolean
, integer
, lexeme
, ops
, parens
, runParser
, space
, symbol
) where
@ -20,9 +22,13 @@ import qualified Text.Megaparsec.Char.Lexer as ML
import Control.Applicative ((<|>))
import Data.Functor (($>))
import Data.Text (Text)
import Data.Text (Text, pack)
import Data.Void (Void)
-- ========================================
-- Parsing
-- ========================================
type Parser = M.Parsec Void Text
type ParserT = M.ParsecT Void Text
@ -66,3 +72,18 @@ ops = M.choice
, symbol "&&" $> OpAnd
, symbol "||" $> OpOr
]
runParser :: forall a. Parser a -> Text -> Either Text a
runParser p input = case M.runParser (p <* M.eof) "" input of
Left e -> Left . pack $ M.errorBundlePretty e
Right a -> pure a
-- ========================================
-- Utility
-- ========================================
allEqual :: forall a. Eq a => [a] -> Bool
allEqual [] = True
allEqual [x] = True
allEqual [x, y] = x == y
allEqual (x:y:xs) = x == y && allEqual (y : xs)

View File

@ -3,51 +3,64 @@
module Main where
import qualified Options.Applicative as O
import Data.Text (Text)
import Data.Text.IO (hGetContents)
import Options.Applicative
import Parser.Tagless.Closed
import Options.Applicative ((<**>))
import Parser.Final
import Parser.Utils
import System.Environment (getArgs)
import System.IO (IOMode(ReadMode), openFile)
-- ========================================
-- Arguments
-- ========================================
data Args = Args {argsFileName :: !FilePath, argsMethod :: !Text}
runExpr :: (Text -> Either Text (Dynamic Eval)) -> Text -> IO ()
runExpr f input = case f input of
args :: O.Parser Args
args = Args
<$> O.strArgument
( O.metavar "FILENAME" <> O.help "The file we want to parse."
)
<*> O.strOption
( O.short 'm'
<> O.long "method"
<> O.metavar "METHOD"
<> O.showDefault
<> O.value "single"
<> O.help "The parse strategy we want to try. Should be one of 'single' \
\or 'strict'."
)
-- ========================================
-- Main
-- ========================================
runExpr :: Parser (Dynamic Eval) -> Text -> IO ()
runExpr p input = case runParser p input of
Left e -> print e
Right d -> case fromDyn @Eval @Integer d of
Just a -> print a
Nothing -> case fromDyn @Eval @Bool d of
Just a -> print a
Nothing -> print "Could not evaluate expression fully."
Left e -> print e
args :: Parser Args
args = Args
<$> strArgument (metavar "FILENAME" <> help "The file we want to parse.")
<*> strOption
( short 'm'
<> long "method"
<> metavar "METHOD"
<> showDefault
<> value "mul_pass"
<> help "The parse strategy we want to try. Should be one of \
\\"mul_pass\" or \"mem_cons\"."
)
run :: Args -> IO ()
run args = do
handle <- openFile (argsFileName args) ReadMode
contents <- hGetContents handle
case argsMethod args of
"mul_pass" -> runExpr runMulPass contents
"mem_cons" -> runExpr runMemCons contents
"single" -> runExpr parseSingle contents
"strict" -> runExpr parseStrict contents
_ -> error "Encountered an invalid parsing strategy."
main :: IO ()
main = run =<< execParser opts
main = run =<< O.execParser opts
where
opts = info (args <**> helper)
( fullDesc
<> progDesc "Different parsing strategies using initial encoding"
<> header "Initial encoding parsing"
opts = O.info (args <**> O.helper)
( O.fullDesc
<> O.progDesc "Different parsing strategies using initial encoding"
<> O.header "Initial encoding parsing"
)

View File

@ -9,7 +9,7 @@
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE ViewPatterns #-}
module Parser.Tagless.Closed
module Parser.Final
( Dynamic(..)
, Eval(..)
, SQ(..)
@ -17,28 +17,21 @@ module Parser.Tagless.Closed
, TQ(..)
, Typeable(..)
, fromDyn
, runMemCons
, runMulPass
, parseSingle
, parseStrict
, toDyn
) where
import qualified Control.Monad.Combinators.Expr as E
import qualified Data.Eq.Type as EQ
import qualified Text.Megaparsec as M
import qualified Text.Megaparsec.Char as MC
import qualified Text.Megaparsec.Char.Lexer as ML
import Control.Applicative ((<|>))
import Control.DeepSeq (NFData(..), deepseq)
import Control.Monad (join)
import Control.Monad.Except (MonadError, throwError)
import Data.Bifunctor (first)
import Data.Eq.Type ((:=))
import Data.Functor (($>), void)
import Data.Functor.Identity (runIdentity)
import Data.Functor (void)
import Data.Maybe (isJust)
import Data.Text (Text, pack, unpack)
import Data.Void (Void)
import Parser.Utils
-- ========================================
@ -53,16 +46,6 @@ class Symantics repr where
eAnd :: repr Bool -> repr Bool -> repr Bool
eOr :: repr Bool -> repr Bool -> repr Bool
newtype SQ a = SQ {runSQ :: forall repr. Symantics repr => repr a}
instance Symantics SQ where
eInt e = SQ (eInt e)
eBool e = SQ (eBool e)
eAdd (SQ lhs) (SQ rhs) = SQ (eAdd lhs rhs)
eSub (SQ lhs) (SQ rhs) = SQ (eSub lhs rhs)
eAnd (SQ lhs) (SQ rhs) = SQ (eAnd lhs rhs)
eOr (SQ lhs) (SQ rhs) = SQ (eOr lhs rhs)
newtype Eval a = Eval {runEval :: a} deriving (Eq, Show)
instance Symantics Eval where
@ -130,7 +113,7 @@ fromDyn (Dynamic t e) = case t of
pure $ EQ.coerce (EQ.lift r') e
-- ========================================
-- Multiple passes
-- Single pass
-- ========================================
binDyn
@ -146,8 +129,8 @@ binDyn bin lhs rhs = do
rhs' <- fromDyn rhs
pure . Dynamic type' $ bin lhs' rhs'
mulPassExpr :: forall repr. Symantics repr => Parser (Dynamic repr)
mulPassExpr = expr >>= \case
parseSingle :: forall repr. Symantics repr => Parser (Dynamic repr)
parseSingle = expr >>= \case
Left (offset, msg) -> M.setOffset offset >> fail msg
Right a -> pure a
where
@ -163,21 +146,15 @@ mulPassExpr = expr >>= \case
lhs' <- lhs
rhs' <- rhs
case binDyn bin lhs' rhs' of
Nothing -> throwError
(offset, "Invalid operands for `" <> unpack name <> "`")
Nothing -> Left (offset, "Invalid operands for `" <> unpack name <> "`")
Just a -> pure a
term = parens expr <|>
Right . toDyn <$> integer <|>
Right . toDyn <$> boolean
runMulPass :: forall repr. Symantics repr => Text -> Either Text (Dynamic repr)
runMulPass input =
let res = M.runParser (mulPassExpr <* M.eof) "" input
in first (pack . M.errorBundlePretty) res
-- ========================================
-- Memory consumption
-- Strict
-- ========================================
instance (NFData t) => NFData (Eval t) where
@ -186,12 +163,12 @@ instance (NFData t) => NFData (Eval t) where
instance NFData (Dynamic Eval) where
rnf (Dynamic t e) = e `seq` ()
memConsExpr
parseStrict
:: forall repr
. Symantics repr
=> NFData (Dynamic repr)
=> Parser (Dynamic repr)
memConsExpr = term >>= expr
parseStrict = term >>= expr
where
expr :: Dynamic repr -> Parser (Dynamic repr)
expr t = do
@ -222,12 +199,30 @@ memConsExpr = term >>= expr
if isJust p then (term >>= expr) <* symbol ")" else
toDyn <$> integer <|> toDyn <$> boolean
runMemCons
:: forall repr
. Symantics repr
=> NFData (Dynamic repr)
=> Text
-> Either Text (Dynamic repr)
runMemCons input =
let res = M.runParser (memConsExpr <* M.eof) "" input
in first (pack . M.errorBundlePretty) res
-- ========================================
-- Printer
-- ========================================
newtype Print a = Print {runPrint :: Text} deriving (Eq, Show)
instance Symantics Print where
eInt = Print . pack . show
eBool = Print . pack . show
eAdd (Print lhs) (Print rhs) = Print ("(" <> lhs <> " + " <> rhs <> ")")
eSub (Print lhs) (Print rhs) = Print ("(" <> lhs <> " - " <> rhs <> ")")
eAnd (Print lhs) (Print rhs) = Print ("(" <> lhs <> " && " <> rhs <> ")")
eOr (Print lhs) (Print rhs) = Print ("(" <> lhs <> " || " <> rhs <> ")")
-- ========================================
-- Closed
-- ========================================
newtype SQ a = SQ {runSQ :: forall repr. Symantics repr => repr a}
instance Symantics SQ where
eInt e = SQ (eInt e)
eBool e = SQ (eBool e)
eAdd (SQ lhs) (SQ rhs) = SQ (eAdd lhs rhs)
eSub (SQ lhs) (SQ rhs) = SQ (eSub lhs rhs)
eAnd (SQ lhs) (SQ rhs) = SQ (eAnd lhs rhs)
eOr (SQ lhs) (SQ rhs) = SQ (eOr lhs rhs)

View File

@ -3,14 +3,15 @@
version: 0.1.0.0
executable tagless-final
main-is: Main.hs
build-depends: base ^>=4.14.3.0,
megaparsec,
optparse-applicative,
tagless-final,
text
hs-source-dirs: app
default-language: Haskell2010
main-is: Main.hs
build-depends: base ^>=4.14.3.0,
megaparsec,
optparse-applicative,
parser-utils,
tagless-final,
text
hs-source-dirs: app
default-language: Haskell2010
library
build-depends: base ^>=4.14.3.0,
@ -23,7 +24,7 @@ library
text
hs-source-dirs: src
default-language: Haskell2010
exposed-modules: Parser.Tagless.Closed
exposed-modules: Parser.Final
test-suite tagless-final-test
type: exitcode-stdio-1.0
@ -39,4 +40,4 @@ test-suite tagless-final-test
tasty-discover,
tasty-hspec,
text
other-modules: Test.Parser.Tagless.ClosedTest
other-modules: Test.Parser.FinalTest

View File

@ -4,16 +4,18 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}
module Test.Parser.Tagless.ClosedTest
module Test.Parser.FinalTest
( spec_parser,
) where
import Data.Text (Text)
import Parser.Tagless.Closed
import Parser.Final
import Parser.Utils
import Test.Hspec (Expectation, Spec, describe, it, shouldBe)
runParsers :: Text -> [Either Text (Dynamic Eval)]
runParsers input = [runMulPass, runMemCons] <*> [input]
-- ========================================
-- Utility
-- ========================================
instance Eq (Dynamic Eval) where
d1 == d2 = case (fromDyn @Eval @Integer d1, fromDyn @Eval @Integer d2) of
@ -29,11 +31,12 @@ instance Show (Dynamic Eval) where
Just a -> show a
_ -> error "No valid `Eval` instance."
allEqual :: forall a. Eq a => [a] -> Bool
allEqual [] = True
allEqual [x] = True
allEqual [x, y] = x == y
allEqual (x:y:xs) = x == y && allEqual (y : xs)
runParsers :: Text -> [Either Text (Dynamic Eval)]
runParsers input = [runParser parseSingle, runParser parseStrict] <*> [input]
-- ========================================
-- Assertions
-- ========================================
shouldParse :: Text -> Dynamic Eval -> Expectation
shouldParse input expected = do
@ -46,6 +49,10 @@ shouldNotParse input expected = do
let res@(x : _) = runParsers input
shouldBe x $ Left expected
-- ========================================
-- Tests
-- ========================================
spec_parser :: Spec
spec_parser = do
describe "literals" do