Last active
March 23, 2022 11:13
-
-
Save NickAger/d4cbc1fa179c95f6aca1c23a7a3580c5 to your computer and use it in GitHub Desktop.
ExpressionParser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- https://wiki.haskell.org/Parsing_a_simple_imperative_language | |
-- currently doesn't support floating point or functions. | |
module Main where | |
import System.IO | |
import Control.Monad | |
import Text.ParserCombinators.Parsec | |
import Text.ParserCombinators.Parsec.Expr | |
import Text.ParserCombinators.Parsec.Language | |
import qualified Text.ParserCombinators.Parsec.Token as Token | |
data BExpr = BoolConst Bool | |
| Not BExpr | |
| BBinary BBinOp BExpr BExpr | |
| RBinary RBinOp AExpr AExpr | |
deriving (Show) | |
data BBinOp = And | Or deriving (Show) | |
data RBinOp = Greater | Less deriving (Show) | |
data AExpr = Var String | |
| IntConst Integer | |
| Neg AExpr | |
| ABinary ABinOp AExpr AExpr | |
deriving (Show) | |
data ABinOp = Add | |
| Subtract | |
| Multiply | |
| Divide | |
deriving (Show) | |
data Stmt = Seq [Stmt] | |
| Assign String AExpr | |
| If BExpr Stmt Stmt | |
| While BExpr Stmt | |
| Skip | |
deriving (Show) | |
languageDef = | |
emptyDef { Token.commentStart = "/*" | |
, Token.commentEnd = "*/" | |
, Token.commentLine = "//" | |
, Token.identStart = letter | |
, Token.identLetter = alphaNum | |
, Token.reservedNames = [ "if" | |
, "then" | |
, "else" | |
, "while" | |
, "do" | |
, "skip" | |
, "true" | |
, "false" | |
, "not" | |
, "and" | |
, "or" | |
] | |
, Token.reservedOpNames = ["+", "-", "*", "/", ":=" | |
, "<", ">", "and", "or", "not" | |
] | |
} | |
lexer = Token.makeTokenParser languageDef | |
identifier = Token.identifier lexer -- parses an identifier | |
reserved = Token.reserved lexer -- parses a reserved name | |
reservedOp = Token.reservedOp lexer -- parses an operator | |
parens = Token.parens lexer -- parses surrounding parenthesis: | |
-- parens p | |
-- takes care of the parenthesis and | |
-- uses p to parse what's inside them | |
integer = Token.integer lexer -- parses an integer | |
semi = Token.semi lexer -- parses a semicolon | |
whiteSpace = Token.whiteSpace lexer -- parses whitespace | |
whileParser :: Parser Stmt | |
whileParser = whiteSpace >> statement | |
statement :: Parser Stmt | |
statement = parens statement | |
<|> sequenceOfStmt | |
sequenceOfStmt = | |
do list <- (sepBy1 statement' semi) | |
-- If there's only one statement return it without using Seq. | |
return $ if length list == 1 then head list else Seq list | |
statement' :: Parser Stmt | |
statement' = ifStmt | |
<|> whileStmt | |
<|> skipStmt | |
<|> assignStmt | |
ifStmt :: Parser Stmt | |
ifStmt = | |
do reserved "if" | |
cond <- bExpression | |
reserved "then" | |
stmt1 <- statement | |
reserved "else" | |
stmt2 <- statement | |
return $ If cond stmt1 stmt2 | |
whileStmt :: Parser Stmt | |
whileStmt = | |
do reserved "while" | |
cond <- bExpression | |
reserved "do" | |
stmt <- statement | |
return $ While cond stmt | |
assignStmt :: Parser Stmt | |
assignStmt = | |
do var <- identifier | |
reservedOp ":=" | |
expr <- aExpression | |
return $ Assign var expr | |
skipStmt :: Parser Stmt | |
skipStmt = reserved "skip" >> return Skip | |
aExpression :: Parser AExpr | |
aExpression = buildExpressionParser aOperators aTerm | |
bExpression :: Parser BExpr | |
bExpression = buildExpressionParser bOperators bTerm | |
aOperators = [ [Prefix (reservedOp "-" >> return (Neg )) ] | |
, [Infix (reservedOp "*" >> return (ABinary Multiply)) AssocLeft, | |
Infix (reservedOp "/" >> return (ABinary Divide )) AssocLeft] | |
, [Infix (reservedOp "+" >> return (ABinary Add )) AssocLeft, | |
Infix (reservedOp "-" >> return (ABinary Subtract)) AssocLeft] | |
] | |
bOperators = [ [Prefix (reservedOp "not" >> return (Not )) ] | |
, [Infix (reservedOp "and" >> return (BBinary And )) AssocLeft, | |
Infix (reservedOp "or" >> return (BBinary Or )) AssocLeft] | |
] | |
aTerm = parens aExpression | |
<|> liftM Var identifier | |
<|> liftM IntConst integer | |
bTerm = parens bExpression | |
<|> (reserved "true" >> return (BoolConst True )) | |
<|> (reserved "false" >> return (BoolConst False)) | |
<|> rExpression | |
rExpression = | |
do a1 <- aExpression | |
op <- relation | |
a2 <- aExpression | |
return $ RBinary op a1 a2 | |
relation = (reservedOp ">" >> return Greater) | |
<|> (reservedOp "<" >> return Less) | |
parseString :: String -> Stmt | |
parseString str = | |
case parse whileParser "" str of | |
Left e -> error $ show e | |
Right r -> r | |
parseFile :: String -> IO Stmt | |
parseFile file = | |
do program <- readFile file | |
case parse whileParser "" program of | |
Left e -> print e >> fail "parse error" | |
Right r -> return r | |
main :: IO () | |
main = | |
do | |
let result = parseString("var := 10 * 20") -- Assign "var" (ABinary Multiply (IntConst 10) (IntConst 20)) | |
putStrLn(show result) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment