diff options
| author | Chouser <chouser@n01se.net> | 2015-02-04 20:05:03 -0500 |
|---|---|---|
| committer | Chouser <chouser@n01se.net> | 2015-02-21 13:22:43 -0500 |
| commit | b745d1914925626c8e48fff3f95dcf440bb58755 (patch) | |
| tree | 86cceb18335619815ac2d0085ea8a724e5ab6a53 | |
| parent | cbedf170e32fac7c559f01b890ae2355a6b2666b (diff) | |
| download | mal-b745d1914925626c8e48fff3f95dcf440bb58755.tar.gz mal-b745d1914925626c8e48fff3f95dcf440bb58755.zip | |
forth: Add basic protocol functionality
| -rw-r--r-- | forth/types.f | 247 | ||||
| -rw-r--r-- | forth/types.fs | 351 |
2 files changed, 351 insertions, 247 deletions
diff --git a/forth/types.f b/forth/types.f deleted file mode 100644 index e30733f..0000000 --- a/forth/types.f +++ /dev/null @@ -1,247 +0,0 @@ -\ === classic lisp list === / -: cons { addr val -- new-list-address } - 2 cells allocate throw - dup addr val rot 2! ; - -: cdr ( addr -- next-addr ) - cell+ @ ; - -: int-pr ( num -- ) - s>d <# #s #> type ; - -: prn ( list-address -- ) - ." (" 2@ int-pr - begin - space 2@ int-pr - dup 0= - until - .\" )\n" ; - -0 1 cons 2 cons 3 cons 4 cons -prn - - -\ === mutable vector === / -\ Singly-linked list, with an "object" pair that points to both ends. -\ This allows fast append and fast iteration from beginning to end, -\ like a vector. ...but buys simplicity with mutability -: new-mutvec ( -- mutvec-addr ) - 2 cells allocate throw - dup 0 0 rot 2! ; - -: mutvec-append { mutvec-addr value -- } - 2 cells allocate throw \ new pair - dup nil value rot 2! \ put value in new pair - dup mutvec-addr @ - ?dup 0= if mutvec-addr endif - cell+ ! \ update old tail - mutvec-addr ! \ update object - ; - -new-mutvec -dup 5 mutvec-append -dup 4 mutvec-append -dup 3 mutvec-append -dup 2 mutvec-append -cdr prn - - -\ === mutable string buffer === / -\ string buffer that maintains an allocation larger than the current -\ string size. When appending would cause the string size exceed the -\ current allocation, resize is used to double the allocation. The -\ current allocation is not stored anywhere, but computed based on -\ current string size or str-base-size, whichever is larger. -64 constant str-base-size - -: new-str ( -- addr length ) - str-base-size allocate throw 0 ; - -: round-up ( n -- n ) - 2 - begin - 1 lshift 2dup < - until - swap drop ; - -: str-append { buf-addr buf-str-len str-addr str-len } - buf-str-len str-len + - { new-len } - new-len str-base-size > if - buf-str-len new-len xor buf-str-len > if - buf-addr new-len round-up resize throw - to buf-addr - endif - endif - str-addr buf-addr buf-str-len + str-len cmove - buf-addr new-len ; - - -bl c, -here constant space-str -: a-space space-str 1 str-append ; - -new-str -s" hello there" str-append a-space -s" is this getting ...." str-append a-space -s\" interesting yet?\n" str-append -type - -\ A rewrite of the list-printer above, but now using string buffer: -: int-pr-str2 ( num -- str-addr str-len ) - s>d <# #s #> ; - -: pr-str2 ( strbuf str-len list-address -- ) - -rot s" (" str-append rot - 2@ 2swap rot int-pr-str2 str-append - begin - a-space - rot 2@ 2swap rot int-pr-str2 str-append - 2 pick 0= - until - s" )" str-append rot drop ; - -new-str -0 1 cons 2 cons 3 cons 4 cons -pr-str2 type -cr - -: test= - 2dup = if - 2drop - else - cr ." assert failed on line " sourceline# . - swap cr ." | got " . cr ." | expected " . cr - endif ; - -\ new-class - -struct - cell% 2 * field MalTypeType-struct - cell% field MalTypeType-methods -end-struct MalTypeType% - -struct - cell% field mal-type - \ cell% field ref-count \ Ha, right. -end-struct MalType% - -: 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 nil swap ! ( MalTypeType ) \ init methods to nil - ; - -\ Example: - -MalType% - cell% field obj-list/car - cell% field obj-list/cdr -deftype* constant ObjList - -ObjList new -ObjList new -= 0 test= - -ObjList new dup obj-list/car 5 swap ! obj-list/car @ 5 test= - -\ 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. -: aarray-find { aa-length aa-addr key -- index } - 0 aa-length ( start end ) - begin - \ cr 2dup . . - 2dup + 2 / dup ( start end middle middle ) - cells aa-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 ) - swap drop ( start middle ) - else - -rot 2drop dup ( middle middle ) - endif - endif - 2dup = until - drop - ; - -create zaa 2 , 6 , 7 , 10 , 15 , 80 , 81 , - -7 zaa 2 aarray-find 0 test= -7 zaa 6 aarray-find 1 test= -7 zaa 10 aarray-find 3 test= -7 zaa 81 aarray-find 6 test= -7 zaa 12 aarray-find 4 test= -7 zaa 8 aarray-find 3 test= -7 zaa 100 aarray-find 7 test= -7 zaa 1 aarray-find 0 test= - -\ manual protocol method - -0 value method-count - -: pr-str ( ?? obj -- ?? ) - dup mal-type @ MalTypeType-methods @ ( obj methods ) - [ method-count ] literal aarray-find ( obj xt ) - execute ; - - -( - method-count 1+ to method-count - -protocol IPrintable - method% pr-str -end-protocol -) - -( -ObjList IPrintable extend - - ' pr-str :noname drop s" <unprintable>" ; extend-method* - - extend-method pr-str - drop s" <unprintable>" ; -end-extend -) - -\ new-obj - -\ new-instance - - -\ maybe useful for debugging? -: p dup . ; -: @p dup @ dup . ; - -( - -create buff 128 allot - -." user> " - -buff 128 stdin read-line throw - -buff c@ . -buff 5 + c@ . - -S" Hello" dup . type - - -bye - -) - -cr ." Done loading" cr diff --git a/forth/types.fs b/forth/types.fs new file mode 100644 index 0000000..3d76329 --- /dev/null +++ b/forth/types.fs @@ -0,0 +1,351 @@ +\ === tiny framework for inline tests === / +: test= + 2dup = if + 2drop + else + cr ." assert failed on line " sourceline# . + swap cr ." | got " . cr ." | expected " . cr + endif ; + +\ === classic lisp list === / +: cons { addr val -- new-list-address } + 2 cells allocate throw + dup addr val rot 2! ; + +: cdr ( addr -- next-addr ) + cell+ @ ; + +: int-pr ( num -- ) + s>d <# #s #> type ; + +: prn ( list-address -- ) + ." (" 2@ int-pr + begin + space 2@ int-pr + dup 0= + until + .\" )\n" ; + +0 1 cons 2 cons 3 cons 4 cons +prn + + +\ === mutable vector === / +\ Singly-linked list, with an "object" pair that points to both ends. +\ This allows fast append and fast iteration from beginning to end, +\ like a vector. ...but buys simplicity with mutability +: new-mutvec ( -- mutvec-addr ) + 2 cells allocate throw + dup 0 0 rot 2! ; + +: mutvec-append { mutvec-addr value -- } + 2 cells allocate throw \ new pair + dup nil value rot 2! \ put value in new pair + dup mutvec-addr @ + ?dup 0= if mutvec-addr endif + cell+ ! \ update old tail + mutvec-addr ! \ update object + ; + +new-mutvec +dup 5 mutvec-append +dup 4 mutvec-append +dup 3 mutvec-append +dup 2 mutvec-append +cdr prn + + +\ === mutable string buffer === / +\ string buffer that maintains an allocation larger than the current +\ string size. When appending would cause the string size exceed the +\ current allocation, resize is used to double the allocation. The +\ current allocation is not stored anywhere, but computed based on +\ current string size or str-base-size, whichever is larger. +64 constant str-base-size + +: new-str ( -- addr length ) + str-base-size allocate throw 0 ; + +: round-up ( n -- n ) + 2 + begin + 1 lshift 2dup < + until + swap drop ; + +: str-append { buf-addr buf-str-len str-addr str-len } + buf-str-len str-len + + { new-len } + new-len str-base-size > if + buf-str-len new-len xor buf-str-len > if + buf-addr new-len round-up resize throw + to buf-addr + endif + endif + str-addr buf-addr buf-str-len + str-len cmove + buf-addr new-len ; + + +bl c, +here constant space-str +: a-space space-str 1 str-append ; + +new-str +s" hello there" str-append a-space +s" is this getting ...." str-append a-space +s\" interesting yet?\n" str-append +type + +\ A rewrite of the list-printer above, but now using string buffer: +: int-pr-str2 ( num -- str-addr str-len ) + s>d <# #s #> ; + +: pr-str2 ( strbuf str-len list-address -- ) + -rot s" (" str-append rot + 2@ 2swap rot int-pr-str2 str-append + begin + a-space + rot 2@ 2swap rot int-pr-str2 str-append + 2 pick 0= + until + s" )" str-append rot drop ; + +new-str +0 1 cons 2 cons 3 cons 4 cons +pr-str2 type +cr + +\ === 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 +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 ) + ; + +MalType% deftype* constant MalDefault + +\ Example and tests + +MalType% + cell% field obj-list/car + cell% field obj-list/cdr +deftype* constant ObjList + +ObjList new +ObjList new += 0 test= + +ObjList new dup obj-list/car 5 swap ! obj-list/car @ 5 test= + +\ === 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 ) + swap drop ( 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 + ; + +\ array function tests +create za 2 , 6 , 7 , 10 , 15 , 80 , 81 , + +7 za 2 array-find -1 test= 0 test= +7 za 6 array-find -1 test= 1 test= +7 za 10 array-find -1 test= 3 test= +7 za 81 array-find -1 test= 6 test= +7 za 12 array-find 0 test= 4 test= +7 za 8 array-find 0 test= 3 test= +7 za 100 array-find 0 test= 7 test= +7 za 1 array-find 0 test= 0 test= + +10 new-array +1 swap 0 5 array-insert +2 swap 1 7 array-insert +3 swap 3 12 array-insert +4 swap 4 15 array-insert +5 swap 5 20 array-insert + +dup 0 cells + @ 5 test= +dup 1 cells + @ 7 test= +dup 2 cells + @ 10 test= +dup 3 cells + @ 12 test= +dup 4 cells + @ 15 test= +dup 5 cells + @ 20 test= + + +\ === protocol methods === / + +\ 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 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 + dup 0= if ." No protocols extended to this type or MalDefault" 1 throw endif + + pxt array-find ( type idx found? ) + dup 0= if \ No implementation found for this method; check for a default + 2drop MalDefault MalTypeType-methods 2@ swap + dup 0= if ." No implementation found for this method, and no protocols extended to MalDefault" 1 throw endif + pxt array-find ( type idx found? ) + endif + 0= if ." No implementation found" 1 throw 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 ) + \ cr ." before: " ObjList MalTypeType-method-vals @ @ . cr + type MalTypeType-method-vals @ idx ixt array-insert ( new-array ) + type MalTypeType-method-vals ! + \ cr ." after: " ObjList MalTypeType-method-vals @ @ . cr + endif + endif + type + ; + +\ Examples of making new protocol methods (without a protocol to group them yet!) +: pr-str [ latestxt ] literal execute-method ; +: conj [ latestxt ] literal execute-method ; + +\ Examples of extending existing protocol methods to existing type +MalDefault ' pr-str :noname s" #<MalObject>" ; extend-method* +ObjList ' pr-str :noname drop s" #<ObjList>" ; extend-method* +ObjList ' conj :noname ." not yet done" ; extend-method* + +\ Run some protocol methods! +ObjList new pr-str type +ObjList new conj + +( + method-count 1+ to method-count + +protocol IPrintable + method% pr-str +end-protocol +) + +( +ObjList IPrintable extend + + ' pr-str :noname drop s" <unprintable>" ; extend-method* + + extend-method pr-str + drop s" <unprintable>" ; +end-extend +) + +\ new-obj + +\ new-instance + + +\ maybe useful for debugging? +: p dup . ; +: @p dup @ dup . ; + +( + +create buff 128 allot + +." user> " + +buff 128 stdin read-line throw + +buff c@ . +buff 5 + c@ . + +S" Hello" dup . type + + +) + + +cr +bye +." Done loading" cr |
