-
Notifications
You must be signed in to change notification settings - Fork 9
/
lice.hs
139 lines (125 loc) · 3.84 KB
/
lice.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
{-# LANGUAGE LambdaCase #-}
module LispLovesMe where
import Data.List
import Data.Maybe
import Text.ParserCombinators.ReadP
data AST = I32 Int
| Sym String
| Nul
| Err
| Lst [AST]
| Boo Bool
| Nod AST [AST]
deriving (Eq, Show)
--
expr = symbols
+++ numbers
+++ booleans
+++ nulls
+++ nodes
nodes = do
char '('
whiteSpace
fist <- expr
exprs <- many expr
char ')'
whiteSpace
return $ Nod fist exprs
nulls = (const Nul <$> string "null" <* whiteSpace) +++ nul
where
nul = do
char '('
whiteSpace
char ')'
whiteSpace
return Nul
booleans = Boo <$> choice [ const True <$> string "true", const False <$> string "false" ] <* whiteSpace
numbers = I32 . read <$> munch1 (`elem` ['0'..'9']) <* whiteSpace
symbols = do
fist <- satisfy (`notElem` (" ,\n\t\r()" ++ [ '0' .. '9' ]))
tal <- munch (`notElem` " ,\n\t\r()") <* whiteSpace
let sym = fist : tal
if sym `notElem` [ "true", "false", "null" ]
then return $ Sym sym
else pfail
whiteSpace = many $ satisfy (`elem`",\r\n\t ")
eval :: AST -> AST
eval (Nod (Sym fist) param) =
if err
then Err
else fromMaybe Err $ ($ ps') <$> lookup fist preludeFunctions
where
ps' = fmap eval param
err = any (\case Err -> True; _ -> False) ps'
eval (Nod _ _) = Err
eval x = x
preludeFunctions :: [(String, [AST] -> AST)]
preludeFunctions =
[ ("+", checkErr (op (+)))
, ("*", checkErr (op (*)))
, ("-", checkErr (op (-)))
, ("/", checkErr (op div))
, ("^", \ps -> if length ps == 2 then checkErr (op (^)) ps else Err)
, (">", \ps -> if length ps == 2 then checkErr (op2bb (>)) ps else Err)
, ("<", \ps -> if length ps == 2 then checkErr (op2bb (<)) ps else Err)
, ("!", \ps -> if length ps == 1
then case eval $ head ps of
Boo b -> Boo $ not b
_ -> Err
else Err)
, ("list", checkErr Lst)
, ("size", checkErr size)
, ("reverse", checkErr reverse')
, ("..", checkErr range)
, ("==", \ps -> if length ps == 2 then checkErr (op2bb (==)) ps else Err)
, (">=", \ps -> if length ps == 2 then checkErr (op2bb (>=)) ps else Err)
, ("<=", \ps -> if length ps == 2 then checkErr (op2bb (<=)) ps else Err)
, ("!=", \ps -> if length ps == 2 then checkErr (op2bb (/=)) ps else Err)
, ("if", if')
]
where
checkErr f ps = if err then Err else f ps'
where
ps' = fmap eval ps
err = any (\case Err -> True; _ -> False) ps'
op f [] = Err
op f ps = if any (\case I32 x -> False; _ -> True ) prs
then Err
else I32 $ foldl1 f ps'
where
prs = eval <$> ps
ps' = (\case I32 v -> v) <$> prs
op2bb f [I32 a, I32 b] = Boo $ f a b
op2bb _ _ = Err
reverse' [Lst x] = Lst $ reverse x
reverse' _ = Err
range [I32 a, I32 b] = Lst $ I32 <$> [a..b]
range _ = Err
if' (p : a : b) = case p' of
Boo x -> if x then eval a else case b of
[ ] -> Nul
[x] -> eval x
_ -> Err
_ -> Err
where p' = eval p
if' _ = Err
size [Lst ls] = I32 $ length ls
size _ = Err
--
pretty :: AST -> String
pretty (I32 xs) = show xs
pretty (Nod a b) = "(" ++ unwords (pretty <$> (a:b)) ++ ")"
pretty (Sym s) = s
pretty Nul = "null"
pretty (Boo True) = "true"
pretty (Boo False) = "false"
lispPretty :: String -> Maybe String
lispPretty s = case filter ((== "") . snd) $ readP_to_S expr $ trimH s of
[] -> Nothing
xs -> Just $ pretty $ fst $ last xs
lispEval :: String -> Maybe AST
lispEval s = case filter ((== "") . snd) $ readP_to_S expr $ trimH s of
[] -> Nothing
xs -> Just $ eval $ fst $ last xs
trimH (x : xs) | x `elem` ",\r\n\t " = trimH xs
| otherwise = x : xs