packages feed

typed-peg-0.4.0.0: examples/Arith.hs

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE GADTs            #-}
{-# LANGUAGE QuasiQuotes      #-}
{-# LANGUAGE TemplateHaskell  #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators    #-}

module Arith
  ( Exp (..)
  , evalExp
  , showExp
  , ArithEnv
  , arith
  ) where

import PEG
import PEG.QQ (pegGrammar)

data Exp
  = Lit Int
  | Neg Exp
  | Add Exp Exp
  | Sub Exp Exp
  | Mul Exp Exp
  | Div Exp Exp
  deriving (Eq, Show)

evalExp :: Exp -> Int
evalExp (Lit n)   = n
evalExp (Neg e)   = negate (evalExp e)
evalExp (Add a b) = evalExp a + evalExp b
evalExp (Sub a b) = evalExp a - evalExp b
evalExp (Mul a b) = evalExp a * evalExp b
evalExp (Div a b) = evalExp a `div` evalExp b

showExp :: Exp -> String
showExp (Lit n)   = show n
showExp (Neg e)   = "(-" ++ showExp e ++ ")"
showExp (Add a b) = bin "+" a b
showExp (Sub a b) = bin "-" a b
showExp (Mul a b) = bin "*" a b
showExp (Div a b) = bin "/" a b

bin :: String -> Exp -> Exp -> String
bin op a b = "(" ++ showExp a ++ " " ++ op ++ " " ++ showExp b ++ ")"

addOp :: Exp -> (Char, Exp) -> Exp
addOp l ('+', r) = Add l r
addOp l ('-', r) = Sub l r
addOp l ('*', r) = Mul l r
addOp l ('/', r) = Div l r
addOp _ (c  , _) = error ("addOp: unexpected operator " ++ show c)

-- | The key type, a binding per rule, the signature and the grammar are all
-- declared by the quasi-quoter.  A rule's result type is the one thing the
-- grammar does not determine, which is what the @:: T@ annotations are for;
-- left recursion and the rest are checked by 'PEG.Analysis' at the splice.
--
-- The annotations are still claims that GHC checks, not assertions: each is
-- the signature of its rule's binding (@arith'term :: Stream s => PExp s
-- (ArithEnv s) Exp@), so an annotation that disagrees with what the rule body
-- actually returns is a type error here, reported against that rule.
--
-- Being polymorphic in the stream has a cost: this is a function of a
-- 'Stream' dictionary rather than a constant, so the compiled parser is not
-- shared between calls.  Bind a monomorphic parser
-- (@arithString = parse arith :: String -> Result String Exp@) where that
-- matters.
[pegGrammar|
  %name  arith
  %start expr

  expr   :: Exp <- t:term ts:(o:[+-] u:term)* { foldl addOp t ts }
  term   :: Exp <- f:factor fs:(o:[*/] g:factor)*
                     { foldl (\acc (op, r) -> addOp acc (op, r)) f fs }
  factor :: Exp <- n:number
                 / '(' e:expr ')'
                 / '-' f:factor { Neg f }
  number :: Exp <- ds:[0-9]+ { Lit (read (chunkToString ds) :: Int) }
|]