-
Notifications
You must be signed in to change notification settings - Fork 0
/
interpreter.lisp
398 lines (359 loc) · 15.5 KB
/
interpreter.lisp
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
(defpackage :interpreter
(:use :common-lisp)
(:export #:interpret))
(in-package #:interpreter)
(defstruct lox-class name superclass methods)
(defun create-lox-class (name superclass methods)
(make-lox-class :name name :superclass superclass :methods methods))
(defun find-class-method (class name)
(let ((method (gethash name (lox-class-methods class) :not-found))
(superclass (lox-class-superclass class)))
(if (eq :not-found method)
(if (null superclass)
:not-found
(find-class-method superclass name))
method)))
(defstruct lox-instance class fields)
(defun create-lox-instance (class)
(make-lox-instance :class class :fields (make-hash-table :test #'equal)))
(defun get-instance-value (instance name-token)
(let* ((name (lexer:token-lexeme name-token))
(fields (lox-instance-fields instance))
(existing-value (gethash name fields :not-found))
(existing-method (find-class-method (lox-instance-class instance) name)))
(if (eq existing-value :not-found)
(if (eq existing-method :not-found)
(error "Undefined property '~a'." name)
(let* ((env (environment:create-env-with-enclosing (lox-function-closure existing-method)))
(env (environment:define-with-name env "this" instance)))
(create-lox-function (lox-function-params existing-method) (lox-function-body existing-method)
env (lox-function-is-initializer existing-method))))
existing-value)))
(defun set-instance-value (instance name-token value)
(let ((name (lexer:token-lexeme name-token))
(fields (lox-instance-fields instance)))
(setf (gethash name fields) value)))
(define-condition lox-return (error)
((value :initarg :value :reader value)))
(defstruct lox-function arity call closure params body is-initializer)
(defun create-lox-function (params body closure is-initializer)
(let ((arity (lambda () (length params)))
(call (lambda (arguments)
(let ((env (environment:create-env-with-enclosing closure)))
(labels ((helper (params arguments env)
(if (not (null params))
(helper
(cdr params)
(cdr arguments)
(environment:define-with-name env (lexer:token-lexeme (car params)) (car arguments)))
env)))
(let ((env (helper params arguments env)))
(if is-initializer
(progn
(execute-block body env)
; return this in init
(environment:get-value env (lexer:create-token 'this "this" nil 0)))
(handler-case
(execute-block body env)
; Code doesn't currently gracefully prevent returning in an initializer
(lox-return (c)
(value c))))))))))
(make-lox-function :arity arity :call call :closure closure :params params :body body :is-initializer is-initializer)))
(defun call (callable arguments)
(case (type-of callable)
((lox-function) (funcall (lox-function-call callable) arguments))
((lox-class)
(let* ((instance (create-lox-instance callable))
(initializer (find-class-method callable "init")))
(if (eq initializer :not-found)
instance
(let* ((env (environment:create-env-with-enclosing (lox-function-closure initializer)))
(env (environment:define-with-name env "this" instance)))
(progn
(call (create-lox-function (lox-function-params initializer) (lox-function-body initializer) env T) arguments)
instance)))))
(t (error "Invalid callable type for call"))))
(defun arity (callable)
(case (type-of callable)
((lox-function) (funcall (lox-function-arity callable)))
((lox-class)
(let ((initializer (find-class-method callable "init")))
(if (eq initializer :not-found)
0
(arity initializer))))
(t (error "Invalid callable type for arity ~a" callable))))
(ast:defvisit literal (value) (list value ast:env))
(ast:defvisit grouping (expression)
(let* ((result (accept expression ast:env))
(value (first result))
(env (second result)))
(list value env)))
(ast:defvisit unary (operator right)
(let* ((result (accept right ast:env))
(right (first result))
(env (second result))
(type (symbol-name (lexer:token-type operator))))
(cond
((string= "MINUS" type)
(progn
(check-number right)
(list (- right) env)))
((string= "BANG" type)
(list (not (is-truthy right)) env))
(t nil))))
(ast:defvisit binary (left operator right)
(let* ((left-result (accept left ast:env))
(right-result (accept right (second left-result)))
(left (first left-result))
(right (first right-result))
(env (second right-result))
(type (symbol-name (lexer:token-type operator))))
(cond
((string= "GREATER" type)
(progn
(check-numbers left right)
(list (> left right) env)))
((string= "GREATER-EQUAL" type)
(progn
(check-numbers left right)
(list (>= left right) env)))
((string= "LESS" type)
(progn
(check-numbers left right)
(list (< left right) env)))
((string= "LESS-EQUAL" type)
(progn
(check-numbers left right)
(list (<= left right) env)))
((string= "BANG-EQUAL" type) (not (is-equal left right)))
((string= "EQUAL-EQUAL" type) (is-equal left right))
((string= "MINUS" type)
(progn
(check-numbers left right)
(list (- left right) env)))
((string= "PLUS" type)
(cond
((and (stringp left) (stringp right)) (list (concatenate 'string left right) env))
((and (numberp left) (numberp right))
(progn
(check-numbers left right)
(list (+ left right) env)))
(t (error "Operands must be two numbers or two strings. Actual: ~a ~a" left right))))
((string= "SLASH" type)
(progn
(check-numbers left right)
(list (/ left right) env)))
((string= "STAR" type)
(progn
(check-numbers left right)
(list (* left right) env)))
(t nil))))
(ast:defvisit expression-stmt (expression)
(let* ((result (accept expression ast:env))
(env (second result)))
(list nil env)))
(ast:defvisit print-stmt (expression)
(let* ((result (accept expression ast:env))
(value (first result))
(env (second result)))
(list (print value) env)))
(ast:defvisit variable-decl (name initializer)
(if (null initializer)
(list nil (environment:define ast:env name nil))
(let* ((result (accept initializer ast:env))
(value (first result))
(env (second result)))
(list nil (environment:define env name value)))))
(ast:defvisit variable-ref (name)
(list (environment:get-value ast:env name) ast:env))
(ast:defvisit this (keyword)
(list (environment:get-value ast:env keyword) ast:env))
(ast:defvisit assign (name expression)
(let* ((result (accept expression ast:env))
(value (first result))
(env (second result)))
(progn
; assign is modifying the current env inplace
(environment:assign env name value)
(list value env))))
(ast:defvisit block-stmt (statements)
(execute-block statements (environment:create-env-with-enclosing ast:env)))
(ast:defvisit if-stmt (condition then-branch else-branch)
(let* ((result (accept condition ast:env))
(condition (first result))
(env (second result)))
(if (is-truthy condition)
(accept then-branch env)
(if (not (null else-branch))
(accept else-branch env)
(list nil env)))))
(ast:defvisit while-stmt (condition body)
(let* ((result (accept condition ast:env))
(condition (first result))
(env (second result)))
(if (is-truthy condition)
(let* ((result (accept body env))
(env (second result)))
(visit-while-stmt ast:obj env)))
(list nil env)))
(ast:defvisit logical (left operator right)
(let* ((result (accept left ast:env))
(left (first result))
(env (second result)))
(if (string= (lexer:token-type operator) 'or)
(if (is-truthy left)
(list left env)
(accept right env))
(if (not (is-truthy left))
(list left env)
(accept right env)))))
(ast:defvisit call (callee arguments)
(let* ((result (accept callee ast:env))
(callee (first result))
(env (second result))
(func (lambda (argument) (accept argument env)))
(arguments (mapcar #'first (mapcar func arguments))))
(if (eq (length arguments) (arity callee))
(list (call callee arguments) env)
(error "Expected ~a arguments but got ~a." (arity callee) (length arguments)))))
(ast:defvisit class-decl (name superclass methods)
(let* ((env (environment:define ast:env name "placeholder"))
(methods-table (make-hash-table :test #'equal))
(evaled-superclass
(if (null superclass)
nil
(let ((superclass (first (accept superclass env))))
(if (not (lox-class-p superclass))
(error "Superclass must be a class")
superclass))))
(env (if (null evaled-superclass)
env
(environment:define-with-name (environment:create-env-with-enclosing env) "super" evaled-superclass))))
(progn
(dolist (method methods)
(let ((func (create-lox-function (ast:function-decl-params method) (ast:function-decl-body method) env
(string= (lexer:token-lexeme (ast:function-decl-name method)) "init"))))
(setf (gethash (lexer:token-lexeme (ast:function-decl-name method)) methods-table) func)))
(let ((env (if (not (null superclass))
(environment:environment-enclosing env)
env)))
(environment:assign env name (create-lox-class name evaled-superclass methods-table))
(list nil env)))))
(ast:defvisit super (keyword method)
(let* ((superclass (environment:get-value ast:env keyword))
; Might need to get from enclosing
(instance (environment:get-value ast:env (lexer:create-token 'this "this" nil 0)))
(evaled-method (find-class-method superclass (lexer:token-lexeme method))))
(if (eq evaled-method :not-found)
(error "Undefined property '~a'" (lexer:token-lexeme method))
(let* ((env (environment:create-env-with-enclosing (lox-function-closure evaled-method)))
(env (environment:define-with-name env "this" instance)))
(list (create-lox-function (lox-function-params evaled-method) (lox-function-body evaled-method) env (lox-function-is-initializer evaled-method)) ast:env)))))
(ast:defvisit function-decl (name params body)
(let* ((env (environment:define ast:env name "placeholder"))
(func (create-lox-function params body env nil)))
(progn
(environment:assign env name func)
(list nil env))))
(ast:defvisit get-expr (object name)
(let* ((result (accept object ast:env))
(object (first result))
(env (second result)))
(if (lox-instance-p object)
(list (get-instance-value object name) env)
(error "Only instances have properties"))))
(ast:defvisit set-expr (object name value)
(let* ((result (accept object ast:env))
(object (first result))
(env (second result)))
(if (not (lox-instance-p object))
(error "Only instances have fields")
(let* ((result (accept value env))
(value (first result))
(env (second result)))
(progn
(set-instance-value object name value)
(list value env))))))
(ast:defvisit return-stmt (expression)
(let ((value
(if (null expression)
nil
(accept expression ast:env))))
; Do we need to pass env in here? I don't think so...
(error 'lox-return :value (car value))))
(defun execute-block (statements new-env)
(labels ((helper (statements env)
(if (null statements)
env
(let* ((result (accept (car statements) env))
(env (second result)))
(helper (cdr statements) env)))))
(let* ((env (helper statements new-env)))
(list nil (environment:environment-enclosing env)))))
(defun check-number (operand)
(if (numberp operand)
T
; TODO: Make this more informative, I just can't be bothered right now.
(error "Operand must be a number")))
(defun check-numbers (left right)
(if (and (numberp left) (numberp right))
T
; TODO: Make this more informative, I just can't be bothered right now.
(error "Operands must be a number")))
(defun is-equal (a b)
(cond
((and (null a) (null b)) T)
((or (null a) (null b)) nil)
((and (stringp a) (stringp b)) (string= a b))
(t (eq a b))))
(defun is-truthy (value)
(if (null value)
nil
(if (symbolp value)
(let ((name (symbol-name value)))
(cond
((string= name "TRUE") T)
((string= name "FALSE") nil)
(t T)))
T)))
(defun visit-interpreter (object env)
"Implements the operation for OBJECT using the visitor pattern."
(case (type-of object)
((ast:expression-stmt) (visit-expression-stmt object env))
((ast:print-stmt) (visit-print-stmt object env))
((ast:while-stmt) (visit-while-stmt object env))
((ast:variable-decl) (visit-variable-decl object env))
((ast:function-decl) (visit-function-decl object env))
((ast:class-decl) (visit-class-decl object env))
((ast:get-expr) (visit-get-expr object env))
((ast:set-expr) (visit-set-expr object env))
((ast:variable-ref) (visit-variable-ref object env))
((ast:this) (visit-this object env))
((ast:super) (visit-super object env))
((ast:block-stmt) (visit-block-stmt object env))
((ast:return-stmt) (visit-return-stmt object env))
((ast:if-stmt) (visit-if-stmt object env))
((ast:call) (visit-call object env))
((ast:logical) (visit-logical object env))
((ast:assign) (visit-assign object env))
((ast:binary) (visit-binary object env))
((ast:unary) (visit-unary object env))
((ast:grouping) (visit-grouping object env))
((ast:literal) (visit-literal object env))))
(defun accept (obj env)
(ast:accept obj env #'visit-interpreter))
(defun interpret (expressions)
(let* ((globals
(environment:define-with-name (environment:create-env) "clock"
(make-lox-function
:arity (lambda () 0)
:call (lambda (arguments) (/ (get-internal-real-time) 1000))
:closure nil)))
(env (environment:create-env-with-enclosing globals)))
(labels ((helper (expressions env)
(if (null expressions)
nil
(let* ((result (accept (car expressions) env))
(env (second result)))
(helper (cdr expressions) env)))))
(helper expressions env))))