-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathcon.scrbl
More file actions
309 lines (227 loc) · 8.86 KB
/
con.scrbl
File metadata and controls
309 lines (227 loc) · 8.86 KB
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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
#lang scribble/manual
@(require (for-label (except-in racket compile ...) a86/printer a86/ast))
@(require redex/pict
racket/runtime-path
scribble/examples
#;(except-in "con/semantics.rkt" ext lookup)
#;(prefix-in sem: (only-in "con/semantics.rkt" ext lookup))
"utils.rkt"
"ev.rkt"
con/semantics
"../utils.rkt")
@(define codeblock-include (make-codeblock-include #'h))
@(ev '(require rackunit a86))
@(for-each (λ (f) (ev `(require (file ,(path->string (build-path langs "con" f))))))
'("main.rkt" "random.rkt" "correct.rkt"))
@title[#:tag "Con"]{Con: branching with conditionals}
@src-code["con"]
@emph{When you come to a fork in the road, take it.}
@table-of-contents[]
@section{Conditional execution}
Let's now consider adding a notion of @bold{conditionals} to our target
language.
We'll call it @bold{Con}.
We will use the following concrete syntax: @racket[(if (zero? _e0) _e1 _e2)].
This leads to the following grammar for concrete Con:
@centered{@render-language[C-concrete]}
And abstract grammar:
@codeblock-include["con/ast.rkt"]
@;{
We will also need a predicate for well-formed Con expressions, but
let's return to this after considering the semantics and interpreter.
}
The parser is similar to what we've seen before:
@codeblock-include["con/parse.rkt"]
@ex[
(parse '(if (zero? 42) 1 2))
(parse '(if (zero? (sub1 1)) (add1 2) (sub1 7)))
(parse '(if (zero? 0) (if (zero? 1) 2 3) 4))]
@section{Meaning of Con programs}
The meaning of Con programs depends on the form of the expression and
the new form is an if-expression.
@itemlist[
@item{the meaning of a if expression @racket[(IfZero _e0 _e1 _e2)] is
the meaning of @racket[_e1] if the meaning of @racket[_e0] is 0 and is
the meaning of @racket[_e2] otherwise.}
]
Let's consider some examples (using concrete notation):
@itemlist[
@item{@racket[(if (zero? 0) (add1 2) 4)] means @racket[3].}
@item{@racket[(if (zero? 1) (add1 2) 4)] means @racket[4].}
@item{@racket[(if (zero? (if (zero? (sub1 1)) 1 0)) (add1 2) 4)] means @racket[4].}
@item{@racket[(if (zero? (add1 0)) (add1 2) (if (zero? (sub1 1)) 1 0))] means @racket[1].}
]
@;{The semantics is inductively defined as before. There are @emph{two}
new rules added for handling if-expressions: one for when the test
expression means @racket[0] and one for when it doesn't.
@(define ((rewrite s) lws)
(define lhs (list-ref lws 2))
(define rhs (list-ref lws 3))
(list "" lhs (string-append " " (symbol->string s) " ") rhs ""))
@(require (only-in racket add-between))
@(define-syntax-rule (show-judgment name i j)
(with-unquote-rewriter
(lambda (lw)
(build-lw (lw-e lw) (lw-line lw) (lw-line-span lw) (lw-column lw) (lw-column-span lw)))
(with-compound-rewriters (['+ (rewrite '+)]
['- (rewrite '–)]
['= (rewrite '=)]
['!= (rewrite '≠)])
(apply centered
(add-between
(build-list (- j i)
(λ (n) (begin (judgment-form-cases (list (+ n i)))
(render-judgment-form name))))
(hspace 4))))))
@(show-judgment 𝑪 0 3)
@(show-judgment 𝑪 3 5)
@;{
The heart of the semantics is an auxiliary relation, @render-term[C
𝑪𝒓], which relates an expression and an environement to the integer
the expression evaluates to (in the given environment):
@(define ((rewrite s) lws)
(define lhs (list-ref lws 2))
(define rhs (list-ref lws 3))
(list "" lhs (string-append " " (symbol->string s) " ") rhs ""))
@(require (only-in racket add-between))
@(define-syntax-rule (show-judgment name i j)
(with-unquote-rewriter
(lambda (lw)
(build-lw (lw-e lw) (lw-line lw) (lw-line-span lw) (lw-column lw) (lw-column-span lw)))
(with-compound-rewriters (['+ (rewrite '+)]
['- (rewrite '–)])
(apply centered
(add-between
(build-list (- j i)
(λ (n) (begin (judgment-form-cases (list (+ n i)))
(render-judgment-form name))))
(hspace 4))))))
@(show-judgment 𝑪𝒓 0 3)
@(show-judgment 𝑪𝒓 3 5)
It relies on two functions: one for extending an environment with a
variable binding and one for lookup up a variable binding in an
environment:
@centered{
@render-metafunction[sem:ext #:contract? #t]
@(with-atomic-rewriter
'undefined
"⊥"
(render-metafunction sem:lookup #:contract? #t))}
The operational semantics for Con is then defined as a binary relation
@render-term[C 𝑪], which says that @math{(e,i)} in @render-term[C 𝑪],
only when @math{e} evaluates to @math{i} in the empty environment
according to @render-term[C 𝑪𝒓]:
@(show-judgment 𝑪 0 1)
}
}
The semantics is defined by extending the interpreter to add a case
for if-expressions, which recursively evaluates the test expression
and branches based on its value.
@codeblock-include["con/interp.rkt"]
We can confirm the interpreter computes the right result for the
examples given earlier (using @racket[parse] to state the examples
with concrete notation):
@ex[
(interp (parse '(if (zero? 0) (add1 2) 4)))
(interp (parse '(if (zero? 1) (add1 2) 4)))
(interp (parse '(if (zero? (if (zero? (sub1 1)) 1 0)) (add1 2) 4)))
(interp (parse '(if (zero? (add1 0)) (add1 2) (if (zero? (sub1 1)) 1 0))))
]
@section{An Example of Con compilation}
Suppose we want to compile @racket[(if (zero? 8) 2 3)]...
We already know how to compile the @racket[8], @racket[2], and
@racket[3] part.
What needs to happen?
@itemlist[
@item{Execute the code for @racket[8] leaving the result in @racket['rax],}
@item{check whether @racket['rax] holds zero,}
@item{if it does, execute the code for @racket[2],}
@item{if it doesn't, execute the code for @racket[3].}
]
We can determine whether @racket[8] evaluates to @racket[0] using a
comparison instruction: @racket[(Cmp rax 0)]. To do the conditional
execution, we will need to jump to different parts of the code to
either execute the code for @racket[2] or @racket[3]. There are
several ways we could accomplish this, but we take the following
approach: immediately after the comparison, do a conditional jump to
the code for the then branch when zero. Should the jump not occur,
the next instructions will carry out the evaluation of the else
branch, then (unconditionally) jump over the then branch code.
To accomplish this, we will need two new labels: one for the then
branch code and one for the end of the then branch code. The
@racket[gensym] function can be used to generate symbols that have not
appeared before. @margin-note{Q: Why should we generate label names
here? What would go wrong if simply used labels like @racket['l0] and
@racket['l1]?}
In total, the code for this example would look like:
@racketblock[
(let ((l0 (gensym))
(l1 (gensym)))
(list (Mov 'rax 8)
(Cmp 'rax 0)
(Je l0)
(Mov 'rax 3)
(Jmp l1)
(Label l0)
(Mov rax 2)
(Label l1)))
]
@section{A Compiler for Con}
Notice that the @racket[(Mov 'rax 8)], @racket[(Mov rax 3)] and
@racket[(Mov rax 2)] parts are just the instructions generated by
compiling @racket[8], @racket[2] and @racket[3]. Generalizing from
this, we arrive at the following code for the compiler:
@racketblock[
(let ((l0 (gensym 'if))
(l1 (gensym 'if)))
(seq (compile-e e1)
(Cmp 'rax 0)
(Je l0)
(compile-e e3)
(Jmp l1)
(Label l0)
(compile-e e2)
(Label l1)))
]
This will require extending our use of a86 instructions; in
particular, we add @racket[Jmp], @racket[Je], and @racket[Cmp]
instructions.
The complete compiler code is:
@codeblock-include["con/compile.rkt"]
Let's take a look at a few examples:
@ex[
(define (show s)
(compile-e (parse s)))
(show '(if (zero? 8) 2 3))
(show '(if (zero? 0) 1 2))
(show '(if (zero? 0) (if (zero? 0) 8 9) 2))
(show '(if (zero? (if (zero? 2) 1 0)) 4 5))
]
And confirm they are running as expected:
@ex[
(exec (parse '(if (zero? 8) 2 3)))
(exec (parse '(if (zero? 0) 1 2)))
(exec (parse '(if (zero? 0) (if (zero? 0) 8 9) 2)))
(exec (parse '(if (zero? (if (zero? 2) 1 0)) 4 5)))
]
@section[#:tag-prefix "con"]{Correctness and random testing}
The statement of correctness follows the same outline as before:
@bold{Compiler Correctness}: @emph{For all @racket[e] @math{∈} @tt{Expr},
@racket[(interp e)] equals @racket[(exec e)].}
Again, we formulate correctness as a property that can be tested:
@codeblock-include["con/correct.rkt"]
Generating random Con programs is essentially the same as Blackmail
programs, and are provided in a @link["con/random.rkt"]{random.rkt}
module.
@ex[
(eval:alts (require "random.rkt") (void))
(random-expr)
(random-expr)
(random-expr)
(random-expr)
(for ([i (in-range 10)])
(check-compiler (random-expr)))
]
This compiler has continues to have the issues identified in
@secref{broken}, but appears correct in its implementation of
conditional expressions.