package gobba

  1. Overview
  2. Docs

Source file puritycheck.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
open Types
open Errors
open Util

(** "level out" the purity of two values *)
let level_purity a b = match (a, b) with
  | Impure, _ | _, Impure             -> Impure
  | PurityModule x, _ | _, PurityModule x         -> PurityModule x
  | Pure, Numerical | Numerical, Pure -> Pure
  | Numerical, Numerical              -> Numerical
  | Uncertain, _ | _, Uncertain       -> Uncertain
  | Pure, Pure                        -> Pure

(** Infer the purity of an expression. Note: this is a naive approach.
  This function is an abstract interpretation of expressions over primitives and environments.
  @param e The expression to infer
  @param state The current computation state
   *)
let rec infer e state : puret =
  let state = { state with stack = (Estack.push_stack state.stack e) } in
  let inferp x = infer x state in
  let inferp2 a b = level_purity (inferp a) (inferp b) in
  let inferpl ls =
    let apl = List.map inferp ls in
    List.fold_left level_purity Numerical apl in
  match e with
  | NumInt _ | NumFloat _ | NumComplex _ | Unit -> Numerical
  | Boolean _ | String _ | Character _ -> Pure
  | Not a -> inferp a
  (* Expressions with lists of expressions *)
  | Vect l  -> inferpl l
  | List l  -> inferpl l
  (* Dictionaries contain key value pairs, level them out *)
  | Dict (l) ->
    let _, names, values = unzip3 l in
    let apl = List.map (fun v -> infer v { state with purity = Impure}) values in
    PurityModule (zip names apl)
  | Binop(Getkey, d, k) ->
    let kk = match k with Symbol s -> s | _ -> traises "Properties accessed must be a symbol" state.stack in
    let dp = inferp d in
    (match dp with
      | PurityModule m -> lookup_env kk m Numerical
      | _ -> traises "Cannot access a property of a value that is not a dictionary" state.stack)
  | SetPurity (allowed, body) ->
    if (state.purity = Pure || state.purity = Numerical) && allowed = Impure then
      iraise (PurityError ("Cannot enter an " ^ (show_puret allowed) ^
      " context from a " ^ (show_puret state.purity) ^ " one!"))
      else infer body { state with purity = allowed }
  (* Infer from all the other binary operators and sequences *)
  | Binop(_, a, b) | Sequence (a, b) -> inferp2 a b
  | Lambda(_, b) -> infer b { state with purity = Impure}
  | IfThenElse(g, t, f) ->
    let pg = infer g state and pt = infer t state and pf = infer f state in
    level_purity pg (level_purity pt pf)
  | Let(assignments, body) ->
    let newstate = infer_assignment_list assignments state in
    infer body newstate
  | Symbol s -> lookup s state
  | Apply (f, arg) ->
    let fp = inferp f and argp = inferp arg in
    if state.purity <> Impure && fp = Impure then
        iraises (PurityError
          (Printf.sprintf "Tried to apply a %s value in a %s state" (show_puret fp) (show_puret state.purity)))
          state.stack
    else level_purity fp argp
  | ApplyPrimitive ((name, params, purity), args) ->
      if Array.length args != Array.length params then (iraise (Fatal "Primitive Application Error"))
      else if state.purity <> Impure && purity = Impure then
        iraises
          (PurityError ("Tried to apply an impure primitive in a pure block: " ^ name))
          state.stack
      else purity

and lookup_env (name: ide) (table: purityenv_type) fallback =
  match Dict.get name table with
  | None -> fallback
  | Some (p) -> p


and lookup (name: ide) (state: evalstate) : puret =
  let prim_purity = lookup_env name Primitives.purity_table (Uncertain) in
  match prim_purity with
    (* Symbol is either a parameter or an unbound variable *)
    | Uncertain -> lookup_env name state.purityenv (Numerical)
    | e -> e

and infer_assignment state (_, name, value) : evalstate =
  (* Return a new state, updating the purityenv with the new binding, infered from the value (where Impure contexts are allowed) *)
  { state with purityenv = (Dict.insert state.purityenv name ((infer value { state with purity = Impure }))) }

and infer_assignment_list assignments state : evalstate =
  match assignments with
  | [] -> state
  | (islazy, name, value)::xs ->
    let newstate = infer_assignment state (islazy, name, value) in
    (infer_assignment_list xs newstate)

and infer_command_list cmdlst state =
  let mstate = ref state in
  List.iter (fun x -> mstate := infer_command x !mstate) cmdlst;
  !mstate

and infer_command command state : evalstate = match command with
  | Directive (Setpurity p) -> {state with purity = p}
  | Directive _ -> state
  | Expr e ->
    (* Infer the expression purity and evaluate if appropriate to the current state *)
    let exprpurity = infer e state in
    if (state.purity = Pure || state.purity = Numerical) && exprpurity = Impure then
      iraises (PurityError ("This expression contains a " ^ (show_puret exprpurity) ^
                            " expression but it is in " ^ (show_puret state.purity) ^ " state!")) state.stack else ();
    if state.verbosity >= 1 then Printf.eprintf "Has purity: %s\n%!" (show_puret exprpurity) else ();
    state
  | Def dl ->
    (* Infer the values purity and evaluate if appropriate to the current state *)
    let new_purity_state = infer_assignment_list dl state in
    new_purity_state
OCaml

Innovation. Community. Security.