From 6f1e8de814b7aaeae409e705ab6085a8db5aa6a3 Mon Sep 17 00:00:00 2001 From: Joshua Potter Date: Fri, 24 Dec 2021 08:16:06 -0500 Subject: [PATCH] Minor clean up of final implementation. Convert initial encoding with better `Result`. --- initial-encoding/app/Main.hs | 70 +++-- initial-encoding/initial-encoding.cabal | 1 + initial-encoding/src/Parser/Initial.hs | 262 ++++++++---------- .../test/Test/Parser/InitialTest.hs | 63 +++-- parser-utils/src/Parser/Utils.hs | 23 +- tagless-final/app/Main.hs | 63 +++-- .../Parser/{Tagless/Closed.hs => Final.hs} | 81 +++--- tagless-final/tagless-final.cabal | 21 +- .../{Tagless/ClosedTest.hs => FinalTest.hs} | 25 +- 9 files changed, 319 insertions(+), 290 deletions(-) rename tagless-final/src/Parser/{Tagless/Closed.hs => Final.hs} (82%) rename tagless-final/test/Test/Parser/{Tagless/ClosedTest.hs => FinalTest.hs} (83%) diff --git a/initial-encoding/app/Main.hs b/initial-encoding/app/Main.hs index 3ee8d49..e74fd41 100644 --- a/initial-encoding/app/Main.hs +++ b/initial-encoding/app/Main.hs @@ -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" ) diff --git a/initial-encoding/initial-encoding.cabal b/initial-encoding/initial-encoding.cabal index 093f882..2378c42 100644 --- a/initial-encoding/initial-encoding.cabal +++ b/initial-encoding/initial-encoding.cabal @@ -9,6 +9,7 @@ executable initial-encoding megaparsec, mtl, optparse-applicative, + parser-utils, text, hs-source-dirs: app default-language: Haskell2010 diff --git a/initial-encoding/src/Parser/Initial.hs b/initial-encoding/src/Parser/Initial.hs index 53f7d4b..0be55a5 100644 --- a/initial-encoding/src/Parser/Initial.hs +++ b/initial-encoding/src/Parser/Initial.hs @@ -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) diff --git a/initial-encoding/test/Test/Parser/InitialTest.hs b/initial-encoding/test/Test/Parser/InitialTest.hs index 80c990d..c66265c 100644 --- a/initial-encoding/test/Test/Parser/InitialTest.hs +++ b/initial-encoding/test/Test/Parser/InitialTest.hs @@ -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" diff --git a/parser-utils/src/Parser/Utils.hs b/parser-utils/src/Parser/Utils.hs index 83ee374..78b7614 100644 --- a/parser-utils/src/Parser/Utils.hs +++ b/parser-utils/src/Parser/Utils.hs @@ -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) diff --git a/tagless-final/app/Main.hs b/tagless-final/app/Main.hs index 293a9fb..8c43775 100644 --- a/tagless-final/app/Main.hs +++ b/tagless-final/app/Main.hs @@ -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" ) diff --git a/tagless-final/src/Parser/Tagless/Closed.hs b/tagless-final/src/Parser/Final.hs similarity index 82% rename from tagless-final/src/Parser/Tagless/Closed.hs rename to tagless-final/src/Parser/Final.hs index 314a3bd..041c29a 100644 --- a/tagless-final/src/Parser/Tagless/Closed.hs +++ b/tagless-final/src/Parser/Final.hs @@ -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) diff --git a/tagless-final/tagless-final.cabal b/tagless-final/tagless-final.cabal index e1596e5..516f95e 100644 --- a/tagless-final/tagless-final.cabal +++ b/tagless-final/tagless-final.cabal @@ -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 diff --git a/tagless-final/test/Test/Parser/Tagless/ClosedTest.hs b/tagless-final/test/Test/Parser/FinalTest.hs similarity index 83% rename from tagless-final/test/Test/Parser/Tagless/ClosedTest.hs rename to tagless-final/test/Test/Parser/FinalTest.hs index 1e15915..835e3b7 100644 --- a/tagless-final/test/Test/Parser/Tagless/ClosedTest.hs +++ b/tagless-final/test/Test/Parser/FinalTest.hs @@ -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