Contents

Contents

My solution to the 2025 Haskell January Test

Contents

See the exam paper here.

module Parser where

import Lexer
import Types

import Data.Bifunctor (Bifunctor (second))

------------------------------------------------------------------------------
-- Given...

showToken :: Token -> String
showToken (Ident v) = v
showToken (Nat v) = show v
showToken WhileTok = "while"
showToken t = [head [c | (c, t') <- tokenTable, t == t']]

printParse :: String -> IO ()
printParse input = either printError printOK (parse input)
 where
  printOK prog = putStrLn "Parse successful..." >> print prog
  printError err = putStr "Parse error: " >> printError' err
  printError'' t s =
    putStrLn
      ( s
          ++ " expected, but "
          ++ maybe "nothing" showToken t
          ++ " found"
      )
  printError' (BadChar c) = do
    putStr "Unrecognised character: "
    putStrLn [c]
  printError' (Unexpected t t') = printError'' t (showToken t')
  printError' (StmtNotFound t) = printError'' t "Statement"
  printError' (ExprNotFound t) = printError'' t "Expression"
  printError' (IntNotFound t) = printError'' t "Integer literal"
  printError' (UnparsedInput toks) =
    putStrLn
      ( "Unparsed input: "
          ++ unwords (map showToken toks)
      )

------------------------------------------------------------------------------

-- Given...
mHead :: [a] -> Maybe a
mHead (x : _) = Just x
mHead _ = Nothing

checkTok :: Token -> [Token] -> Either Error [Token]
checkTok tok toks
  | h == Just tok = Right (tail toks)
  | otherwise = Left (Unexpected h tok)
 where
  h = mHead toks

parseAtom :: Parser Expr
parseAtom [] = Left (ExprNotFound Nothing)
parseAtom (Ident x : toks) = Right (toks, Var x)
parseAtom (Nat x : toks) = Right (toks, Val x)
parseAtom (Minus : Nat x : toks) = Right (toks, Val (negate x))
parseAtom (Minus : toks) = Left (IntNotFound (mHead toks))
parseAtom (LParen : toks) = do
  (toks', expr) <- parseExpr toks
  toks'' <- checkTok RParen toks'
  return (toks'', expr)
parseAtom toks = Left (ExprNotFound (mHead toks))

parseTerm :: Parser Expr
parseTerm = parseE parseAtom Times Mul

parseExpr :: Parser Expr
parseExpr = parseE parseTerm Plus Add

parseE :: Parser Expr -> Token -> (Expr -> Expr -> Expr) -> Parser Expr
parseE p tok constructor toks = do
  (toks', term) <- p toks
  parseE' p tok constructor term toks'

parseE' :: Parser Expr -> Token -> (Expr -> Expr -> Expr) -> Expr -> Parser Expr
parseE' p tok constructor t ts@(tok' : toks)
  | tok == tok' = do
      (toks', term) <- p toks
      parseE' p tok constructor (constructor t term) toks'
  | otherwise = return (ts, t)
parseE' _ _ _ t toks = Right (toks, t)

parseStmt :: Parser Stmt
parseStmt ((Ident v) : Eq : toks) = do
  (toks', expr) <- parseExpr toks
  return (toks', Asgn v expr)
parseStmt (WhileTok : toks) = do
  (toks', expr) <- parseExpr toks
  toks'' <- checkTok LBrace toks'
  (toks''', block) <- parseBlock toks''
  -- it's obvious that you want a State monad at this point
  toks'''' <- checkTok RBrace toks'''
  return (toks'''', While expr block)
parseStmt toks = Left (StmtNotFound (mHead toks))

parseBlock :: Parser Block
parseBlock toks = fmap (second reverse) (parseBlock' [] toks)

parseBlock' :: [Stmt] -> Parser Block
parseBlock' [] toks = do
  (toks', stmt) <- parseStmt toks
  parseBlock' [stmt] toks'
parseBlock' stmts (Semi : toks) = do
  (toks', stmt) <- parseStmt toks
  parseBlock' (stmt : stmts) toks'
parseBlock' stmt toks = return (toks, stmt)

parse :: String -> Either Error Program
parse input = do
  toks <- tokenise input
  (toks', program) <- parseBlock toks
  case toks' of
    [] -> Right program
    ts -> Left (UnparsedInput ts)

I completed the whole thing in 1 hour and 30 minutes. Though I took an compiler course and wrote a compiler in Haskell in my second year, so that’s some unfair advantage.

Many students therefore resorted to inspecting the result of every parser call using pattern matching, which missed the point.

That’s misreable. It reflects poor understanding of the concept of monads. I would blame it on bad course design. The last chapter is often rushed through in the end and people get confused.