patternMinor
Using Parsec for simple arithmetic language
Viewed 0 times
simplelanguageforusingparsecarithmetic
Problem
I've been reading Types and Programming Languages and I wanted to try to implement the first language in Haskell to understand it properly
I have barely written any Haskell before and not used Parsec so I would grateful for any feedback on this code
Here are some specific points I am unsure about
If there's anything else you consider odd don't hesitate to mention it
```
import Control.Monad
import System.Environment
import Text.ParserCombinators.Parsec
data Term = TmTrue
| TmFalse
| TmIf Term Term Term
| TmZero
| TmSucc Term
| TmPred Term
| TmIsZero Term
| TmError
deriving Show
main :: IO[()]
main = do
args case parseArith arg of
Left e -> print e
Right term -> print $ eval term)
isNumerical :: Term -> Bool
isNumerical term = case term of
TmZero -> True
TmSucc subterm -> isNumerical subterm
TmPred subterm -> isNumerical subterm
_ -> False
eval :: Term -> Term
eval term = case term of
TmTrue -> TmTrue
TmFalse -> TmFalse
TmZero -> TmZero
TmIf term1 term2 term3 -> case eval term1 of
TmTrue -> eval term2
TmFalse -> eval term3
_ -> TmError
TmIsZero subterm -> case eval subterm of
TmZero -> TmTrue
t2 | isNumerical t2 -> TmFalse
_ -> TmError
TmPred TmZero -> TmZero
TmPred (TmSucc subterm) -> eval subterm
TmSucc subterm -> case eval subterm of
t2 | isNumerical t2 -> TmSucc t2
_ -> TmError
_ -> TmError
parseArith :: String -> Either ParseError Term
parseArith input = parse arithParser "Failed to parse arithmetic expression" input
arithParser :: GenParser Char st Term
arithParser = try( ifParser )
try(
I have barely written any Haskell before and not used Parsec so I would grateful for any feedback on this code
Here are some specific points I am unsure about
- Is my main function sensible?
- Can the eval function be expressed any better?
- I'm unhappy with functionParser and ifParser. How can I code these better. In particular can the ifParser be coded in an applicative style?
If there's anything else you consider odd don't hesitate to mention it
```
import Control.Monad
import System.Environment
import Text.ParserCombinators.Parsec
data Term = TmTrue
| TmFalse
| TmIf Term Term Term
| TmZero
| TmSucc Term
| TmPred Term
| TmIsZero Term
| TmError
deriving Show
main :: IO[()]
main = do
args case parseArith arg of
Left e -> print e
Right term -> print $ eval term)
isNumerical :: Term -> Bool
isNumerical term = case term of
TmZero -> True
TmSucc subterm -> isNumerical subterm
TmPred subterm -> isNumerical subterm
_ -> False
eval :: Term -> Term
eval term = case term of
TmTrue -> TmTrue
TmFalse -> TmFalse
TmZero -> TmZero
TmIf term1 term2 term3 -> case eval term1 of
TmTrue -> eval term2
TmFalse -> eval term3
_ -> TmError
TmIsZero subterm -> case eval subterm of
TmZero -> TmTrue
t2 | isNumerical t2 -> TmFalse
_ -> TmError
TmPred TmZero -> TmZero
TmPred (TmSucc subterm) -> eval subterm
TmSucc subterm -> case eval subterm of
t2 | isNumerical t2 -> TmSucc t2
_ -> TmError
_ -> TmError
parseArith :: String -> Either ParseError Term
parseArith input = parse arithParser "Failed to parse arithmetic expression" input
arithParser :: GenParser Char st Term
arithParser = try( ifParser )
try(
Solution
Where you used
Note the complex terms are farmed off to their own functions. This makes testing easier. Especially as the runtime gets more complex.
You asked about
As for Applicative:
Also, the last 'try' should not be used. 'try' means to attempt a parse and do not consume the input if it failed. The last parse action is the end of the parse so there is no need to leave the input in the parser.
case you could have used pattern matching. This yields more idiomatic code in many cases.isNumerical :: Term -> Bool
isNumerical TmZero = True
isNumerical (TmSucc subterm) = isNumerical subterm
isNumerical (TmPred subterm) = isNumerical subterm
isNumerical _ = False
eval :: Term -> Term
eval TmTrue = TmTrue
eval TmFalse = TmFalse
eval TmZero = TmZero
eval (TmIf term1 term2 term3) = evalIf (eval term1) term2 term3
eval (TmIsZero subterm) = evalIsZero $ eval subterm
eval (TmPred subterm) = evalPred $ eval subterm
eval (TmSucc subterm) = evalSucc $ eval subterm
eval TmError = TmError
evalIf :: Term -> Term -> Term -> Term
evalIf TmTrue a _ = eval a
evalIf TmFalse _ b = eval b
evalIf _ _ _ = TmError
evalIsZero :: Term -> Term
evalIsZero TmZero = TmTrue
evalIsZero term
| isNumerical term = TmFalse
| otherwise = TmError
evalPred :: Term -> Term
evalPred TmZero = TmZero
evalPred t@(TmSucc subterm) = t
evalPred _ = TmError
evalSucc :: Term -> Term
evalSucc term
| isNumerical term = TmSucc term
| otherwise = TmErrorNote the complex terms are farmed off to their own functions. This makes testing easier. Especially as the runtime gets more complex.
You asked about
main's type. If you use forM_ you can define it as main :: IO ().As for Applicative:
functionParser :: String -> (Term -> Term) -> GenParser Char st Term
functionParser name funcTerm = funcTerm (string (name ++ "(")
*> arithParser
(string "if" *> spaces *> arithParser)
(spaces *> string "then" *> spaces *> arithParser)
(spaces *> string "else" *> spaces *> arithParser)Also, the last 'try' should not be used. 'try' means to attempt a parse and do not consume the input if it failed. The last parse action is the end of the parse so there is no need to leave the input in the parser.
Code Snippets
isNumerical :: Term -> Bool
isNumerical TmZero = True
isNumerical (TmSucc subterm) = isNumerical subterm
isNumerical (TmPred subterm) = isNumerical subterm
isNumerical _ = False
eval :: Term -> Term
eval TmTrue = TmTrue
eval TmFalse = TmFalse
eval TmZero = TmZero
eval (TmIf term1 term2 term3) = evalIf (eval term1) term2 term3
eval (TmIsZero subterm) = evalIsZero $ eval subterm
eval (TmPred subterm) = evalPred $ eval subterm
eval (TmSucc subterm) = evalSucc $ eval subterm
eval TmError = TmError
evalIf :: Term -> Term -> Term -> Term
evalIf TmTrue a _ = eval a
evalIf TmFalse _ b = eval b
evalIf _ _ _ = TmError
evalIsZero :: Term -> Term
evalIsZero TmZero = TmTrue
evalIsZero term
| isNumerical term = TmFalse
| otherwise = TmError
evalPred :: Term -> Term
evalPred TmZero = TmZero
evalPred t@(TmSucc subterm) = t
evalPred _ = TmError
evalSucc :: Term -> Term
evalSucc term
| isNumerical term = TmSucc term
| otherwise = TmErrorfunctionParser :: String -> (Term -> Term) -> GenParser Char st Term
functionParser name funcTerm = funcTerm <$> (string (name ++ "(")
*> arithParser
<* char ')')
ifParser :: GenParser Char st Term
ifParser = TmIf <$> (string "if" *> spaces *> arithParser)
<*> (spaces *> string "then" *> spaces *> arithParser)
<*> (spaces *> string "else" *> spaces *> arithParser)Context
StackExchange Code Review Q#43351, answer score: 2
Revisions (0)
No revisions yet.