40
40
41
41
We have added multiple, disjoint types, but mostly swept issues of
42
42
errors under the rug by considering type mismatches as meaningless.
43
- Now let 's redesign the semantics to specify the error behavior of such
44
- programs.
43
+ But undefined behavior, while convenient from the compiler writer's
44
+ perspective, is the gateway to all kinds of bad outcomes for
45
+ programmers. Let's redesign the semantics to specify the error
46
+ behavior of such programs.
45
47
46
48
47
49
We'll call it @bold{@this-lang}.
@@ -134,18 +136,32 @@ results that may be given by the interpretation function:
134
136
Type mismatches can arise as the result of primitive operations being
135
137
applied to arguments for which the primitive is undefined, so we
136
138
revise @racket[interp-prim1] to check all necessary preconditions
137
- before carrying out an operation, and producing an error in case
138
- those conditions are not met:
139
+ before carrying out an operation.
140
+
141
+ We will take advantage of Racket's exception system to @racket[raise]
142
+ an error result whenever an error occurs. This is a nice way of
143
+ shortcircuiting the remaining evaluation since as soon as we encounter
144
+ an error, we know this is going to be the answer for the whole
145
+ program.
139
146
140
147
@codeblock-include["extort/interp-prim.rkt " ]
141
148
142
- Within the interpreter, we update the type signature to reflect the
143
- fact that interpreting an expression produces an answer, no longer
144
- just an expression. We must also take care to observe that evaluating
145
- a subexpression may produce an error and as such it should prevent
146
- further evaluation. To do this , the interpreter is written to check
147
- for an error result of any subexpression it evaluates before
148
- proceeding to evaluate another subexpression:
149
+ Notice that the signature for these functions indicate that you get a
150
+ value, or they raise an exception. The functions for interpreting
151
+ primitives check all of the necessary preconditions of a primitive
152
+ before calling the corresponding Racket function. This ensures that
153
+ the Racket functions are always @emph{safe}, they cannot raise an
154
+ error. As a final catch-all case , we know that some precondition must
155
+ not have held and we can raise the @racket['err ] answer to indicate an
156
+ error.
157
+
158
+ Within the interpreter, we restructure things so that there is now a
159
+ top-level @racket[interp] function that installs an exception handler.
160
+ Should interpreting the expression raise @racket['err ], it handles
161
+ this exception and returns the @racket['err ] answer. The
162
+ @racket[interp] function makes use of an auxilary function
163
+ @racket[interp-e] that does the work of recursively interpreting the
164
+ meaning of an expression:
149
165
150
166
@codeblock-include["extort/interp.rkt " ]
151
167
@@ -158,6 +174,16 @@ examples given earlier:
158
174
(interp (parse '(if (zero? #f ) 1 2 )))
159
175
]
160
176
177
+ Note that if you use the @racket[interp-e] function, expressions that
178
+ cause errors will raise an exception:
179
+
180
+ @ex[
181
+ (eval:error (interp-e (parse '(add1 #f ))))
182
+ (eval:error (interp-e (parse '(zero? #t ))))
183
+ (eval:error (interp-e (parse '(if (zero? #f ) 1 2 ))))
184
+ ]
185
+
186
+
161
187
This interpreter implicitly relies on the state of the input and
162
188
output port, but we can define a pure interpreter like before,
163
189
which we take as the specification of our language:
@@ -376,7 +402,6 @@ Linking in the run-time allows us to define the @racket[exec] and
376
402
@racket[exec/io] functions:
377
403
378
404
@codeblock-include["extort/exec.rkt " ]
379
- @codeblock-include["extort/exec-io.rkt " ]
380
405
381
406
We can run examples:
382
407
0 commit comments