-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathL1.sml
291 lines (228 loc) · 10.2 KB
/
L1.sml
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
(* 2002-11-08 -- Time-stamp: <2006-10-25 09:22:33 pes20> -*-SML-*- *)
(* Peter Sewell *)
(* This file contains an interpreter, pretty-printer and type-checker
for the language L1. To make it go, copy it into a working
directory, ensure Moscow ML is available, and type
mosml -P full l1.ml
That will give you a MoscowML top level in which these definitions
are present. You can then type
doit ();
to show the reduction sequence of < l1:=3;!l1 , {l1=0 } >, and
doit2 ();
to run the type-checker on the same simple example; you can try
other examples analogously. This file doesn't have a parser for
l1, so you'll have to enter the abstract syntax directly, eg
prettyreduce (Seq( Assign ("l1",Integer 3), Deref "l1"), [("l1",0)]);
This has been tested with Moscow ML version 2.00 (June 2000), but
should work with any other implementation of Standard ML. *)
(* *********************)
(* the abstract syntax *)
(* *********************)
type loc = string
datatype oper = Plus | GTEQ
datatype expr =
Integer of int
| Boolean of bool
| Op of expr * oper * expr
| If of expr * expr * expr
| Assign of loc * expr
| Deref of loc
| Skip
| Seq of expr * expr
| While of expr * expr
(* **********************************)
(* an interpreter for the semantics *)
(* **********************************)
fun is_value (Integer n) = true
| is_value (Boolean b) = true
| is_value (Skip) = true
| is_value _ = false
(* In the semantics, a store is a finite partial function from
locations to integers. In the implementation, we represent a store
as a list of loc*int pairs containing, for each l in the domain of
the store, exactly one element of the form (l,n). The operations
lookup : store * loc -> int option
update : store * (loc * int) -> store option
both return NONE if given a location that is not in the domain of
the store. This is not a very efficient implementation, but it is
simple. *)
type store = (loc * int) list
fun lookup ( [], l ) = NONE
| lookup ( (l',n')::pairs, l) =
if l=l' then SOME n' else lookup (pairs,l)
fun update' front [] (l,n) = NONE
| update' front ((l',n')::pairs) (l,n) =
if l=l' then
SOME(front @ ((l,n)::pairs) )
else
update' ((l',n')::front) pairs (l,n)
fun update (s, (l,n)) = update' [] s (l,n)
(* now define the single-step function
reduce : expr * store -> (expr * store) option
which takes a configuration (e,s) and returns either NONE, if it has
no transitions, or SOME (e',s'), if it has a transition (e,s) -->
(e',s').
Note that the code depends on global properties of the semantics,
including the fact that it defines a deterministic transition
system, so the comments indicating that particular lines of code
implement particular semantic rules are not the whole story. *)
fun reduce (Integer n,s) = NONE
| reduce (Boolean b,s) = NONE
| reduce (Op (e1,opr,e2),s) =
(case (e1,opr,e2) of
(Integer n1, Plus, Integer n2) => SOME(Integer (n1+n2), s) (*op + *)
| (Integer n1, GTEQ, Integer n2) => SOME(Boolean (n1 >= n2), s)(*op >=*)
| (e1,opr,e2) => (
if (is_value e1) then (
case reduce (e2,s) of
SOME (e2',s') => SOME (Op(e1,opr,e2'),s') (* (op2) *)
| NONE => NONE )
else (
case reduce (e1,s) of
SOME (e1',s') => SOME(Op(e1',opr,e2),s') (* (op1) *)
| NONE => NONE ) ) )
| reduce (If (e1,e2,e3),s) =
(case e1 of
Boolean(true) => SOME(e2,s) (* (if1) *)
| Boolean(false) => SOME(e3,s) (* (if2) *)
| _ => (case reduce (e1,s) of
SOME(e1',s') => SOME(If(e1',e2,e3),s') (* (if3) *)
| NONE => NONE ))
| reduce (Deref l,s) =
(case lookup (s,l) of
SOME n => SOME(Integer n,s) (* (deref) *)
| NONE => NONE )
| reduce (Assign (l,e),s) =
(case e of
Integer n => (case update (s,(l,n)) of
SOME s' => SOME(Skip, s') (* (assign1) *)
| NONE => NONE)
| _ => (case reduce (e,s) of
SOME (e',s') => SOME(Assign (l,e'), s') (* (assign2) *)
| NONE => NONE ) )
| reduce (While (e1,e2),s) = SOME( If(e1,Seq(e2,While(e1,e2)),Skip),s) (* (while) *)
| reduce (Skip,s) = NONE
| reduce (Seq (e1,e2),s) =
(case e1 of
Skip => SOME(e2,s) (* (seq1) *)
| _ => ( case reduce (e1,s) of
SOME (e1',s') => SOME(Seq (e1',e2), s') (* (seq2) *)
| NONE => NONE ) )
(* now define the many-step evaluation function
evaluate : expr * store -> (expr * store) option
which takes a configuration (e,s) and returns the unique (e',s')
such that (e,s) -->* (e',s') -/->. *)
fun evaluate (e,s) = case reduce (e,s) of
NONE => (e,s)
| SOME (e',s') => evaluate (e',s')
(* **********************************)
(* typing *)
(* **********************************)
(* types *)
datatype type_L1 =
int
| unit
| bool
datatype type_loc =
intref
type typeEnv = (loc*type_loc) list
(* in the semantics, type environments gamma are partial functions
from locations to the singleton set {intref}. Here, just as we did for
stores, we represent them as a list of loc*type_loc pairs containing,
for each l in the domain of the type environment, exactly one element
of the form (l,intref). *)
(* ****************)
(* type inference *)
(* ****************)
(* infertype : typeEnv -> expr -> type_L1 option *)
(* again, we depend on a uniqueness property, without which we would
have to have infertype return a type_L1 list of all the possible types *)
fun infertype gamma (Integer n) = SOME int
| infertype gamma (Boolean b) = SOME bool
| infertype gamma (Op (e1,opr,e2))
= (case (infertype gamma e1, opr, infertype gamma e2) of
(SOME int, Plus, SOME int) => SOME int
| (SOME int, GTEQ, SOME int) => SOME bool
| _ => NONE)
| infertype gamma (If (e1,e2,e3))
= (case (infertype gamma e1, infertype gamma e2, infertype gamma e3) of
(SOME bool, SOME t2, SOME t3) =>
(if t2=t3 then SOME t2 else NONE)
| _ => NONE)
| infertype gamma (Deref l)
= (case lookup (gamma,l) of
SOME intref => SOME int
| NONE => NONE)
| infertype gamma (Assign (l,e))
= (case (lookup (gamma,l), infertype gamma e) of
(SOME intref,SOME int) => SOME unit
| _ => NONE)
| infertype gamma (Skip) = SOME unit
| infertype gamma (Seq (e1,e2))
= (case (infertype gamma e1, infertype gamma e2) of
(SOME unit, SOME t2) => SOME t2
| _ => NONE )
| infertype gamma (While (e1,e2))
= (case (infertype gamma e1, infertype gamma e2) of
(SOME bool, SOME unit) => SOME unit
| _ => NONE )
(* ****************** *)
(* testing machinery *)
(* ****************** *)
;
load "Listsort";
load "Int";
load "Bool";
(* pretty print expressions *)
fun prettyprintop Plus = "+"
| prettyprintop GTEQ = ">="
fun prettyprintexpr (Integer n) = Int.toString n
| prettyprintexpr (Boolean b) = Bool.toString b
| prettyprintexpr (Op (e1,opr,e2))
= "(" ^ (prettyprintexpr e1) ^ (prettyprintop opr)
^ (prettyprintexpr e2) ^ ")"
| prettyprintexpr (If (e1,e2,e3))
= "if " ^ (prettyprintexpr e1 ) ^ " then " ^ (prettyprintexpr e2)
^ " else " ^ (prettyprintexpr e3)
| prettyprintexpr (Deref l) = "!" ^ l
| prettyprintexpr (Assign (l,e)) = l ^ ":=" ^ (prettyprintexpr e )
| prettyprintexpr (Skip) = "skip"
| prettyprintexpr (Seq (e1,e2)) = (prettyprintexpr e1 ) ^ ";"
^ (prettyprintexpr e2)
| prettyprintexpr (While (e1,e2)) = "while " ^ (prettyprintexpr e1 )
^ " do " ^ (prettyprintexpr e2)
(* pretty print stores, first sorting by location names for readability *)
fun rawprintstore [] = ""
| rawprintstore ((l,n)::pairs) = l ^ "=" ^ (Int.toString n)
^ " " ^ (rawprintstore pairs)
fun prettyprintstore pairs =
let val pairs' = Listsort.sort
(fn ((l,n),(l',n')) => String.compare (l,l'))
pairs
in
"{" ^ rawprintstore pairs' ^ "}" end
(* pretty print configurations *)
fun prettyprintconfig (e,s) = "< " ^ (prettyprintexpr e)
^ " , " ^ (prettyprintstore s) ^ " >"
(* do a reduction sequence, printing the initial state and the state
after each reduction step *)
fun prettyreduce' (e,s) =
case reduce (e,s) of
SOME (e',s') =>
( TextIO.print ("\n --> " ^ prettyprintconfig (e',s') ) ;
prettyreduce' (e',s'))
| NONE => (TextIO.print "\n -/-> " ;
if is_value e then
TextIO.print "(a value)\n"
else
TextIO.print "(stuck - not a value)" )
fun prettyreduce (e,s) = (TextIO.print (" "^(prettyprintconfig (e,s))) ;
prettyreduce' (e,s))
(* **************)
(* simple tests *)
(* **************)
fun doit () = prettyreduce (Seq( Assign ("l1",Integer 3), Deref "l1"), [("l1",0)] )
(*
infertype [("l1",intref)] (Seq( Assign ("l1",Integer 3), Deref "l1"));;
*)
fun doit2 () = infertype [("l1",intref)] (Seq( Assign ("l1",Integer 3), Deref "l1"));