Lecture 11.0 — 2016-10-04

A parser for WhileNZ

This lecture is written in literate Haskell; you can download the raw source.

import Data.Char
import Data.List
import Control.Applicative

newtype Parser a = Parser { parse :: String -> Maybe (a,String) }

instance Functor Parser where
  fmap f p = Parser $ \s ->
    case parse p s of
      Nothing -> Nothing
      Just (v,s') -> Just (f v,s')

instance Applicative Parser where
  pure a = Parser $ \s -> Just (a,s)
  f <*> a = Parser $ \s ->
    case parse f s of
      Nothing -> Nothing
      Just (g,s') -> parse (fmap g a) s'

eof :: Parser ()
eof = Parser $ \s -> if null s then Just ((),"") else Nothing

satisfy :: (Char -> Bool) -> Parser Char
satisfy p = Parser f
  where f [] = Nothing
        f (x:xs) = if p x then Just (x,xs) else Nothing

ensure :: (a -> Bool) -> Parser a -> Parser a
ensure pred p = Parser $ \s ->
  case parse p s of
    Nothing -> Nothing
    Just (a,s') -> if pred a then Just (a,s') else Nothing

lookahead :: Parser (Maybe Char)
lookahead = Parser f
  where f [] = Just (Nothing,[])
        f (c:s) = Just (Just c,c:s)

instance Alternative Parser where
  empty = Parser $ \s -> Nothing
  p1 <|> p2 = Parser $ \s ->
    case parse p1 s of
      Just (a,s') -> Just (a,s')
      Nothing -> parse p2 s

spaces :: Parser ()
spaces = many (satisfy isSpace) *> pure ()

char :: Char -> Parser Char
char c = spaces *> satisfy (==c)

int :: Parser Int
int = read <$> (spaces *> some (satisfy isDigit))

parens p = char '(' *> (p <* char ')')

keywords :: [String]
keywords = ["WHILE", "DO", "END", "SKIP"]

kw :: String -> Parser ()
kw s = pure () <* spaces <* (sequenceA $ map (satisfy . (==)) s)

type VarName = String

data ArithExp =
    Var VarName
  | Num Int
  | Plus ArithExp ArithExp
  | Times ArithExp ArithExp
  | Neg ArithExp
  deriving Show

term,factor,atom :: Parser ArithExp
term = (Plus <$> (factor <* char '+') <*> term) <|>
       ((\a b -> Plus a (Neg b)) <$> (factor <* char '-') <*> term) <|>
       factor
factor = (Times <$> neg <* char '*' <*> factor) <|> neg
neg = Neg <$> (char '-' *> atom) <|> atom
atom = Num <$> int <|>
       Var <$> var <|>
       parens term

var :: Parser VarName
var = ensure (not . (`elem` keywords)) (spaces *> id)
  where id = (:) <$> satisfy isAlpha <*> many (satisfy isAlphaNum)

data WhileNZ =
    Skip
  | Assign VarName ArithExp
  | Seq WhileNZ WhileNZ
  | WhileNZ ArithExp WhileNZ
  deriving Show

sepBy1 :: Parser a -> Parser b -> Parser [a]
sepBy1 p sep = (:) <$> p <*> many (sep *> p)

statement, statements :: Parser WhileNZ
statements = foldl1 Seq <$> sepBy1 statement (char ';')
statement =     const Skip <$> kw "SKIP"
            <|> Assign <$> (var <* kw ":=") <*> term
            <|> WhileNZ <$>
                  (kw "WHILE" *> (term <* kw "DO")) <*>
                  (statements <* kw "END")
            <|> (\c b -> Seq c (WhileNZ b c)) <$>
                  (kw "DO" *> (statements <* kw "WHILE")) <*>
                  (term <* kw "END")

-- use statements as the top-level parser

parseProgram :: String -> Maybe WhileNZ
parseProgram s = fst <$> parse statements s

prog1 = parseProgram "x := 0"
prog2 = parseProgram "x := 0;\nWHILE x - 5 DO x := x + 1 END; SKIP;"