Commit | Line | Data |
---|---|---|
4cb2c1e4 JB |
1 | port module Main exposing (..) |
2 | ||
c792f15e JB |
3 | import IO exposing (..) |
4 | import Json.Decode exposing (decodeValue) | |
4cb2c1e4 | 5 | import Platform exposing (programWithFlags) |
74547df6 | 6 | import Types exposing (..) |
4cb2c1e4 JB |
7 | import Reader exposing (readString) |
8 | import Printer exposing (printString) | |
9 | import Utils exposing (maybeToList, zip) | |
10 | import Dict exposing (Dict) | |
74547df6 | 11 | import Tuple exposing (mapFirst, mapSecond, second) |
4cb2c1e4 | 12 | import Array |
74547df6 JB |
13 | import Env |
14 | import Eval | |
4cb2c1e4 JB |
15 | |
16 | ||
4cb2c1e4 JB |
17 | main : Program Flags Model Msg |
18 | main = | |
19 | programWithFlags | |
20 | { init = init | |
21 | , update = update | |
c792f15e | 22 | , subscriptions = \model -> input (decodeValue decodeIO >> Input) |
4cb2c1e4 JB |
23 | } |
24 | ||
25 | ||
26 | type alias Flags = | |
27 | { args : List String | |
28 | } | |
29 | ||
30 | ||
31 | type alias Model = | |
32 | { args : List String | |
33 | , env : Env | |
34 | } | |
35 | ||
36 | ||
37 | type Msg | |
c792f15e | 38 | = Input (Result String IO) |
4cb2c1e4 JB |
39 | |
40 | ||
41 | init : Flags -> ( Model, Cmd Msg ) | |
42 | init { args } = | |
43 | ( { args = args, env = initReplEnv }, readLine prompt ) | |
44 | ||
45 | ||
46 | initReplEnv : Env | |
47 | initReplEnv = | |
48 | let | |
74547df6 JB |
49 | makeFn = |
50 | CoreFunc >> MalFunction | |
51 | ||
4cb2c1e4 JB |
52 | binaryOp fn args = |
53 | case args of | |
54 | [ MalInt x, MalInt y ] -> | |
74547df6 | 55 | Eval.succeed <| MalInt (fn x y) |
4cb2c1e4 JB |
56 | |
57 | _ -> | |
74547df6 | 58 | Eval.fail "unsupported arguments" |
4cb2c1e4 | 59 | in |
74547df6 JB |
60 | Env.global |
61 | |> Env.set "+" (makeFn <| binaryOp (+)) | |
62 | |> Env.set "-" (makeFn <| binaryOp (-)) | |
63 | |> Env.set "*" (makeFn <| binaryOp (*)) | |
64 | |> Env.set "/" (makeFn <| binaryOp (//)) | |
4cb2c1e4 JB |
65 | |
66 | ||
67 | update : Msg -> Model -> ( Model, Cmd Msg ) | |
68 | update msg model = | |
69 | case msg of | |
c792f15e | 70 | Input (Ok (LineRead (Just line))) -> |
4cb2c1e4 JB |
71 | case rep model.env line of |
72 | Nothing -> | |
73 | ( model, readLine prompt ) | |
74 | ||
75 | Just ( result, newEnv ) -> | |
c792f15e JB |
76 | ( { model | env = newEnv }, writeLine (makeOutput result) ) |
77 | ||
78 | Input (Ok LineWritten) -> | |
79 | ( model, readLine prompt ) | |
80 | ||
81 | Input (Ok (LineRead Nothing)) -> | |
4cb2c1e4 JB |
82 | ( model, Cmd.none ) |
83 | ||
74547df6 JB |
84 | Input (Ok io) -> |
85 | Debug.crash "unexpected IO received: " io | |
86 | ||
c792f15e JB |
87 | Input (Err msg) -> |
88 | Debug.crash msg ( model, Cmd.none ) | |
89 | ||
4cb2c1e4 | 90 | |
c792f15e | 91 | makeOutput : Result String String -> String |
4cb2c1e4 | 92 | makeOutput result = |
c792f15e JB |
93 | case result of |
94 | Ok str -> | |
95 | str | |
4cb2c1e4 | 96 | |
c792f15e | 97 | Err msg -> |
dd7a4f55 | 98 | "Error: " ++ msg |
4cb2c1e4 JB |
99 | |
100 | ||
101 | prompt : String | |
102 | prompt = | |
103 | "user> " | |
104 | ||
105 | ||
106 | {-| read can return three things: | |
107 | ||
108 | Ok (Just expr) -> parsed okay | |
109 | Ok Nothing -> empty string (only whitespace and/or comments) | |
110 | Err msg -> parse error | |
111 | ||
112 | -} | |
113 | read : String -> Result String (Maybe MalExpr) | |
114 | read = | |
115 | readString | |
116 | ||
117 | ||
118 | eval : Env -> MalExpr -> ( Result String MalExpr, Env ) | |
119 | eval env ast = | |
120 | case ast of | |
121 | MalList [] -> | |
122 | ( Ok ast, env ) | |
123 | ||
124 | MalList ((MalSymbol "def!") :: args) -> | |
125 | evalDef env args | |
126 | ||
127 | MalList ((MalSymbol "let*") :: args) -> | |
128 | evalLet env args | |
129 | ||
130 | MalList list -> | |
131 | case evalList env list [] of | |
132 | ( Ok newList, newEnv ) -> | |
133 | case newList of | |
134 | [] -> | |
135 | ( Err "can't happen", newEnv ) | |
136 | ||
74547df6 | 137 | (MalFunction (CoreFunc fn)) :: args -> |
2110814e JB |
138 | case Eval.runSimple (fn args) of |
139 | Ok res -> | |
74547df6 JB |
140 | ( Ok res, newEnv ) |
141 | ||
2110814e | 142 | Err msg -> |
74547df6 JB |
143 | ( Err (print msg), newEnv ) |
144 | ||
4cb2c1e4 | 145 | fn :: _ -> |
74547df6 | 146 | ( Err ((print fn) ++ " is not a function"), newEnv ) |
4cb2c1e4 JB |
147 | |
148 | ( Err msg, newEnv ) -> | |
149 | ( Err msg, newEnv ) | |
150 | ||
151 | _ -> | |
152 | evalAst env ast | |
153 | ||
154 | ||
155 | evalAst : Env -> MalExpr -> ( Result String MalExpr, Env ) | |
156 | evalAst env ast = | |
157 | case ast of | |
158 | MalSymbol sym -> | |
159 | -- Lookup symbol in env and return value or raise error if not found. | |
160 | case Env.get sym env of | |
161 | Ok val -> | |
162 | ( Ok val, env ) | |
163 | ||
164 | Err msg -> | |
165 | ( Err msg, env ) | |
166 | ||
167 | MalList list -> | |
168 | -- Return new list that is result of calling eval on each element of list. | |
169 | evalList env list [] | |
170 | |> mapFirst (Result.map MalList) | |
171 | ||
172 | MalVector vec -> | |
173 | evalList env (Array.toList vec) [] | |
174 | |> mapFirst (Result.map (Array.fromList >> MalVector)) | |
175 | ||
176 | MalMap map -> | |
177 | evalList env (Dict.values map) [] | |
178 | |> mapFirst | |
179 | (Result.map | |
180 | (zip (Dict.keys map) | |
181 | >> Dict.fromList | |
182 | >> MalMap | |
183 | ) | |
184 | ) | |
185 | ||
186 | _ -> | |
187 | ( Ok ast, env ) | |
188 | ||
189 | ||
190 | evalList : Env -> List MalExpr -> List MalExpr -> ( Result String (List MalExpr), Env ) | |
191 | evalList env list acc = | |
192 | case list of | |
193 | [] -> | |
194 | ( Ok (List.reverse acc), env ) | |
195 | ||
196 | x :: rest -> | |
197 | case eval env x of | |
198 | ( Ok val, newEnv ) -> | |
199 | evalList newEnv rest (val :: acc) | |
200 | ||
201 | ( Err msg, newEnv ) -> | |
202 | ( Err msg, newEnv ) | |
203 | ||
204 | ||
205 | evalDef : Env -> List MalExpr -> ( Result String MalExpr, Env ) | |
206 | evalDef env args = | |
207 | case args of | |
208 | [ MalSymbol name, uneValue ] -> | |
209 | case eval env uneValue of | |
210 | ( Ok value, newEnv ) -> | |
211 | ( Ok value, Env.set name value newEnv ) | |
212 | ||
213 | err -> | |
214 | err | |
215 | ||
216 | _ -> | |
217 | ( Err "def! expected two args: name and value", env ) | |
218 | ||
219 | ||
220 | evalLet : Env -> List MalExpr -> ( Result String MalExpr, Env ) | |
221 | evalLet env args = | |
222 | let | |
223 | evalBinds env binds = | |
224 | case binds of | |
225 | (MalSymbol name) :: expr :: rest -> | |
226 | case eval env expr of | |
227 | ( Ok value, newEnv ) -> | |
228 | let | |
229 | newEnv = | |
230 | Env.set name value env | |
231 | in | |
232 | if List.isEmpty rest then | |
233 | Ok newEnv | |
234 | else | |
235 | evalBinds newEnv rest | |
236 | ||
237 | ( Err msg, _ ) -> | |
238 | Err msg | |
239 | ||
240 | _ -> | |
241 | Err "let* expected an even number of binds (symbol expr ..)" | |
242 | ||
243 | go binds body = | |
74547df6 | 244 | case evalBinds (Env.push env) binds of |
4cb2c1e4 | 245 | Ok newEnv -> |
74547df6 JB |
246 | eval newEnv body |
247 | |> mapSecond (\_ -> Env.pop newEnv) | |
4cb2c1e4 JB |
248 | |
249 | Err msg -> | |
250 | ( Err msg, env ) | |
251 | in | |
252 | case args of | |
253 | [ MalList binds, body ] -> | |
254 | go binds body | |
255 | ||
256 | [ MalVector bindsVec, body ] -> | |
257 | go (Array.toList bindsVec) body | |
258 | ||
259 | _ -> | |
260 | ( Err "let* expected two args: binds and a body", env ) | |
261 | ||
262 | ||
263 | {-| Try to map a list with a fn that can return a Err. | |
264 | ||
265 | Maps the list from left to right. As soon as a error | |
266 | occurs it will not process any more elements and return | |
267 | the error. | |
268 | ||
269 | -} | |
270 | tryMapList : (a -> Result e b) -> List a -> Result e (List b) | |
271 | tryMapList fn list = | |
272 | let | |
273 | go x = | |
274 | Result.andThen | |
275 | (\acc -> | |
276 | case fn x of | |
277 | Ok val -> | |
278 | Ok (val :: acc) | |
279 | ||
280 | Err msg -> | |
281 | Err msg | |
282 | ) | |
283 | in | |
284 | List.foldl go (Ok []) list | |
285 | |> Result.map List.reverse | |
286 | ||
287 | ||
288 | print : MalExpr -> String | |
289 | print = | |
74547df6 | 290 | printString Env.global True |
4cb2c1e4 JB |
291 | |
292 | ||
293 | {-| Read-Eval-Print. rep returns: | |
294 | ||
295 | Nothing -> if an empty string is read (ws/comments) | |
296 | Just ((Ok out), newEnv) -> input has been evaluated. | |
297 | Just ((Err msg), env) -> error parsing or evaluating. | |
298 | ||
299 | -} | |
300 | rep : Env -> String -> Maybe ( Result String String, Env ) | |
301 | rep env input = | |
302 | let | |
303 | evalPrint = | |
304 | eval env >> mapFirst (Result.map print) | |
305 | in | |
306 | case readString input of | |
307 | Ok Nothing -> | |
308 | Nothing | |
309 | ||
310 | Err msg -> | |
311 | Just ( Err msg, env ) | |
312 | ||
313 | Ok (Just ast) -> | |
314 | Just (evalPrint ast) |