-
Notifications
You must be signed in to change notification settings - Fork 0
/
calculator.hs
301 lines (251 loc) · 9.87 KB
/
calculator.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
module Main where
import Text.ParserCombinators.Parsec
import Control.Monad
import Data.Char
import Data.List
import qualified Data.Map.Lazy as ML
import Data.IORef
import Test.QuickCheck
--TODO:
-- Nice printouts
-- Evaluation (Simplification + evaluation)
-- Print out mode (Dec, Hex, Oct, ...)
-- Variables
-- Functions
-- Modulo operator
data Oper = ADD
| SUB
| MUL
| DIV
| EXP
| Fun String
deriving (Eq, Show, Ord)
operToStr :: Oper -> String
operToStr ADD = "+"
operToStr SUB = "-"
operToStr MUL = "*"
operToStr DIV = "/"
operToStr EXP = "^"
operToStr (Fun s) = s
data Expr = Value (NonNegative Integer)
| BinOp Oper Expr Expr
| UnOp Oper Expr
deriving (Eq, Show, Ord)
-- For quickCheck
instance Arbitrary Expr where
arbitrary = sized exprTree
where exprTree 0 = liftM Value arbitrary
exprTree n = oneof [ liftM2 UnOp someUnOp (exprTree $ n-1),
liftM3 BinOp someBinOp (exprTree $ n `div` 2)
(exprTree $ n `div` 2) ]
someBinOp = elements [ ADD, SUB, MUL, DIV, EXP ]
someUnOp = elements [ SUB, Fun "sin", Fun "cos" ]
shrink (Value x) = []
shrink (UnOp op x) = [x] ++ [UnOp op x' | x' <- shrink x]
shrink (BinOp op x y) = [BinOp op x' y' | (x', y') <- shrink (x, y)]
exprToString :: Expr -> String
exprToString (Value x) = show $ getNonNegative x
exprToString (BinOp op a b) = "(" ++ exprToString a ++ operToStr op ++ exprToString b ++ ")"
exprToString (UnOp (Fun f) a) = "(" ++ f ++ (exprToString a) ++ ")"
exprToString (UnOp op a) = "(" ++ operToStr op ++ (exprToString a) ++ ")"
transform :: (Expr -> Expr) -> Expr -> Expr
transform f old = let new = case old of
(BinOp op a b) -> f (BinOp op (transform f a) (transform f b))
(UnOp op a) -> f (UnOp op (transform f a))
a -> f a
in if new == old then old else transform f new
{- transform this into some kind of access-function
flatten :: Expr -> Expr
flatten = transform f
where f (Op "/" a b) = (Op "*" a $ fmap (Inv) b)
f (Op "*" (Op "*" a b) c) = (Op "*" a (b ++ c))
f (Op "*" a b) = (Op "*" a (extract "*" b))
f (Op "-" a b) = (Op "+" a $ fmap (Neg) b)
f (Op "+" (Op "+" a b) c) = (Op "+" a (b ++ c))
f (Op "+" a b) = (Op "+" a (extract "+" b))
f (Op op a b) = (Op op a b)
f (UnOp op a) = (UnOp op a)
f a = a
extract op l = (foldl (++) [] $ fmap (unWrap op) l)
unWrap op expr = case expr of
(Op o x y) -> if o == op then (x:extract op y) else [(Op o x y)]
x -> [x]
-}
data Ent a = CombineE Expr a -- Expression that should be combined
| CombineI a -- No expression needed just combine
| Pass Expr -- Don't combine just pass along
deriving (Eq)
expNumOfDigitsInBase10 :: Floating a => (NonNegative Integer) -> (NonNegative Integer) -> a
expNumOfDigitsInBase10 a b = fromNN b * (log $ fromNN a) / (log 10)
where fromNN = fromInteger . getNonNegative
simplify :: Expr -> Expr
simplify = transform f where
-- Exponent
f (BinOp EXP (Value 0) _) = Value 0
f (BinOp EXP _ (Value 0)) = Value 1
f (BinOp EXP (Value a) (Value b)) | expNumOfDigitsInBase10 a b < 50 = (Value $ a ^ b)
f (BinOp SUB (Value x) (Value y)) = Value $ x - y
-- Double negation
f (UnOp SUB (UnOp SUB x)) = x
{-
f (Op "+" a b) = toOp "+" $ merge lowerT liftT (a:b)
where liftT (Op "*" (Value x) [y]) = [CombineE y (x+)]
liftT (Op "*" y [Value x]) = [CombineE y (x+)]
liftT (Value x) = [CombineI (x+)]
liftT (Neg x) = [CombineE x (-1+)]
liftT (x) = [CombineE x (1+)]
lowerT (Pass x) = [x]
lowerT (CombineI n) | (n 0) == 0 = []
| otherwise = [Value (n 0)]
lowerT (CombineE x n) | (n 0) == 0 = []
| (n 0) == -1 = [Neg x]
| (n 0) == 1 = [x]
| otherwise = [Op "*" (Value $ n 0) [x]]
-}
-- Reduce integer divisions
f (BinOp DIV (Value a) (Value b)) | a `rem` b == 0 = (Value $ a `div` b)
{-
-- Multiplication by 1 or 0
f (Op "*" (Value 1) a) = toOp "*" a
f (Op "*" a [Value 1]) = a
f (Op "*" (Value 0) _) = Value 0
f (Op "*" _ [Value 0]) = Value 0
-}
{-
f (Op "*" a b) = toOp "*" $ merge lowerE liftE (a:b)
where
-- Values
liftE (Op "*" (Value x) [y]) = [CombineI (x*), Pass y]
liftE (Op "*" y [Value x]) = [CombineI (x*), Pass y]
liftE (Neg x) = [CombineI ((-1)*)] ++ liftE x
liftE (Value x) = [CombineI (x*)]
liftE (Inv (Value x)) = [CombineI ((1/x)*)]
-- Expr
--liftE (Op "^" (Value x) [y]) = [CombineE (Value x) (y+)]
liftE (Op "^" y [Value x]) = [CombineE y (x+)]
liftE (Inv x) = [CombineE x (-1+)]
liftE (x) = [CombineE x (1+)]
lowerE (Pass x) = [x]
lowerE (CombineI x) = [Value $ x 1]
lowerE (CombineE x n)
| (n 0) == 0 = []
| (n 0) == 1 = [x]
| otherwise = [Op "^" x [Value $ n 0]]
-}
f x = x
merge down up l = let (pass, process) = partition passQ $ foldl1 (++) $ fmap up l
merged = fmap (toComb) $ ML.toList . ML.fromListWith (.) $ fmap (toPair) process
passQ (Pass _) = True
passQ _ = False
toPair (CombineI n) = ([], n)
toPair (CombineE e n) = ([e], n)
toComb ([], n) = CombineI n
toComb ([e], n) = CombineE e n
in foldl (++) [] $ fmap down $ merged ++ pass
inLine :: Parser [[Expr]]
inLine = endBy statements newline
semiColon :: Parser Char
semiColon = char ';'
statements :: Parser [Expr]
statements = sepBy statement semiColon
statement :: Parser Expr
statement = exprP
function :: Parser Oper
function = do s <- funStr
return $ Fun s
where funStr = string "cos"
<|> string "sin"
atom :: Parser Expr
atom = parenTerm
<|> unOp
<|> fun
<|> valueP
where parenTerm = do char '('; a <- statement; char ')'; return a
unOp :: Parser Expr
unOp = do op <- opParser "-" SUB
a <- atom
return $ (UnOp op a)
fun :: Parser Expr
fun = do f <- function
a <- atom
return (UnOp f a)
valueP :: Parser Expr
valueP = (try $ string "0x" >> integerP 16)
<|> (try $ string "0o" >> integerP 8)
<|> (try $ string "0b" >> integerP 2)
<|> integerP 10
where
integerP base = do v <- many1 $ choice $ map (charValP) [0..(base-1)]
return $ Value (fromIntegral (calc base v))
calc base vs = sum $ zipWith (*) (baseList base) (reverse vs)
baseList base = map (base^) [0..]
charValP i = if i < 10 then
char (chr $ ord '0' + i) >> return i
else
((char (chr $ ord 'A' + (i-10)))
<|> (char (chr $ ord 'a' + (i-10)))) >> return i
data OpType = BinRight
| BinLeft
| Unary
data Operator = Operator { opType :: OpType
, parser :: Parser Oper
}
opParser :: String -> Oper -> Parser Oper
opParser str op = do string str
return $ op
exprP :: Parser Expr
exprP = inner lst where
lst = [ [Operator BinLeft $ opParser "+" ADD, -- level 1
Operator BinLeft $ opParser "-" SUB]
, [Operator BinLeft $ opParser "*" MUL, -- level 2
Operator BinLeft $ opParser "/" DIV]
, [Operator BinRight $ opParser "^" EXP] -- level 3
]
inner [] = atom
inner (level:levels) =
do a <- inner levels
parseRest level a <|> return a
where
parseRest [] a = return a
parseRest ops a = leftOrRight opP a where
leftOrRight = case opType $ head ops of
BinLeft -> leftA
BinRight -> rightA
opP = choice $ fmap infixP ops
infixP op = case op of
(Operator BinLeft n) -> n
(Operator BinRight n) -> n
leftA op a = do o <- op
b <- inner levels
leftA op (BinOp o a b)
<|> return a
rightA op a = do o <- op
b <- do c <- atom
rightA op c <|> return c
return (BinOp o a b)
parseLine :: String -> Either ParseError [[Expr]]
parseLine input = parse inLine "(unknown)" input
parseString :: String -> Maybe Expr
parseString str = case parseLine $ str ++ "\n" of
(Right [[e]]) -> Just e
_ -> Nothing
-- QuickCheck
prop_simplify :: Expr -> Bool
prop_simplify x = simplify (BinOp MUL (Value 2) x) == simplify (BinOp ADD x x)
prop_print_and_parse :: Expr -> Bool
prop_print_and_parse x = maybe False (\y -> xStr == exprToString y) (parseString xStr)
where xStr = exprToString x
repl :: IO ()
repl = do putStr "HCalc\n"
forever $
do l <- getLine
case parseLine (l ++ "\n") of
(Right [[e]]) -> do
putStrLn $ show e
putStrLn $ maybe "Could not parse" exprToString (parseString $ exprToString e)
putStrLn $ exprToString e
putStrLn $ (exprToString $ simplify e)
(Right _) -> putStr "Error: no input.\n"
(Left err) -> putStr $ show err
main :: IO ()
main = repl