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
|
\ === sorted-array === /
\ Here are a few utility functions useful for creating and maintaining
\ the deftype* method tables. The keys array is kept in sorted order,
\ and the methods array is maintained in parallel so that an index into
\ one corresponds to an index in the other.
\ Search a sorted array for key, returning the index of where it was
\ found. If key is not in the array, return the index where it would
\ be if added.
: array-find { a-length a-addr key -- index found? }
0 a-length ( start end )
begin
\ cr 2dup . .
2dup + 2 / dup ( start end middle middle )
cells a-addr + @ ( start end middle mid-val )
dup key < if
drop rot ( end middle start )
2dup = if
2drop dup ( end end )
else
drop swap ( middle end )
endif
else
key > if ( start end middle )
nip ( start middle )
else
-rot 2drop dup ( middle middle )
endif
endif
2dup = until
cells a-addr + @ key =
;
\ Create a new array, one cell in length, initialized the provided value
: new-array { value -- array }
cell allocate throw value over ! ;
\ Resize a heap-allocated array to be one cell longer, inserting value
\ at idx, and shifting the tail of the array as necessary. Returns the
\ (possibly new) array address
: array-insert { old-array-length old-array idx value -- array }
old-array old-array-length 1+ cells resize throw
{ a }
a idx cells + dup cell+ old-array-length idx - cells cmove>
value a idx cells + !
a
;
\ === deftype* -- protocol-enabled structs === /
\ Each type has MalTypeType% struct allocated on the stack, with
\ mutable fields pointing to all class-shared resources, specifically
\ the data needed to allocate new instances, and the table of protocol
\ methods that have been extended to the type.
\ Use 'deftype*' to define a new type, and 'new' to create new
\ instances of that type.
struct
cell% field mal-type
\ cell% field ref-count \ Ha, right.
end-struct MalType%
struct
cell% 2 * field MalTypeType-struct
cell% field MalTypeType-methods
cell% field MalTypeType-method-keys
cell% field MalTypeType-method-vals
cell% field MalTypeType-name-addr
cell% field MalTypeType-name-len
end-struct MalTypeType%
: new ( MalTypeType -- obj )
dup MalTypeType-struct 2@ %allocate throw ( MalTypeType obj ) \ create struct
dup -rot mal-type ! ( obj ) \ set struct's type pointer to this type
;
: deftype* ( struct-align struct-len -- MalTypeType )
MalTypeType% %allot ( s-a s-l MalTypeType )
dup 2swap rot ( MalTypeType s-a s-l MalTypeType )
MalTypeType-struct 2! ( MalTypeType ) \ store struct info
dup MalTypeType-methods 0 swap ! ( MalTypeType )
dup MalTypeType-method-keys nil swap ! ( MalTypeType )
dup MalTypeType-method-vals nil swap ! ( MalTypeType )
dup MalTypeType-name-len 0 swap ! ( MalTypeType )
;
\ parse-name uses temporary space, so copy into dictionary stack:
: parse-allot-name { -- new-str-addr str-len }
parse-name { str-addr str-len }
here { new-str-addr } str-len allot
str-addr new-str-addr str-len cmove
new-str-addr str-len ;
: deftype ( struct-align struct-len R:type-name -- )
parse-allot-name { name-addr name-len }
\ allot and initialize type structure
deftype* { mt }
name-addr mt MalTypeType-name-addr !
name-len mt MalTypeType-name-len !
\ ." Defining " mt MalTypeType-name-addr @ mt MalTypeType-name-len @ type cr
mt name-addr name-len nextname 1 0 const-does> ;
: type-name ( mal-type )
dup MalTypeType-name-addr @ ( mal-type name-addr )
swap MalTypeType-name-len @ ( name-addr name-len )
;
MalType% deftype MalDefault
\ nil type and instance to support extending protocols to it
MalType% deftype MalNil MalNil new constant mal-nil
MalType% deftype MalTrue MalTrue new constant mal-true
MalType% deftype MalFalse MalFalse new constant mal-false
: mal-bool
0= if mal-false else mal-true endif ;
: not-object? ( obj -- bool )
dup 7 and 0 <> if
drop true
else
1000000 <
endif ;
\ === protocol methods === /
0 constant trace
\ Used by protocol methods to find the appropriate implementation of
\ themselves for the given object, and then execute that implementation.
: execute-method { obj pxt -- }
obj not-object? if
." Refusing to invoke protocol fn '"
pxt >name name>string type
." ' on non-object: " obj .
1 throw
endif
obj mal-type @ dup MalTypeType-methods 2@ swap ( type methods method-keys )
dup 0= if \ No protocols extended to this type; check for a default
2drop drop MalDefault MalTypeType-methods 2@ swap
endif
pxt array-find ( type idx found? )
dup 0= if \ No implementation found for this method; check for a default
2drop drop MalDefault dup MalTypeType-methods 2@ swap
pxt array-find ( type idx found? )
endif
0= if ( type idx )
2drop
." No protocol fn '"
pxt >name name>string type
." ' extended to type '"
obj mal-type @ type-name type
." '" cr
1 throw
endif
trace if ." Calling '" pxt >name name>string type ." ' on " obj mal-type @ type-name type cr endif
cells swap MalTypeType-method-vals @ + @ ( xt )
obj swap execute ;
\ Extend a type with a protocol method. This mutates the MalTypeType
\ object that represents the MalType being extended.
: extend-method* { type pxt ixt -- type }
type MalTypeType-methods 2@ swap ( methods method-keys )
dup 0= if \ no protocols extended to this type
2drop
1 type MalTypeType-methods !
pxt new-array type MalTypeType-method-keys !
ixt new-array type MalTypeType-method-vals !
else
pxt array-find { idx found? }
found? if \ overwrite
." Warning: overwriting protocol method implementation"
type MalTypeType-method-vals @ idx cells + ixt !
else \ resize
type MalTypeType-methods dup @ 1+ dup rot ! ( new-count )
1- dup type MalTypeType-method-keys @ idx pxt array-insert ( old-count new-array )
type MalTypeType-method-keys ! ( old-count )
type MalTypeType-method-vals @ idx ixt array-insert ( new-array )
type MalTypeType-method-vals !
endif
endif
type
;
\ def-protocol-method pr-str ...can be written:
\ : pr-str ( obj -- str ) [ latestxt ] literal execute-method ;
: def-protocol-method ( "name" -- )
create latestxt ,
does> ( ??? obj xt-ref -- ??? )
@ execute-method ;
: extend ( type -- type pxt install-xt <noname...>)
parse-name find-name name>int ( type pxt )
['] extend-method*
:noname
;
: ;; ( type pxt <noname...> -- type )
[compile] ; ( type pxt install-xt ixt )
swap execute
; immediate
(
\ These whole-protocol names are only needed for 'satisfies?':
protocol IPrintable
def-protocol-method pr-str
end-protocol
MalList IPrintable extend
' pr-str :noname drop s" <unprintable>" ; extend-method*
extend-method pr-str
drop s" <unprintable>" ;;
end-extend
)
\ === Mal types and protocols === /
def-protocol-method conj ( obj this -- this )
def-protocol-method assoc ( k v this -- this )
def-protocol-method get ( not-found k this -- value )
def-protocol-method mal= ( a b -- bool )
def-protocol-method as-native ( obj -- )
def-protocol-method to-list ( obj -- mal-list )
def-protocol-method empty? ( obj -- mal-bool )
def-protocol-method mal-count ( obj -- mal-int )
: m= ( a b -- bool )
2dup = if
2drop true
else
mal=
endif ;
MalType%
cell% field MalInt/int
deftype MalInt
: MalInt. { int -- mal-int }
MalInt new dup MalInt/int int swap ! ;
MalInt
extend mal= ( other this -- bool )
over mal-type @ MalInt = if
MalInt/int @ swap MalInt/int @ =
else
2drop 0
endif ;;
extend as-native ( mal-int -- int )
MalInt/int @ ;;
drop
MalType%
cell% field MalList/count
cell% field MalList/start
deftype MalList
: here>MalList ( old-here -- mal-list )
here over - { bytes } ( old-here )
MalList new bytes ( old-here mal-list bytes )
allocate throw dup { target } over MalList/start ! ( old-here mal-list )
bytes cell / over MalList/count ! ( old-here mal-list )
swap target bytes cmove ( mal-list )
0 bytes - allot \ pop list contents from dictionary stack
;
MalList
extend to-list ;;
extend conj { elem old-list -- list }
old-list MalList/count @ 1+ { new-count }
new-count cells allocate throw { new-start }
elem new-start !
new-count 1 > if
old-list MalList/start @ new-start cell+ new-count 1- cells cmove
endif
MalList new
new-count over MalList/count !
new-start over MalList/start ! ;;
extend empty? MalList/count @ 0= mal-bool ;;
extend mal-count MalList/count @ MalInt. ;;
extend mal=
swap to-list dup 0= if
nip
else
2dup MalList/count @ swap MalList/count @ over = if ( list-a list-b count )
-rot MalList/start @ swap MalList/start @ { start-b start-a }
true swap ( return-val count )
0 ?do
start-a i cells + @
start-b i cells + @
m= if else
drop false leave
endif
loop
else
drop 2drop false
endif
endif ;;
drop
MalList new 0 over MalList/count ! constant MalList/Empty
: MalList/rest { list -- list }
MalList new
list MalList/start @ cell+ over MalList/start !
list MalList/count @ 1- over MalList/count ! ;
MalType%
cell% field MalVector/list
deftype MalVector
MalVector
extend to-list
MalVector/list @ ;;
extend empty?
MalVector/list @
MalList/count @ 0= mal-bool ;;
extend mal-count
MalVector/list @
MalList/count @ MalInt. ;;
extend mal=
MalVector/list @ swap m= ;;
drop
MalType%
cell% field MalMap/list
deftype MalMap
MalMap new MalList/Empty over MalMap/list ! constant MalMap/Empty
MalMap
extend conj ( kv map -- map )
MalMap/list @ \ get list
over MalList/start @ cell+ @ swap conj \ add value
swap MalList/start @ @ swap conj \ add key
MalMap new dup -rot MalMap/list ! \ put back in map
;;
extend assoc ( k v map -- map )
MalMap/list @ \ get list
conj conj
MalMap new dup -rot MalMap/list ! \ put back in map
;;
extend get { not-found k map -- value }
map MalMap/list @
dup MalList/start @ { start }
MalList/count @ { count }
0
begin
dup count >= if
drop not-found true
else
start over cells + @ k m= if
start swap cells + cell+ @ true \ found it ( value true )
else
2 + false
endif
endif
until ;;
extend empty?
MalMap/list @
MalList/count @ 0= mal-bool ;;
extend mal-count
MalMap/list @
MalList/count @ 2 / MalInt. ;;
drop
\ Examples of extending existing protocol methods to existing type
MalDefault
extend conj ( obj this -- this )
nip ;;
extend as-native ;; ( obj -- obj )
extend to-list drop 0 ;;
drop
MalNil
extend conj ( item nil -- mal-list )
drop MalList/Empty conj ;;
extend as-native drop 0 ;;
extend empty? drop mal-true ;;
extend mal-count drop 0 MalInt. ;;
extend mal= drop mal-nil = ;;
drop
MalType%
cell% field MalSymbol/sym-addr
cell% field MalSymbol/sym-len
cell% field MalSymbol/meta
deftype MalSymbol
: MalSymbol. { str-addr str-len -- mal-sym }
MalSymbol new { sym }
str-addr sym MalSymbol/sym-addr !
str-len sym MalSymbol/sym-len !
MalMap/Empty sym MalSymbol/meta !
sym ;
: unpack-sym ( mal-string -- addr len )
dup MalSymbol/sym-addr @
swap MalSymbol/sym-len @ ;
MalSymbol
extend mal= ( other this -- bool )
over mal-type @ MalSymbol = if
unpack-sym rot unpack-sym str=
else
2drop 0
endif ;;
' as-native ' unpack-sym extend-method*
drop
MalType%
cell% field MalKeyword/str-addr
cell% field MalKeyword/str-len
deftype MalKeyword
: unpack-keyword ( mal-keyword -- addr len )
dup MalKeyword/str-addr @
swap MalKeyword/str-len @ ;
MalKeyword
extend mal= ( other this -- bool )
over mal-type @ MalKeyword = if
unpack-keyword rot unpack-keyword str=
else
2drop 0
endif ;;
' as-native ' unpack-keyword extend-method*
drop
: MalKeyword. { str-addr str-len -- mal-keyword }
MalKeyword new { kw }
str-addr kw MalKeyword/str-addr !
str-len kw MalKeyword/str-len !
kw ;
MalType%
cell% field MalString/str-addr
cell% field MalString/str-len
deftype MalString
: MalString. { str-addr str-len -- mal-str }
MalString new { str }
str-addr str MalString/str-addr !
str-len str MalString/str-len !
str ;
: unpack-str ( mal-string -- addr len )
dup MalString/str-addr @
swap MalString/str-len @ ;
MalString
extend mal= ( other this -- bool )
over mal-type @ MalString = if
unpack-str rot unpack-str str=
else
2drop 0
endif ;;
' as-native ' unpack-str extend-method*
drop
MalType%
cell% field MalNativeFn/xt
cell% field MalNativeFn/meta
deftype MalNativeFn
: MalNativeFn. { xt -- mal-fn }
MalNativeFn new { mal-fn }
xt mal-fn MalNativeFn/xt !
MalMap/Empty mal-fn MalNativeFn/meta !
mal-fn ;
MalNativeFn
extend as-native
MalNativeFn/xt @ ;;
drop
MalType%
cell% field MalUserFn/meta
cell% field MalUserFn/env
cell% field MalUserFn/formal-args
cell% field MalUserFn/var-arg
cell% field MalUserFn/body
deftype MalUserFn
MalType%
cell% field SpecialOp/xt
deftype SpecialOp
: SpecialOp.
SpecialOp new swap over SpecialOp/xt ! ;
|