forked from kanaka/mal
-
Notifications
You must be signed in to change notification settings - Fork 0
/
types.fs
669 lines (581 loc) · 19.2 KB
/
types.fs
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
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
require str.fs
\ === 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
dup a-length = if
drop false
else
cells a-addr + @ key =
endif ;
\ 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 mal-meta
\ 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
nil over mal-meta !
;
: 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 === /
struct
cell% field call-site/type
cell% field call-site/xt
end-struct call-site%
\ Used by protocol methods to find the appropriate implementation of
\ themselves for the given object, and then execute that implementation.
: execute-method { obj pxt call-site -- }
obj not-object? if
0 0 obj int>str s" ' on non-object: " pxt >name name>string
s" Refusing to invoke protocol fn '" ...throw-str
endif
\ ." Calling '" pxt >name name>string type ." ' on " obj mal-type @ type-name type ." , cs " call-site .
obj mal-type @ ( type )
dup call-site call-site/type @ = if
\ ." hit!" cr
drop
call-site call-site/xt @
else
\ ." miss!" cr
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
0 0 s" '" obj mal-type @ type-name s" ' extended to type '"
pxt >name name>string s" No protocol fn '" ...throw-str
endif
cells over MalTypeType-method-vals @ + @ ( type xt )
swap call-site call-site/type ! ( xt )
dup call-site call-site/xt ! ( xt )
endif
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 }
\ ." Extend '" pxt dup . >name name>string safe-type ." ' to " type type-name safe-type ." , "
\ type MalTypeType-methods 2@ ( method-keys methods )
\ 0 ?do
\ dup i cells + @ >name name>string safe-type ." , "
\ \ dup i cells + @ .
\ loop
\ drop cr
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 '"
pxt >name name>string safe-type ." ' on " type type-name safe-type ." , " idx . found? . cr
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
;
\ Define a new protocol function. For example:
\ def-protocol-method pr-str
\ When called as above, defines a new word 'pr-str' and stores there its
\ own xt (known as pxt). When a usage of pr-str is compiled, it
\ allocates a call-site object on the heap and injects a reference to
\ both that and the pxt into the compilation, along with a call to
\ execute-method. Thus when pr-str runs, execute-method can check the
\ call-site object to see if the type of the target object is the same
\ as the last call for this site. If so, it executes the implementation
\ immediately. Otherwise, it searches the target type's method list and
\ if necessary MalDefault's method list. If an implementation of pxt is
\ found, it is cached in the call-site, and then executed.
: make-call-site { pxt -- }
pxt postpone literal \ transfer pxt into call site
call-site% %allocate throw dup postpone literal \ allocate call-site, push reference
\ dup ." Make cs '" pxt >name name>string type ." ' " . cr
0 swap call-site/type !
postpone execute-method ;
: def-protocol-method ( parse: name -- )
: latestxt postpone literal postpone make-call-site postpone ; immediate
;
: 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 seq ( obj -- mal-list|nil )
def-protocol-method assoc ( k v this -- this )
def-protocol-method dissoc ( k 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 )
def-protocol-method sequential? ( obj -- mal-bool )
def-protocol-method get-map-hint ( obj -- hint )
def-protocol-method set-map-hint! ( hint obj -- )
\ Fully evalutate any Mal object:
def-protocol-method mal-eval ( env ast -- val )
\ Invoke an object, given whole env and unevaluated argument forms:
def-protocol-method eval-invoke ( env list obj -- ... )
\ Invoke a function, given parameter values
def-protocol-method invoke ( argv argc mal-fn -- ... )
: 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
: MalList. ( start count -- mal-list )
MalList new
swap over MalList/count ! ( start list )
swap over MalList/start ! ( list ) ;
: 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/concat ( list-of-lists )
dup MalList/start @ swap MalList/count @ { lists argc }
0 lists argc cells + lists +do ( count )
i @ to-list MalList/count @ +
cell +loop { count }
count cells allocate throw { start }
start lists argc cells + lists +do ( target )
i @ to-list MalList/count @ cells 2dup i @ to-list MalList/start @ -rot ( target bytes src target bytes )
cmove ( target bytes )
+ ( new-target )
cell +loop
drop start count MalList. ;
MalList
extend to-list ;;
extend sequential? drop mal-true ;;
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
new-start new-count MalList. ;;
extend seq
dup MalList/count @ 0= if
drop mal-nil
endif ;;
extend empty? MalList/count @ 0= mal-bool ;;
extend mal-count MalList/count @ MalInt. ;;
extend mal=
over mal-nil = if
2drop false
else
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
endif ;;
drop
MalList new 0 over MalList/count ! constant MalList/Empty
: MalList/rest { list -- list }
list MalList/start @ cell+
list MalList/count @ 1-
MalList. ;
MalType%
cell% field MalVector/list
deftype MalVector
MalVector
extend sequential? drop mal-true ;;
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= ;;
extend conj
MalVector/list @ { elem old-list }
old-list MalList/count @ { old-count }
old-count 1+ cells allocate throw { new-start }
elem new-start old-count cells + !
old-list MalList/start @ new-start old-count cells cmove
new-start old-count 1+ MalList.
MalVector new swap
over MalVector/list ! ;;
extend seq
MalVector/list @ seq ;;
drop
MalType%
cell% field MalMap/list
deftype MalMap
MalMap new MalList/Empty over MalMap/list ! constant MalMap/Empty
: MalMap/get-addr ( k map -- addr-or-nil )
MalMap/list @
dup MalList/start @
swap MalList/count @ { k start count }
true \ need to search?
k get-map-hint { hint-idx }
hint-idx -1 <> if
hint-idx count < if
hint-idx cells start + { key-addr }
key-addr @ k m= if
key-addr cell+
nip false
endif
endif
endif
if \ search
nil ( addr )
count cells start + start +do
i @ k m= if
drop i
dup start - cell / k set-map-hint!
cell+ leave
endif
[ 2 cells ] literal +loop
endif ;
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 tuck MalMap/list ! \ put back in map
;;
extend dissoc { k map -- map }
map MalMap/list @
dup MalList/start @ swap MalList/count @ { start count }
map \ return original if key not found
count 0 +do
start i cells + @ k mal= if
drop here
start i MalList. ,
start i 2 + cells + count i - 2 - MalList. ,
here>MalList MalList/concat
MalMap new dup -rot MalMap/list ! \ put back in map
endif
2 +loop ;;
extend get ( not-found k map -- value )
MalMap/get-addr ( not-found addr-or-nil )
dup 0= if drop else nip @ endif ;;
extend empty?
MalMap/list @
MalList/count @ 0= mal-bool ;;
extend mal-count
MalMap/list @
MalList/count @ 2 / MalInt. ;;
extend mal= { b a -- bool }
b mal-type @ MalMap = if
a MalMap/list @ MalList/count @ { a-count }
b MalMap/list @ MalList/count @ { b-count }
a-count b-count = if
a MalMap/list @ MalList/start @ { a-start }
true ( return-val )
a-count 0 +do
a-start i cells + @ ( return-val key )
dup a MalMap/get-addr swap b MalMap/get-addr ( return-val a-val-addr b-val-addr )
dup 0= if
drop 2drop false leave
else
@ swap @ ( return-val b-val a-val )
m= if else
drop false leave
endif
endif
2 +loop
else
false
endif
else
false
endif ;;
drop
\ Examples of extending existing protocol methods to existing type
MalDefault
extend conj ( obj this -- this )
nip ;;
extend to-list drop 0 ;;
extend empty? drop mal-true ;;
extend sequential? drop mal-false ;;
extend mal= = ;;
extend get-map-hint drop -1 ;;
extend set-map-hint! 2drop ;;
drop
MalNil
extend conj ( item nil -- mal-list )
drop MalList/Empty conj ;;
extend seq drop mal-nil ;;
extend as-native drop nil ;;
extend get 2drop ;;
extend to-list drop MalList/Empty ;;
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/map-hint
deftype MalSymbol
: MalSymbol. { str-addr str-len -- mal-sym }
MalSymbol new { sym }
str-addr sym MalSymbol/sym-addr !
str-len sym MalSymbol/sym-len !
-1 sym MalSymbol/map-hint !
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 ;;
extend get-map-hint MalSymbol/map-hint @ ;;
extend set-map-hint! MalSymbol/map-hint ! ;;
extend as-native ( this )
unpack-sym evaluate ;;
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.0 { str-addr str-len -- mal-str }
MalString new { str }
str-addr str MalString/str-addr !
str-len str MalString/str-len !
str ;
' MalString.0 is MalString.
: 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*
extend seq { str }
str MalString/str-len @ { len }
len 0= if
mal-nil
else
len cells allocate throw { list-start }
len 0 ?do
str MalString/str-addr @ i + 1 MalString. ( new-char-string )
list-start i cells + !
loop
list-start len MalList.
endif ;;
drop
MalType%
cell% field MalNativeFn/xt
deftype MalNativeFn
: MalNativeFn. { xt -- mal-fn }
MalNativeFn new { mal-fn }
xt mal-fn MalNativeFn/xt !
mal-fn ;
MalType%
cell% field MalUserFn/is-macro?
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 ! ;
MalType%
cell% field Atom/val
deftype Atom
: Atom. Atom new swap over Atom/val ! ;