package minicaml

  1. Overview
  2. Docs

Source file eval.ml

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
open Types
open Env
open Interface
open Util
open Typecheck

module T = ANSITerminal

(** Numerical Primitives *)

let int_binop (x, y) (op: int -> int -> int) =
  let a = unpack_int x and b = unpack_int y in EvtInt(op a b)

let bool_binop (x, y) (op: bool -> bool -> bool) =
  let a = unpack_bool x and b = unpack_bool y in EvtBool(op a b)

let bool_unop x (op: bool -> bool) =
  let a = unpack_bool x in EvtBool(op a)

let uniqueorfail l = if dup_key_exist l then
  raise (DictError "Duplicate key in dictionary")
  else l

(** Evaluate an expression in an environment *)
let rec eval (e: expr) (env: env_type) (n: stackframe) vb : evt =
  let n = push_stack n e in
  let depth = (match n with
    | StackValue(d, _, _) -> d
    | EmptyStack -> 0) in
  (* Partially apply eval to the current stackframe, verbosity and environment *)
  let ieval = fun x -> eval x env n vb in
  if vb >= 2 then print_message ~color:T.Blue ~loc:(Nowhere)
    "Reduction at depth" "%d\nExpression:\n%s" depth (show_expr e)
  else ();
  let evaluated = (match e with
  | Unit -> EvtUnit
  | Integer n -> EvtInt n
  | Boolean b -> EvtBool b
  | String s -> EvtString s
  | Symbol x -> lookup env x ieval
  | List x -> EvtList (List.map ieval x)
  | Cons (x, xs) ->
    let ls = unpack_list (ieval xs) in
    (match ls with
      | [] -> EvtList([(ieval x)])
      | lss -> EvtList((ieval x)::lss))
  (* Dictionaries and operations *)
  | Dict(l) ->
    let el = uniqueorfail (List.map (fun (x,y) -> isvalidkey (ieval x, ieval y)) l) in
    EvtDict el
  | Plus   (x, y) ->  int_binop   (ieval x, ieval y)  (+)
  | Sub   (x, y) ->   int_binop   (ieval x, ieval y)  (-)
  | Mult  (x, y) ->   int_binop   (ieval x, ieval y)  ( * )
  | And   (x, y) ->   bool_binop  (ieval x, ieval y)  (&&)
  | Or    (x, y) ->   bool_binop  (ieval x, ieval y)  (||)
  | Not   x      ->   bool_unop   (ieval x)           (not)
  | Eq    (x, y) ->   EvtBool(compare_evt (ieval x) (ieval y) = 0)
  | Gt    (x, y) ->   EvtBool(compare_evt (ieval x) (ieval y) > 0)
  | Lt    (x, y) ->   EvtBool(compare_evt (ieval x) (ieval y) < 0)
  | Ge    (x, y) ->   EvtBool(compare_evt (ieval x) (ieval y) >= 0)
  | Le    (x, y) ->   EvtBool(compare_evt (ieval x) (ieval y) <= 0)
  | IfThenElse (guard, first, alt) ->
    let g = unpack_bool (ieval guard) in
    if g then ieval first else ieval alt
  | Let (assignments, body) ->
    let evaluated_assignments = List.map
      (fun (_, value) -> AlreadyEvaluated (ieval value)) assignments
    and identifiers = fstl assignments in
    let new_env = bindlist env identifiers evaluated_assignments in
    eval body new_env n vb
  | Letlazy (assignments, body) ->
    let identifiers = fstl assignments in
    let new_env = bindlist env identifiers
      (List.map (fun (_, value) -> LazyExpression value) assignments) in
    eval body new_env n vb
  | Letrec (ident, value, body) ->
    (match value with
      | Lambda (params, fbody) ->
        let rec_env = (bind env ident
          (AlreadyEvaluated (RecClosure(ident, params, fbody, env))))
        in eval body rec_env n vb
      | _ -> raise (TypeError "Cannot define recursion on non-functional values"))
  | Letreclazy (ident, value, body) ->
    (match value with
      | Lambda (_, _) ->
        let rec_env = (bind env ident (LazyExpression value))
        in eval body rec_env n vb
      | _ -> raise (TypeError "Cannot define recursion on non-functional values"))
  | Lambda (params,body) -> Closure(params, body, env)
  (* Special Primitives that are eval-recursive *)
  (* Map a function over an iterable structure *)
  | Apply (Symbol "map", args) ->
    let (f, s) = (match args with
      | [f; s] -> (f, s)
      | _ -> raise WrongPrimitiveArgs) in
    let ef = ieval f and es = ieval s in
    typecheck ef "fun";
    (match es with
      | EvtList x ->
        EvtList(List.map (fun x -> applyfun ef [AlreadyEvaluated x] env n vb) x)
      | EvtDict d ->
        let (keys, values) = unzip d in
        EvtDict(zip keys (List.map (fun x -> applyfun ef [AlreadyEvaluated x] env n vb) values))
      | _ -> failwith "Value is not iterable")
  | Apply (Symbol "map2", args) ->
    let (f, s1, s2) = (match args with
      | [f; s1; s2] -> (f, s1, s2)
      | _ -> raise WrongPrimitiveArgs) in
    let ef = ieval f and es1 = ieval s1 and es2 = ieval s2 in
    typecheck ef "fun";
    (match es1 with
      | EvtList x ->
        let y = unpack_list es2 in
        EvtList(List.map2 (fun a b -> applyfun ef [AlreadyEvaluated a;
        AlreadyEvaluated b] env n vb) x y)
      | _ -> failwith "Value is not iterable")
  | Apply (Symbol "foldl", args) ->
    let (f, ac, s) = (match args with
      | [f; ac; s] -> (f, ac, s)
      | _ -> raise WrongPrimitiveArgs) in
    let ef = ieval f and es = ieval s and a = ieval ac in
    typecheck ef "fun";
    (match es with
      | EvtList x -> (List.fold_left
      (fun acc x -> applyfun ef [AlreadyEvaluated acc; AlreadyEvaluated x] env n vb) a x)
      | EvtDict d ->
        let (_, values) = unzip d in
        (List.fold_left (fun acc x -> applyfun ef [AlreadyEvaluated acc;
        AlreadyEvaluated x] env n vb) a values)
      | _ -> failwith "Value is not iterable")
  | Apply (Symbol "filter", args) ->
    let (p, s) = (match args with
      | [p; s] -> (ieval p, ieval s)
      | _ -> raise WrongPrimitiveArgs) in
    typecheck p "fun";
    (match s with
      | EvtList x ->
        EvtList(List.filter
        (fun x -> applyfun p [AlreadyEvaluated x] env n vb = EvtBool true) x)
      | EvtDict d ->
        EvtDict(List.filter (fun (_,v) ->
          applyfun p [AlreadyEvaluated v] env n vb = EvtBool true) d)
      | _ -> failwith "Value is not iterable")
  (* Function Application *)
  | Apply(f, expr_args) ->
    let closure = ieval f in
    let args = List.map (fun x -> AlreadyEvaluated (ieval x)) expr_args in
    applyfun closure args env n vb
  (* Eval a sequence of expressions but return the last *)
  | Sequence(exprl) ->
    let rec unroll el = (match el with
    | [] -> failwith "fatal: empty command sequence"
    | x::[] -> ieval x
    | x::xs -> (let _ = ieval x in unroll xs)) in unroll exprl
  (* Pipe two functions together, creating a new function
     That uses the first functions's result as the second's first argument *)
  | Pipe(e1, e2) ->
    (* Convert a list of identifiers to a list of symbols *)
    let syml l = List.map (fun x -> Symbol x) l in
    let f1 = ieval e1 and f2 = ieval e2 in
    typecheck f2 "fun";
    let (_, params1, _, _) = unpack_anyfun f1 in
    Closure(params1, Apply(e2, [Apply(e1, syml params1)]), env))
  in
  if vb >= 2 then print_message ~color:T.Cyan ~loc:(Nowhere)
    "Evaluates to at depth" "%d\n%s\n" depth (show_evt evaluated)
  else ();
  evaluated;
(* Search for a value in the primitives table and environment *)
and lookup (env: env_type) (ident: ide) ieval : evt =
  if key_exist ident Primitives.table
  then
    let ( _, numargs) = (get_key_val ident Primitives.table) in
    PrimitiveAbstraction (ident, numargs, env)
  else lookup_env env ident ieval
(* Search for a value in an environment *)
and lookup_env (env: env_type) (ident: ide) ieval : evt =
  if ident = "" then failwith "invalid identifier" else
  match env with
  | [] -> raise (UnboundVariable ident)
  | (i, LazyExpression e) :: env_rest -> if ident = i then ieval e
    else lookup env_rest ident ieval
  | (i, AlreadyEvaluated e) :: env_rest -> if ident = i then e else
    lookup env_rest ident ieval
and applyfun (closure: evt) (args: type_wrapper list) env n vb : evt =
    (* Evaluate the arguments and unpack the evt encapsuled in them *)
    let args = List.map (fun x ->
      match x with
        | AlreadyEvaluated _ -> x
        | LazyExpression y -> AlreadyEvaluated (eval y env n vb)) args
    in let evtargs = List.map (fun x -> match x with
        | AlreadyEvaluated y -> y
        | LazyExpression _ -> failwith "FATAL ERROR: this should have never happened") args in
    let p_length = List.length args in
    (match closure with
      | Closure(params, body, decenv) -> (* Use static scoping *)
        if (List.compare_lengths params args) > 0 then (* curry *)
          let applied_env = bindlist decenv (take p_length params) args in
          Closure((drop p_length params), body, applied_env)
        else  (* apply the function *)
          let application_env = bindlist decenv params args in
          eval body application_env n vb
      (* Apply a recursive function *)
      | RecClosure(name, params, body, decenv) ->
        let rec_env = (bind decenv name (AlreadyEvaluated closure)) in
        if (List.compare_lengths params args) > 0 then (* curry *)
          let applied_env = bindlist rec_env (take p_length params) args in
          RecClosure(name, (drop p_length params), body, applied_env)
        else  (* apply the function *)
          let application_env = bindlist rec_env params args in
          eval body application_env n vb
      | PrimitiveAbstraction(name, numargs, decenv) ->
        if (numargs > p_length) then (* curry *)
          let primargs = generate_prim_params (numargs) in
          let symprimargs = List.map (fun x -> Symbol x) primargs in
          let missing_args = drop p_length primargs
          and ihavethose_args = take p_length primargs in
          let app_env = bindlist decenv ihavethose_args args in
          Closure(missing_args, Apply(Symbol name, symprimargs), app_env)
        else
          (* Apply the primitive *)
          let (prim, _) = get_key_val name Primitives.table in
          prim evtargs
      | _ -> raise (TypeError "Cannot apply a non functional value"))