| 1 | import Text.ParserCombinators.ReadP |
|---|
| 2 | import Control.Monad |
|---|
| 3 | import Data.Char |
|---|
| 4 | import Debug.Trace |
|---|
| 5 | |
|---|
| 6 | data Op = Op String Prec Fixity deriving (Eq,Show) |
|---|
| 7 | data Fixity = Leftfix | Rightfix | Nonfix deriving (Eq,Show) |
|---|
| 8 | data Exp = Var Var | OpApp Exp Op Exp | Neg Exp deriving Eq |
|---|
| 9 | type Prec = Int |
|---|
| 10 | type Var = String |
|---|
| 11 | |
|---|
| 12 | -- Printing |
|---|
| 13 | |
|---|
| 14 | instance Show Exp where |
|---|
| 15 | showsPrec _ (Var x) = showString x |
|---|
| 16 | showsPrec p (OpApp l (Op op _ _) r) = |
|---|
| 17 | showParen (p > 0) $ showsPrec 9 l . showString op . showsPrec 9 r |
|---|
| 18 | showsPrec p (Neg e) = |
|---|
| 19 | showParen (p > 0) $ showString "-" . showsPrec 9 e |
|---|
| 20 | |
|---|
| 21 | -- ----------------------------------------------------------------------------- |
|---|
| 22 | -- recursive-descent |
|---|
| 23 | |
|---|
| 24 | data Tok = TExp Exp | TOp Op | TNeg deriving Show |
|---|
| 25 | |
|---|
| 26 | {- |
|---|
| 27 | the call |
|---|
| 28 | |
|---|
| 29 | parse op1 E1 (op2 : E2 : tokens) |
|---|
| 30 | |
|---|
| 31 | means that we are looking at an expression like |
|---|
| 32 | |
|---|
| 33 | E0 `op1` E1 `op2` ... (*1) |
|---|
| 34 | |
|---|
| 35 | (the caller holds E0). The job of parse is to build the |
|---|
| 36 | expression to the right of op1, returning this expression and the |
|---|
| 37 | remaining input. |
|---|
| 38 | |
|---|
| 39 | (1) if op1 and op2 have the same precedence, but they do not have |
|---|
| 40 | the same associativity, or they are declared to be nonfix, then the |
|---|
| 41 | expression is illegal. |
|---|
| 42 | |
|---|
| 43 | (2) If op1 has a higher precedence than op2, or op1 and op2 should |
|---|
| 44 | left-associate, then we know that the expression to the right of |
|---|
| 45 | op1 is E1, so we return this to the caller. |
|---|
| 46 | |
|---|
| 47 | (3) Otherwise, we know we want to build an expression of the form (E1 |
|---|
| 48 | `op2` R). To find R, we recursively call (parse op2 E2 tokens), |
|---|
| 49 | which returns the expression to the right of op2, namely R. Now, |
|---|
| 50 | we have |
|---|
| 51 | |
|---|
| 52 | E0 `op1` (E1 `op2` R) `op3` ... |
|---|
| 53 | |
|---|
| 54 | where op3 is the next operator in the input. This is an instance |
|---|
| 55 | of (*1) above, so to continue we call parse, with the new E1 == (E1 |
|---|
| 56 | `op2` R) |
|---|
| 57 | |
|---|
| 58 | To initialise the algorithm, we set op1 to be an imaginary operator |
|---|
| 59 | with precedence lower than everything else. Hence parse will |
|---|
| 60 | consume the whole input, and return the resulting expression. |
|---|
| 61 | -} |
|---|
| 62 | resolve :: [Tok] -> Maybe Exp |
|---|
| 63 | resolve tokens = fmap fst $ parseNeg (Op "" (-1) Nonfix) tokens |
|---|
| 64 | where |
|---|
| 65 | parseNeg :: Op -> [Tok] -> Maybe (Exp,[Tok]) |
|---|
| 66 | parseNeg op1 (TExp e1 : rest) |
|---|
| 67 | = parse op1 e1 rest |
|---|
| 68 | parseNeg op1 (TNeg : rest) |
|---|
| 69 | = do guard (prec1 < 6) |
|---|
| 70 | (r, rest') <- parseNeg (Op "-" 6 Leftfix) rest |
|---|
| 71 | parse op1 (Neg r) rest' |
|---|
| 72 | where |
|---|
| 73 | Op _ prec1 fix1 = op1 |
|---|
| 74 | |
|---|
| 75 | parse :: Op -> Exp -> [Tok] -> Maybe (Exp, [Tok]) |
|---|
| 76 | parse _ e1 [] = Just (e1, []) |
|---|
| 77 | parse op1 e1 tokens@(TOp op2 : rest) |
|---|
| 78 | -- case (1): check for illegal expressions |
|---|
| 79 | | prec1 == prec2 && (fix1 /= fix2 || fix1 == Nonfix) |
|---|
| 80 | = Nothing |
|---|
| 81 | |
|---|
| 82 | -- case (2): op1 and op2 should associate to the left |
|---|
| 83 | | prec1 > prec2 || (prec1 == prec2 && fix1 == Leftfix) |
|---|
| 84 | = Just (e1, tokens) |
|---|
| 85 | |
|---|
| 86 | -- case (3): op1 and op2 should associate to the right |
|---|
| 87 | | otherwise |
|---|
| 88 | = do (r,rest') <- parseNeg op2 rest |
|---|
| 89 | parse op1 (OpApp e1 op2 r) rest' |
|---|
| 90 | where |
|---|
| 91 | Op _ prec1 fix1 = op1 |
|---|
| 92 | Op _ prec2 fix2 = op2 |
|---|
| 93 | |
|---|
| 94 | -- ----------------------------------------------------------------------------- |
|---|
| 95 | -- ReadP |
|---|
| 96 | |
|---|
| 97 | -- lots of backtracking... |
|---|
| 98 | parseReadP :: String -> [Exp] |
|---|
| 99 | parseReadP str = map fst $ filter (null.snd) $ readP_to_S (exp 0) str |
|---|
| 100 | where |
|---|
| 101 | exp :: Int -> ReadP Exp |
|---|
| 102 | exp 10 = do c <- satisfy isAlphaNum; return (Var [c]) |
|---|
| 103 | exp n = do |
|---|
| 104 | choice [ |
|---|
| 105 | do l <- exp (n+1); rexp l n, |
|---|
| 106 | if n == 6 |
|---|
| 107 | then do char '-'; e <- exp 7; return (Neg e) |
|---|
| 108 | else pfail |
|---|
| 109 | ] |
|---|
| 110 | |
|---|
| 111 | rexp l n = choice [ |
|---|
| 112 | return l, |
|---|
| 113 | do |
|---|
| 114 | op@(Op _ prec fixity) <- do c <- get; return (lookupop c) |
|---|
| 115 | if n /= prec then pfail else do |
|---|
| 116 | case fixity of |
|---|
| 117 | Leftfix -> do r <- exp (n+1); rexp (OpApp l op r) n |
|---|
| 118 | Nonfix -> do r <- exp (n+1); return (OpApp l op r) |
|---|
| 119 | Rightfix -> do r <- exp n ; return (OpApp l op r) |
|---|
| 120 | ] |
|---|
| 121 | |
|---|
| 122 | -- ----------------------------------------------------------------------------- |
|---|
| 123 | -- Testing |
|---|
| 124 | |
|---|
| 125 | lookupop '|' = Op "|" 2 Rightfix |
|---|
| 126 | lookupop '&' = Op "&" 3 Rightfix |
|---|
| 127 | lookupop '>' = Op ">" 4 Nonfix |
|---|
| 128 | lookupop '<' = Op "<" 4 Nonfix |
|---|
| 129 | lookupop '+' = Op "+" 6 Leftfix |
|---|
| 130 | lookupop '-' = Op "-" 6 Leftfix |
|---|
| 131 | lookupop '*' = Op "*" 7 Leftfix |
|---|
| 132 | lookupop '/' = Op "/" 7 Leftfix |
|---|
| 133 | lookupop '^' = Op "^" 8 Rightfix |
|---|
| 134 | |
|---|
| 135 | negop = Op "-" 6 Leftfix |
|---|
| 136 | |
|---|
| 137 | fromstr ('-':z) = TNeg : fromstr z |
|---|
| 138 | fromstr (x:z) = TExp (Var [x]) : fromstrop z |
|---|
| 139 | |
|---|
| 140 | fromstrop [] = [] |
|---|
| 141 | fromstrop (op:z) = TOp (lookupop op) : fromstr z |
|---|
| 142 | |
|---|
| 143 | tests = [ |
|---|
| 144 | "a+b+c" |
|---|
| 145 | , "a+b+c*d" |
|---|
| 146 | , "a/b/c" |
|---|
| 147 | , "a/b+c" |
|---|
| 148 | , "a/b*c" |
|---|
| 149 | , "1^2^3+4" |
|---|
| 150 | , "a/1^2^3" |
|---|
| 151 | , "a*b/c" |
|---|
| 152 | , "a>b>c" |
|---|
| 153 | , "a>b+c" |
|---|
| 154 | , "a+b>c" |
|---|
| 155 | , "a+b<c+d" |
|---|
| 156 | , "a+b<c+d>e" |
|---|
| 157 | , "-a" |
|---|
| 158 | , "--a" |
|---|
| 159 | , "a+-b" |
|---|
| 160 | , "a*-b" |
|---|
| 161 | , "-a+b" |
|---|
| 162 | , "-a*b" |
|---|
| 163 | , "-a+b*c" |
|---|
| 164 | , "a<-b" |
|---|
| 165 | , "a<-3<4" |
|---|
| 166 | ] |
|---|
| 167 | |
|---|
| 168 | runtests = putStr $ unlines $ map (show.resolve.fromstr) tests |
|---|
| 169 | |
|---|
| 170 | readptests = putStr $ unlines $ map (show.parseReadP) tests |
|---|