-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpreter2-callcc-no-boxes.rkt
509 lines (433 loc) · 24 KB
/
interpreter2-callcc-no-boxes.rkt
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
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
; If you are using scheme instead of racket, comment these two lines, uncomment the (load "simpleParser.scm") and comment the (require "simpleParser.rkt")
#lang racket
(provide (all-defined-out))
;(require "simpleParser.rkt")
(require "functionParser.rkt")
; (load "simpleParser.scm")
; An interpreter for the simple language that uses call/cc for the continuations. Does not handle side effects.
(define call/cc call-with-current-continuation)
; The functions that start interpret-... all return the current environment.
; The functions that start eval-... all return a value
; The main function. Calls parser to get the parse tree and interprets it with a new environment. The returned value is in the environment.
(define interpret
(lambda (file)
(scheme->language
(return-value (interpret-call-function '(funcall main) (create-base-layer (parser file) (newenvironment) (lambda (v env) (myerror "Uncaught exception thrown")))
(lambda (v env) (myerror "Uncaught exception thrown")))))))
; does the first pass through of the parsed code to put the global variables and functions in the base layer of the state
(define create-base-layer
(lambda (statement-list environment throw)
(if (null? statement-list)
environment
(create-base-layer (cdr statement-list) (base-layer-interpret-statement (car statement-list) environment throw) throw))))
; interpret a statement in the first pass through of the parsed code
(define base-layer-interpret-statement
(lambda (statement environment throw)
(cond
((eq? 'var (statement-type statement)) (interpret-declare statement environment throw))
((eq? '= (statement-type statement)) (interpret-assign statement environment throw))
((eq? 'function (statement-type statement)) (interpret-declare-function statement environment))
(else (myerror "Unknown statement:" (statement-type statement))))))
; interprets the function declaration and builds the function binding
; interprets the function declaration and builds the function binding
(define interpret-declare-function
(lambda (statement environment)
(insert (get-function-name statement)
(make-closure (get-function-formal-parameters statement)
(get-function-body statement)
(delayed-environment-builder (get-function-name statement))) ; attach function name in env to enable recursion
environment)))
(define delayed-environment-builder
(lambda (func-name)
(lambda (future-environment)
(rebuild-environment-from-func future-environment func-name))))
(define rebuild-environment-from-func
(lambda (environment func-name)
(if
(exists-in-list? func-name (variables (topframe environment)))
environment
(rebuild-environment-from-func (pop-frame environment) func-name))))
; temporary not-recursion
; closure is a list of formal parameters, function body, and environment
(define make-closure list)
; interprets a list of statements. The environment from each statement is used for the next ones.
; Mstate (<statement><statement-list>, state) = Mstate(<statement-list>, Mstate(<statement>, state))
(define interpret-statement-list
(lambda (statement-list environment return break continue throw)
(if (null? statement-list)
environment
(interpret-statement-list (cdr statement-list) (interpret-statement (car statement-list) environment return break continue throw) return break continue throw))))
; interpret a statement in the environment with continuations for return, break, continue, throw
(define interpret-statement
(lambda (statement environment return break continue throw)
(cond
((eq? 'return (statement-type statement)) (interpret-return statement environment return throw))
((eq? 'var (statement-type statement)) (interpret-declare statement environment throw))
((eq? '= (statement-type statement)) (interpret-assign statement environment throw))
((eq? 'if (statement-type statement)) (interpret-if statement environment return break continue throw))
((eq? 'while (statement-type statement)) (interpret-while statement environment return throw))
((eq? 'continue (statement-type statement)) (continue environment))
((eq? 'break (statement-type statement)) (break environment))
((eq? 'begin (statement-type statement)) (interpret-block statement environment return break continue throw))
((eq? 'throw (statement-type statement)) (interpret-throw statement environment throw))
((eq? 'try (statement-type statement)) (interpret-try statement environment return break continue throw))
((eq? 'funcall (statement-type statement)) (update-environment (pop-frame (return-environment (interpret-call-function statement environment throw))) environment))
((eq? 'function (statement-type statement)) (interpret-declare-function statement environment))
(else (myerror "Unknown statement:" (statement-type statement))))))
(define update-environment
(lambda (function-environment previous-environment)
(cond
((null? function-environment) previous-environment)
(else (update-environment (remainingframes function-environment) (update-frame (variables (topframe function-environment)) (store (topframe function-environment)) previous-environment))))))
(define update-frame
(lambda (function-var-list function-val-list previous-environment)
(if (null? function-var-list)
previous-environment
(cond
((exists? (car function-var-list) previous-environment)
(update-frame (cdr function-var-list) (cdr function-val-list)
(update (car function-var-list) (car function-val-list) previous-environment)))
(else (update-frame (cdr function-var-list) (cdr function-val-list) previous-environment))))))
; interprets a function call
; TODO: Fix the order of the interpret call statement. Stay potent.
(define interpret-call-function
(lambda (statement environment throw)
(call/cc
(lambda (return)
((lambda (function-closure)
(interpret-statement-list (append (get-closure-body function-closure) (list '(return 0)))
(bind-parameters (get-closure-params function-closure)
(get-funcall-actual-parameters statement)
(push-frame ((get-closure-environment function-closure) environment)) ; get-closure-environment might break! current implementation is just a list! recursion might break it for real!!
environment
throw)
;(lambda (s) (myerror "no return statement")) do we need this??? -probably not
return
(lambda (env) (myerror "Break used outside of loop"))
(lambda (env) (myerror "Continue used outside of loop"))
throw))
(lookup (get-function-name statement) environment))))))
; binds function input values to formal parameters
(define bind-parameters
(lambda (formal-params argument-list fstate environment throw)
(if (eq? (length formal-params) (length argument-list))
(if (null? formal-params)
fstate
(bind-parameters (cdr formal-params)
(cdr argument-list)
(insert (car formal-params) (eval-expression (car argument-list) environment throw) fstate)
environment
throw))
(myerror "argument-mismatch"))))
; Calls the return continuation with the given expression value
(define interpret-return
(lambda (statement environment return throw)
(return (list (eval-expression (get-expr statement) environment throw) environment))))
(define return-value car)
(define return-environment cadr)
; Adds a new variable binding to the environment. There may be an assignment with the variable
(define interpret-declare
(lambda (statement environment throw)
(if (exists-declare-value? statement)
(insert (get-declare-var statement) (eval-expression (get-declare-value statement) environment throw) environment)
(insert (get-declare-var statement) 'novalue environment))))
; Updates the environment to add an new binding for a variable
(define interpret-assign
(lambda (statement environment throw)
(update (get-assign-lhs statement) (eval-expression (get-assign-rhs statement) environment throw) environment)))
; We need to check if there is an else condition. Otherwise, we evaluate the expression and do the right thing.
(define interpret-if
(lambda (statement environment return break continue throw)
(cond
((eval-expression (get-condition statement) environment throw) (interpret-statement (get-then statement) environment return break continue throw))
((exists-else? statement) (interpret-statement (get-else statement) environment return break continue throw))
(else environment))))
; Interprets a while loop. We must create break and continue continuations for this loop
(define interpret-while
(lambda (statement environment return throw)
(call/cc
(lambda (break)
(letrec ((loop (lambda (condition body environment)
(if (eval-expression condition environment throw)
(loop condition body (interpret-statement body environment return break
(lambda (env) (break (loop condition body env))) throw))
environment))))
(loop (get-condition statement) (get-body statement) environment))))))
; Interprets a block. The break, continue, and throw continuations must be adjusted to pop the environment
; Mstate({ <body> }, state) = pop-frame (Mstate (<body>, pushframe(state)))
(define interpret-block
(lambda (statement environment return break continue throw)
(pop-frame (interpret-statement-list (cdr statement)
(push-frame environment)
return
(lambda (env) (break (pop-frame env)))
(lambda (env) (continue (pop-frame env)))
(lambda (v env) (throw v (pop-frame env)))))))
; We use a continuation to throw the proper value. Because we are not using boxes, the environment/state must be thrown as well so any environment changes will be kept
(define interpret-throw
(lambda (statement environment throw)
(throw (eval-expression (get-expr statement) environment throw) environment)))
; Interpret a try-catch-finally block
; Create a continuation for the throw. If there is no catch, it has to interpret the finally block, and once that completes throw the exception.
; Otherwise, it interprets the catch block with the exception bound to the thrown value and interprets the finally block when the catch is done
(define create-throw-catch-continuation
(lambda (catch-statement environment return break continue throw jump finally-block)
(cond
((null? catch-statement) (lambda (ex env) (throw ex (interpret-block finally-block env return break continue throw))))
((not (eq? 'catch (statement-type catch-statement))) (myerror "Incorrect catch statement"))
(else (lambda (ex env)
(jump (interpret-block finally-block
(pop-frame (interpret-statement-list
(get-body catch-statement)
(insert (catch-var catch-statement) ex (push-frame env))
return
(lambda (env2) (break (pop-frame env2)))
(lambda (env2) (continue (pop-frame env2)))
(lambda (v env2) (throw v (pop-frame env2)))))
return break continue throw)))))))
; To interpret a try block, we must adjust the return, break, continue continuations to interpret the finally block if any of them are used.
; We must create a new throw continuation and then interpret the try block with the new continuations followed by the finally block with the old continuations
(define interpret-try
(lambda (statement environment return break continue throw)
(call/cc
(lambda (jump)
(let* ((finally-block (make-finally-block (get-finally statement)))
(try-block (make-try-block (get-try statement)))
(new-return (lambda (v) (begin (interpret-block finally-block environment return break continue throw) (return v))))
(new-break (lambda (env) (break (interpret-block finally-block env return break continue throw))))
(new-continue (lambda (env) (continue (interpret-block finally-block env return break continue throw))))
(new-throw (create-throw-catch-continuation (get-catch statement) environment return break continue throw jump finally-block)))
(interpret-block finally-block
(interpret-block try-block environment new-return new-break new-continue new-throw)
return break continue throw))))))
; helper methods so that I can reuse the interpret-block method on the try and finally blocks
(define make-try-block
(lambda (try-statement)
(cons 'begin try-statement)))
(define make-finally-block
(lambda (finally-statement)
(cond
((null? finally-statement) '(begin))
((not (eq? (statement-type finally-statement) 'finally)) (myerror "Incorrectly formatted finally block"))
(else (cons 'begin (cadr finally-statement))))))
; Evaluates all possible boolean and arithmetic expressions, including constants and variables.
(define eval-expression
(lambda (expr environment throw)
(cond
((number? expr) expr)
((eq? expr 'true) #t)
((eq? expr 'false) #f)
((not (list? expr)) (lookup expr environment))
((eq? 'funcall (statement-type expr)) (return-value (interpret-call-function expr environment throw)))
(else (eval-operator expr environment throw)))))
; Evaluate a binary (or unary) operator. Although this is not dealing with side effects, I have the routine evaluate the left operand first and then
; pass the result to eval-binary-op2 to evaluate the right operand. This forces the operands to be evaluated in the proper order in case you choose
; to add side effects to the interpreter
(define eval-operator
(lambda (expr environment throw)
(cond
((eq? '! (operator expr)) (not (eval-expression (operand1 expr) environment throw)))
((and (eq? '- (operator expr)) (= 2 (length expr))) (- (eval-expression (operand1 expr) environment throw)))
(else (eval-binary-op2 expr (eval-expression (operand1 expr) environment throw) environment throw)))))
; Complete the evaluation of the binary operator by evaluating the second operand and performing the operation.
(define eval-binary-op2
(lambda (expr op1value environment throw)
(cond
((eq? '+ (operator expr)) (+ op1value (eval-expression (operand2 expr) environment throw)))
((eq? '- (operator expr)) (- op1value (eval-expression (operand2 expr) environment throw)))
((eq? '* (operator expr)) (* op1value (eval-expression (operand2 expr) environment throw)))
((eq? '/ (operator expr)) (quotient op1value (eval-expression (operand2 expr) environment throw)))
((eq? '% (operator expr)) (remainder op1value (eval-expression (operand2 expr) environment throw)))
((eq? '== (operator expr)) (isequal op1value (eval-expression (operand2 expr) environment throw)))
((eq? '!= (operator expr)) (not (isequal op1value (eval-expression (operand2 expr) environment throw))))
((eq? '< (operator expr)) (< op1value (eval-expression (operand2 expr) environment throw)))
((eq? '> (operator expr)) (> op1value (eval-expression (operand2 expr) environment throw)))
((eq? '<= (operator expr)) (<= op1value (eval-expression (operand2 expr) environment throw)))
((eq? '>= (operator expr)) (>= op1value (eval-expression (operand2 expr) environment throw)))
((eq? '|| (operator expr)) (or op1value (eval-expression (operand2 expr) environment throw)))
((eq? '&& (operator expr)) (and op1value (eval-expression (operand2 expr) environment throw)))
(else (myerror "Unknown operator:" (operator expr))))))
; Determines if two values are equal. We need a special test because there are both boolean and integer types.
(define isequal
(lambda (val1 val2)
(if (and (number? val1) (number? val2))
(= val1 val2)
(eq? val1 val2))))
;-----------------
; HELPER FUNCTIONS
;-----------------
; These helper functions define the operator and operands of a value expression
(define operator car)
(define operand1 cadr)
(define operand2 caddr)
(define operand3 cadddr)
(define operand3-to-end cddr)
(define exists-operand2?
(lambda (statement)
(not (null? (cddr statement)))))
(define exists-operand3?
(lambda (statement)
(not (null? (cdddr statement)))))
; these helper functions define the parts of the various statement types
(define statement-type operator)
(define get-expr operand1)
(define get-declare-var operand1)
(define get-declare-value operand2)
(define exists-declare-value? exists-operand2?)
(define get-assign-lhs operand1)
(define get-assign-rhs operand2)
(define get-condition operand1)
(define get-then operand2)
(define get-else operand3)
(define get-body operand2)
(define exists-else? exists-operand3?)
(define get-try operand1)
(define get-catch operand2)
(define get-finally operand3)
(define get-function-name operand1)
(define get-function-formal-parameters operand2)
(define get-function-body operand3)
(define get-closure-params operator)
(define get-closure-body operand1)
(define get-closure-environment operand2)
(define get-funcall-actual-parameters operand3-to-end)
(define catch-var
(lambda (catch-statement)
(car (operand1 catch-statement))))
;------------------------
; Environment/State Functions
;------------------------
; create a new empty environment
(define newenvironment
(lambda ()
(list (newframe))))
; create an empty frame: a frame is two lists, the first are the variables and the second is the "store" of values
(define newframe
(lambda ()
'(() ())))
; add a frame onto the top of the environment
(define push-frame
(lambda (environment)
(cons (newframe) environment)))
; remove a frame from the environment
(define pop-frame
(lambda (environment)
(cdr environment)))
; some abstractions
(define topframe car)
(define remainingframes cdr)
; does a variable exist in the environment?
(define exists?
(lambda (var environment)
(cond
((null? environment) #f)
((exists-in-list? var (variables (topframe environment))) #t)
(else (exists? var (remainingframes environment))))))
; does a variable exist in a list?
(define exists-in-list?
(lambda (var l)
(cond
((null? l) #f)
((eq? var (car l)) #t)
(else (exists-in-list? var (cdr l))))))
; Looks up a value in the environment. If the value is a boolean, it converts our languages boolean type to a Scheme boolean type
(define lookup
(lambda (var environment)
(lookup-variable var environment)))
; A helper function that does the lookup. Returns an error if the variable does not have a legal value
(define lookup-variable
(lambda (var environment)
(let ((value (lookup-in-env var environment)))
(if (eq? 'novalue value)
(myerror "error: variable without an assigned value:" var)
value))))
; Return the value bound to a variable in the environment
(define lookup-in-env
(lambda (var environment)
(cond
((null? environment) (myerror "error: undefined variable" var))
((exists-in-list? var (variables (topframe environment))) (lookup-in-frame var (topframe environment)))
(else (lookup-in-env var (cdr environment))))))
; Return the value bound to a variable in the frame
(define lookup-in-frame
(lambda (var frame)
(cond
((not (exists-in-list? var (variables frame))) (myerror "error: undefined variable" var))
(else (language->scheme (get-value (indexof var (variables frame)) (store frame)))))))
; Get the location of a name in a list of names
(define indexof
(lambda (var l)
(cond
((null? l) 0) ; should not happen
((eq? var (car l)) 0)
(else (+ 1 (indexof var (cdr l)))))))
; Get the value stored at a given index in the list
(define get-value
(lambda (n l)
(cond
((zero? n) (car l))
(else (get-value (- n 1) (cdr l))))))
; Adds a new variable/value binding pair into the environment. Gives an error if the variable already exists in this frame.
(define insert
(lambda (var val environment)
(if (exists-in-list? var (variables (car environment)))
(myerror "error: variable is being re-declared:" var)
(cons (add-to-frame var val (car environment)) (cdr environment)))))
; Changes the binding of a variable to a new value in the environment. Gives an error if the variable does not exist.
(define update
(lambda (var val environment)
(if (exists? var environment)
(update-existing var val environment)
(myerror "error: variable used but not defined:" var))))
; Add a new variable/value pair to the frame.
(define add-to-frame
(lambda (var val frame)
(list (cons var (variables frame)) (cons (scheme->language val) (store frame)))))
; Changes the binding of a variable in the environment to a new value
(define update-existing
(lambda (var val environment)
(if (exists-in-list? var (variables (car environment)))
(cons (update-in-frame var val (topframe environment)) (remainingframes environment))
(cons (topframe environment) (update-existing var val (remainingframes environment))))))
; Changes the binding of a variable in the frame to a new value.
(define update-in-frame
(lambda (var val frame)
(list (variables frame) (update-in-frame-store var val (variables frame) (store frame)))))
; Changes a variable binding by placing the new value in the appropriate place in the store
(define update-in-frame-store
(lambda (var val varlist vallist)
(cond
((eq? var (car varlist)) (cons (scheme->language val) (cdr vallist)))
(else (cons (car vallist) (update-in-frame-store var val (cdr varlist) (cdr vallist)))))))
; Returns the list of variables from a frame
(define variables
(lambda (frame)
(car frame)))
; Returns the store from a frame
(define store
(lambda (frame)
(cadr frame)))
; Functions to convert the Scheme #t and #f to our languages true and false, and back.
(define language->scheme
(lambda (v)
(cond
((eq? v 'false) #f)
((eq? v 'true) #t)
(else v))))
(define scheme->language
(lambda (v)
(cond
((eq? v #f) 'false)
((eq? v #t) 'true)
(else v))))
; Because the error function is not defined in R5RS scheme, I create my own:
(define error-break (lambda (v) v))
(call-with-current-continuation (lambda (k) (set! error-break k)))
(define myerror
(lambda (str . vals)
(letrec ((makestr (lambda (str vals)
(if (null? vals)
str
(makestr (string-append str (string-append " " (symbol->string (car vals)))) (cdr vals))))))
(error-break (display (string-append str (makestr "" vals)))))))
;(interpret "Tests3/Test 13.txt")