1
1
mirror of https://github.com/kanaka/mal.git synced 2024-09-21 02:27:10 +03:00
mal/forth/types.fs
Joel Martin 82e2b26b36 forth: add seq and string?
Issue #166

With assistance from chouser (thanks!).
2016-02-16 13:56:51 -06:00

670 lines
19 KiB
Forth

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 ! ;