{-

An ambiguous grammar made unambiguous using associativity and
precedences.

	frown PrecExpr.g

Try

	fmap unparse $ expr (lexer "a+b+c+d") :: Maybe String
	fmap unparse $ expr (lexer "a+b*c+d") :: Maybe String
	fmap unparse $ expr (lexer "a+b*c*d") :: Maybe String
	fmap unparse $ expr (lexer "a+b+-c+d") :: Maybe String
	fmap unparse $ expr (lexer "a+-b*c+d") :: Maybe String

-}

module PrecExpr
where
import Char
import Monad

data Expr                     =  Id     String
                              |  Negate Expr
                              |  Plus   Expr Expr
                              |  Minus  Expr Expr
                              |  Times  Expr Expr
                              |  Divide Expr Expr
                                 deriving (Show)

unparse (Id     s)            =  s
unparse (Negate e)            =  "(" ++ "-" ++ unparse e ++ ")"
unparse (Plus   e1 e2)        =  "(" ++ unparse e1 ++ "+" ++ unparse e2 ++ ")"
unparse (Minus  e1 e2)        =  "(" ++ unparse e1 ++ "-" ++ unparse e2 ++ ")"
unparse (Times  e1 e2)        =  "(" ++ unparse e1 ++ "*" ++ unparse e2 ++ ")"
unparse (Divide e1 e2)        =  "(" ++ unparse e1 ++ "/" ++ unparse e2 ++ ")"

type Result                   =  Maybe

%{

Terminal                      =             IDENT {String}
                              |  left     6 PLUS
                              |  left     6 MINUS
                              |  left     7 TIMES
                              |  left     7 DIVIDE
                              |  nonassoc 8 NEGATE
                              |             LPAR
                              |             RPAR;

Nonterminal                   = *expr {Expr};

expr {Negate e}               :  MINUS, expr {e}, prec NEGATE;
     {Plus   e1 e2}           |  expr {e1}, PLUS,   expr {e2};
     {Minus  e1 e2}           |  expr {e1}, MINUS,  expr {e2};
     {Times  e1 e2}           |  expr {e1}, TIMES,  expr {e2};
     {Divide e1 e2}           |  expr {e1}, DIVIDE, expr {e2};
     {e}                      |  LPAR, expr {e}, RPAR;
     {Id s}                   |  IDENT {s};

}%

frown ts                      =  fail "syntax error"

data Terminal                 =  IDENT String | PLUS | MINUS | TIMES | DIVIDE | LPAR | RPAR

lexer                         :: String -> [Terminal]
lexer []                      =  []
lexer ('+' : cs)              =  PLUS   : lexer cs
lexer ('-' : cs)              =  MINUS  : lexer cs
lexer ('*' : cs)              =  TIMES  : lexer cs
lexer ('/' : cs)              =  DIVIDE : lexer cs
lexer ('(' : cs)              =  LPAR   : lexer cs
lexer (')' : cs)              =  RPAR   : lexer cs
lexer (c : cs)
    | isAlpha c               =  let (n, cs') = span isAlphaNum cs
                                 in  IDENT (c : n) : lexer cs'
    | otherwise               =  lexer cs
