xref: /freebsd/stand/ficl/softwords/oo.fr (revision 4b9d6057)
1\ #if FICL_WANT_OOP
2\ ** ficl/softwords/oo.fr
3\ ** F I C L   O - O   E X T E N S I O N S
4\ ** john sadler aug 1998
5\
6
717 ficl-vocabulary oop
8also oop definitions
9
10\ Design goals:
11\ 0. Traditional OOP: late binding by default for safety.
12\    Early binding if you ask for it.
13\ 1. Single inheritance
14\ 2. Object aggregation (has-a relationship)
15\ 3. Support objects in the dictionary and as proxies for
16\    existing structures (by reference):
17\    *** A ficl object can wrap a C struct ***
18\ 4. Separate name-spaces for methods - methods are
19\    only visible in the context of a class / object
20\ 5. Methods can be overridden, and subclasses can add methods.
21\    No limit on number of methods.
22
23\ General info:
24\ Classes are objects, too: all classes are instances of METACLASS
25\ All classes are derived (by convention) from OBJECT. This
26\ base class provides a default initializer and superclass
27\ access method
28
29\ A ficl object binds instance storage (payload) to a class.
30\ object  ( -- instance class )
31\ All objects push their payload address and class address when
32\ executed.
33
34\ A ficl class consists of a parent class pointer, a wordlist
35\ ID for the methods of the class, and a size for the payload
36\ of objects created by the class. A class is an object.
37\ The NEW method creates and initializes an instance of a class.
38\ Classes have this footprint:
39\ cell 0: parent class address
40\ cell 1: wordlist ID
41\ cell 2: size of instance's payload
42
43\ Methods expect an object couple ( instance class )
44\ on the stack. This is by convention - ficl has no way to
45\ police your code to make sure this is always done, but it
46\ happens naturally if you use the facilities presented here.
47\
48\ Overridden methods must maintain the same stack signature as
49\ their predecessors. Ficl has no way of enforcing this, either.
50\
51\ Revised Apr 2001 - Added Guy Carver's vtable extensions. Class now
52\ has an extra field for the vtable method count. Hasvtable declares
53\ refs to vtable classes
54\
55\ Revised Nov 2001 - metaclass debug method now finds only metaclass methods
56\
57\ Planned: Ficl vtable support
58\ Each class has a vtable size parameter
59\ END-CLASS allocates and clears the vtable - then it walks class's method
60\ list and inserts all new methods into table. For each method, if the table
61\ slot is already nonzero, do nothing (overridden method). Otherwise fill
62\ vtable slot. Now do same check for parent class vtable, filling only
63\ empty slots in the new vtable.
64\ Methods are now structured as follows:
65\ - header
66\ - vtable index
67\ - xt
68\ :noname definition for code
69\
70\ : is redefined to check for override, fill in vtable index, increment method
71\ count if not an override, create header and fill in index. Allot code pointer
72\ and run :noname
73\ ; is overridden to fill in xt returned by :noname
74\ --> compiles code to fetch vtable address, offset by index, and execute
75\ => looks up xt in the vtable and compiles it directly
76
77
78
79user current-class
800 current-class !
81
82\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
83\ ** L A T E   B I N D I N G
84\ Compile the method name, and code to find and
85\ execute it at run-time...
86\
87
88\ p a r s e - m e t h o d
89\ compiles a method name so that it pushes
90\ the string base address and count at run-time.
91
92: parse-method  \ name  run: ( -- c-addr u )
93    parse-word
94    postpone sliteral
95; compile-only
96
97
98
99: (lookup-method)  { class 2:name -- class 0 | class xt 1 | class xt -1  }
100    class  name class cell+ @  ( class c-addr u wid )
101    search-wordlist
102;
103
104\ l o o k u p - m e t h o d
105\ takes a counted string method name from the stack (as compiled
106\ by parse-method) and attempts to look this method up in the method list of
107\ the class that's on the stack. If successful, it leaves the class on the stack
108\ and pushes the xt of the method. If not, it aborts with an error message.
109
110: lookup-method  { class 2:name -- class xt }
111    class name (lookup-method)    ( 0 | xt 1 | xt -1 )
112    0= if
113        name type ."  not found in "
114        class body> >name type
115        cr abort
116    endif
117;
118
119: find-method-xt   \ name ( class -- class xt )
120    parse-word lookup-method
121;
122
123: catch-method  ( instance class c-addr u -- <method-signature> exc-flag )
124    lookup-method catch
125;
126
127: exec-method  ( instance class c-addr u -- <method-signature> )
128    lookup-method execute
129;
130
131\ Method lookup operator takes a class-addr and instance-addr
132\ and executes the method from the class's wordlist if
133\ interpreting. If compiling, bind late.
134\
135: -->   ( instance class -- ??? )
136    state @ 0= if
137        find-method-xt execute
138    else
139        parse-method  postpone exec-method
140    endif
141; immediate
142
143\ Method lookup with CATCH in case of exceptions
144: c->   ( instance class -- ?? exc-flag )
145    state @ 0= if
146        find-method-xt catch
147    else
148        parse-method  postpone catch-method
149    endif
150; immediate
151
152\ METHOD  makes global words that do method invocations by late binding
153\ in case you prefer this style (no --> in your code)
154\ Example: everything has next and prev for array access, so...
155\ method next
156\ method prev
157\ my-instance next ( does whatever next does to my-instance by late binding )
158
159: method   create does> body> >name lookup-method execute ;
160
161
162\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
163\ ** E A R L Y   B I N D I N G
164\ Early binding operator compiles code to execute a method
165\ given its class at compile time. Classes are immediate,
166\ so they leave their cell-pair on the stack when compiling.
167\ Example:
168\   : get-wid   metaclass => .wid @ ;
169\ Usage
170\   my-class get-wid  ( -- wid-of-my-class )
171\
1721 ficl-named-wordlist instance-vars
173instance-vars dup >search ficl-set-current
174
175: =>   \ c:( class meta -- ) run: ( -- ??? ) invokes compiled method
176    drop find-method-xt compile, drop
177; immediate compile-only
178
179: my=>   \ c:( -- ) run: ( -- ??? ) late bind compiled method of current-class
180    current-class @ dup postpone =>
181; immediate compile-only
182
183\ Problem: my=[ assumes that each method except the last is am obj: member
184\ which contains its class as the first field of its parameter area. The code
185\ detects non-obect members and assumes the class does not change in this case.
186\ This handles methods like index, prev, and next correctly, but does not deal
187\ correctly with CLASS.
188: my=[   \ same as my=> , but binds a chain of methods
189    current-class @
190    begin
191        parse-word 2dup             ( class c-addr u c-addr u )
192        s" ]" compare while         ( class c-addr u )
193        lookup-method               ( class xt )
194        dup compile,                ( class xt )
195        dup ?object if        \ If object member, get new class. Otherwise assume same class
196           nip >body cell+ @        ( new-class )
197        else
198           drop                     ( class )
199        endif
200    repeat 2drop drop
201; immediate compile-only
202
203
204\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
205\ ** I N S T A N C E   V A R I A B L E S
206\ Instance variables (IV) are represented by words in the class's
207\ private wordlist. Each IV word contains the offset
208\ of the IV it represents, and runs code to add that offset
209\ to the base address of an instance when executed.
210\ The metaclass SUB method, defined below, leaves the address
211\ of the new class's offset field and its initial size on the
212\ stack for these words to update. When a class definition is
213\ complete, END-CLASS saves the final size in the class's size
214\ field, and restores the search order and compile wordlist to
215\ prior state. Note that these words are hidden in their own
216\ wordlist to prevent accidental use outside a SUB END-CLASS pair.
217\
218: do-instance-var
219    does>   ( instance class addr[offset] -- addr[field] )
220        nip @ +
221;
222
223: addr-units:  ( offset size "name" -- offset' )
224    create over , +
225    do-instance-var
226;
227
228: chars:    \ ( offset nCells "name" -- offset' ) Create n char member.
229   chars addr-units: ;
230
231: char:     \ ( offset nCells "name" -- offset' ) Create 1 char member.
232   1 chars: ;
233
234: cells:  ( offset nCells "name" -- offset' )
235    cells >r aligned r> addr-units:
236;
237
238: cell:   ( offset nCells "name" -- offset' )
239    1 cells: ;
240
241\ Aggregate an object into the class...
242\ Needs the class of the instance to create
243\ Example: object obj: m_obj
244\
245: do-aggregate
246    objectify
247    does>   ( instance class pfa -- a-instance a-class )
248    2@          ( inst class a-class a-offset )
249    2swap drop  ( a-class a-offset inst )
250    + swap      ( a-inst a-class )
251;
252
253: obj:   { offset class meta -- offset' }  \ "name"
254    create  offset , class ,
255    class meta --> get-size  offset +
256    do-aggregate
257;
258
259\ Aggregate an array of objects into a class
260\ Usage example:
261\ 3 my-class array: my-array
262\ Makes an instance variable array of 3 instances of my-class
263\ named my-array.
264\
265: array:   ( offset n class meta "name" -- offset' )
266    locals| meta class nobjs offset |
267    create offset , class ,
268    class meta --> get-size  nobjs * offset +
269    do-aggregate
270;
271
272\ Aggregate a pointer to an object: REF is a member variable
273\ whose class is set at compile time. This is useful for wrapping
274\ data structures in C, where there is only a pointer and the type
275\ it refers to is known. If you want polymorphism, see c_ref
276\ in classes.fr. REF is only useful for pre-initialized structures,
277\ since there's no supported way to set one.
278: ref:   ( offset class meta "name" -- offset' )
279    locals| meta class offset |
280    create offset , class ,
281    offset cell+
282    does>    ( inst class pfa -- ptr-inst ptr-class )
283    2@       ( inst class ptr-class ptr-offset )
284    2swap drop + @ swap
285;
286
287\ #if FICL_WANT_VCALL
288\ vcall extensions contributed by Guy Carver
289: vcall:  ( paramcnt "name" -- )
290    current-class @ 8 + dup @ dup 1+ rot !  \ Kludge fix to get to .vtCount before it's defined.
291    create , ,                              \ ( paramcnt index -- )
292    does>                                   \ ( inst class pfa -- ptr-inst ptr-class )
293   nip 2@ vcall                             \ ( params offset inst class offset -- )
294;
295
296: vcallr: 0x80000000 or vcall: ;            \ Call with return address desired.
297
298\ #if FICL_WANT_FLOAT
299: vcallf:                                   \ ( paramcnt -<name>- f: r )
300    0x80000000 or
301    current-class @ 8 + dup @ dup 1+ rot !  \ Kludge fix to get to .vtCount before it's defined.
302    create , ,                              \ ( paramcnt index -- )
303    does>                                   \ ( inst class pfa -- ptr-inst ptr-class )
304    nip 2@ vcall f>                         \ ( params offset inst class offset -- f: r )
305;
306\ #endif /* FLOAT */
307\ #endif /* VCALL */
308
309\ END-CLASS terminates construction of a class by storing
310\  the size of its instance variables in the class's size field
311\ ( -- old-wid addr[size] 0 )
312\
313: end-class  ( old-wid addr[size] size -- )
314    swap ! set-current
315    search> drop        \ pop struct builder wordlist
316;
317
318\ See resume-class (a metaclass method) below for usage
319\ This is equivalent to end-class for now, but that will change
320\ when we support vtable bindings.
321: suspend-class  ( old-wid addr[size] size -- )   end-class ;
322
323set-current previous
324\ E N D   I N S T A N C E   V A R I A B L E S
325
326
327\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
328\ D O - D O - I N S T A N C E
329\ Makes a class method that contains the code for an
330\ instance of the class. This word gets compiled into
331\ the wordlist of every class by the SUB method.
332\ PRECONDITION: current-class contains the class address
333\ why use a state variable instead of the stack?
334\ >> Stack state is not well-defined during compilation (there are
335\ >> control structure match codes on the stack, of undefined size
336\ >> easiest way around this is use of this thread-local variable
337\
338: do-do-instance  ( -- )
339    s" : .do-instance does> [ current-class @ ] literal ;"
340    evaluate
341;
342
343\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
344\ ** M E T A C L A S S
345\ Every class is an instance of metaclass. This lets
346\ classes have methods that are different from those
347\ of their instances.
348\ Classes are IMMEDIATE to make early binding simpler
349\ See above...
350\
351:noname
352    wordlist
353    create
354    immediate
355    0       ,   \ NULL parent class
356    dup     ,   \ wid
357\ #if FICL_WANT_VCALL
358    4 cells ,   \ instance size
359\ #else
360    3 cells ,   \ instance size
361\ #endif
362    ficl-set-current
363    does> dup
364;  execute metaclass
365\ now brand OBJECT's wordlist (so that ORDER can display it by name)
366metaclass drop cell+ @ brand-wordlist
367
368metaclass drop current-class !
369do-do-instance
370
371\
372\ C L A S S   M E T H O D S
373\
374instance-vars >search
375
376create .super  ( class metaclass -- parent-class )
377    0 cells , do-instance-var
378
379create .wid    ( class metaclass -- wid ) \ return wid of class
380    1 cells , do-instance-var
381
382\ #if FICL_WANT_VCALL
383create .vtCount   \ Number of VTABLE methods, if any
384    2 cells , do-instance-var
385
386create  .size  ( class metaclass -- size ) \ return class's payload size
387    3 cells , do-instance-var
388\ #else
389create  .size  ( class metaclass -- size ) \ return class's payload size
390    2 cells , do-instance-var
391\ #endif
392
393: get-size    metaclass => .size  @ ;
394: get-wid     metaclass => .wid   @ ;
395: get-super   metaclass => .super @ ;
396\ #if FICL_WANT_VCALL
397: get-vtCount metaclass => .vtCount @ ;
398: get-vtAdd   metaclass => .vtCount ;
399\ #endif
400
401\ create an uninitialized instance of a class, leaving
402\ the address of the new instance and its class
403\
404: instance   ( class metaclass "name" -- instance class )
405    locals| meta parent |
406    create
407    here parent --> .do-instance \ ( inst class )
408    parent meta metaclass => get-size
409    allot                        \ allocate payload space
410;
411
412\ create an uninitialized array
413: array   ( n class metaclass "name" -- n instance class )
414    locals| meta parent nobj |
415    create  nobj
416    here parent --> .do-instance \ ( nobj inst class )
417    parent meta metaclass => get-size
418    nobj *  allot           \ allocate payload space
419;
420
421\ create an initialized instance
422\
423: new   \ ( class metaclass "name" -- )
424    metaclass => instance --> init
425;
426
427\ create an initialized array of instances
428: new-array   ( n class metaclass "name" -- )
429    metaclass => array
430    --> array-init
431;
432
433\ Create an anonymous initialized instance from the heap
434: alloc   \ ( class metaclass -- instance class )
435    locals| meta class |
436    class meta metaclass => get-size allocate   ( -- addr fail-flag )
437    abort" allocate failed "                    ( -- addr )
438    class 2dup --> init
439;
440
441\ Create an anonymous array of initialized instances from the heap
442: alloc-array   \ ( n class metaclass -- instance class )
443    locals| meta class nobj |
444    class meta metaclass => get-size
445    nobj * allocate                 ( -- addr fail-flag )
446    abort" allocate failed "        ( -- addr )
447    nobj over class --> array-init
448    class
449;
450
451\ Create an anonymous initialized instance from the dictionary
452: allot   { 2:this -- 2:instance }
453    here   ( instance-address )
454    this my=> get-size  allot
455    this drop 2dup --> init
456;
457
458\ Create an anonymous array of initialized instances from the dictionary
459: allot-array   { nobj 2:this -- 2:instance }
460    here   ( instance-address )
461    this my=> get-size  nobj * allot
462    this drop 2dup     ( 2instance 2instance )
463    nobj -rot --> array-init
464;
465
466\ create a proxy object with initialized payload address given
467: ref   ( instance-addr class metaclass "name" -- )
468    drop create , ,
469    does> 2@
470;
471
472\ suspend-class and resume-class help to build mutually referent classes.
473\ Example:
474\ object subclass c-akbar
475\ suspend-class   ( put akbar on hold while we define jeff )
476\ object subclass c-jeff
477\     c-akbar ref: .akbar
478\     ( and whatever else comprises this class )
479\ end-class    ( done with c-jeff )
480\ c-akbar --> resume-class
481\     c-jeff ref: .jeff
482\     ( and whatever else goes in c-akbar )
483\ end-class    ( done with c-akbar )
484\
485: resume-class   { 2:this -- old-wid addr[size] size }
486    this --> .wid @ ficl-set-current  ( old-wid )
487    this --> .size dup @   ( old-wid addr[size] size )
488    instance-vars >search
489;
490
491\ create a subclass
492\ This method leaves the stack and search order ready for instance variable
493\ building. Pushes the instance-vars wordlist onto the search order,
494\ and sets the compilation wordlist to be the private wordlist of the
495\ new class. The class's wordlist is deliberately NOT in the search order -
496\ to prevent methods from getting used with wrong data.
497\ Postcondition: leaves the address of the new class in current-class
498: sub   ( class metaclass "name" -- old-wid addr[size] size )
499    wordlist
500    locals| wid meta parent |
501    parent meta metaclass => get-wid
502    wid wid-set-super       \ set superclass
503    create  immediate       \ get the  subclass name
504    wid brand-wordlist      \ label the subclass wordlist
505    here current-class !    \ prep for do-do-instance
506    parent ,                \ save parent class
507    wid    ,                \ save wid
508\ #if FICL_WANT_VCALL
509    parent meta --> get-vtCount ,
510\ #endif
511    here parent meta --> get-size dup ,  ( addr[size] size )
512    metaclass => .do-instance
513    wid ficl-set-current -rot
514    do-do-instance
515    instance-vars >search \ push struct builder wordlist
516;
517
518\ OFFSET-OF returns the offset of an instance variable
519\ from the instance base address. If the next token is not
520\ the name of in instance variable method, you get garbage
521\ results -- there is no way at present to check for this error.
522: offset-of   ( class metaclass "name" -- offset )
523    drop find-method-xt nip >body @ ;
524
525\ ID returns the string name cell-pair of its class
526: id   ( class metaclass -- c-addr u )
527    drop body> >name  ;
528
529\ list methods of the class
530: methods \ ( class meta -- )
531    locals| meta class |
532    begin
533        class body> >name type ."  methods:" cr
534        class meta --> get-wid >search words cr previous
535        class meta metaclass => get-super
536        dup to class
537    0= until  cr
538;
539
540\ list class's ancestors
541: pedigree  ( class meta -- )
542    locals| meta class |
543    begin
544        class body> >name type space
545        class meta metaclass => get-super
546        dup to class
547    0= until  cr
548;
549
550\ decompile an instance method
551: see  ( class meta -- )
552    metaclass => get-wid >search see previous ;
553
554\ debug a method of metaclass
555\ Eg: my-class --> debug my-method
556: debug  ( class meta -- )
557	find-method-xt debug-xt ;
558
559previous set-current
560\ E N D   M E T A C L A S S
561
562\ ** META is a nickname for the address of METACLASS...
563metaclass drop
564constant meta
565
566\ ** SUBCLASS is a nickname for a class's SUB method...
567\ Subclass compilation ends when you invoke end-class
568\ This method is late bound for safety...
569: subclass   --> sub ;
570
571\ #if FICL_WANT_VCALL
572\ VTABLE Support extensions (Guy Carver)
573\ object --> sub mine hasvtable
574: hasvtable 4 + ; immediate
575\ #endif
576
577
578\ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
579\ ** O B J E C T
580\ Root of all classes
581:noname
582    wordlist
583    create  immediate
584    0       ,   \ NULL parent class
585    dup     ,   \ wid
586    0       ,   \ instance size
587    ficl-set-current
588    does> meta
589;  execute object
590\ now brand OBJECT's wordlist (so that ORDER can display it by name)
591object drop cell+ @ brand-wordlist
592
593object drop current-class !
594do-do-instance
595instance-vars >search
596
597\ O B J E C T   M E T H O D S
598\ Convert instance cell-pair to class cell-pair
599\ Useful for binding class methods from an instance
600: class  ( instance class -- class metaclass )
601    nip meta ;
602
603\ default INIT method zero fills an instance
604: init   ( instance class -- )
605    meta
606    metaclass => get-size   ( inst size )
607    erase ;
608
609\ Apply INIT to an array of NOBJ objects...
610\
611: array-init   ( nobj inst class -- )
612    0 dup locals| &init &next class inst |
613    \
614    \ bind methods outside the loop to save time
615    \
616    class s" init" lookup-method to &init
617          s" next" lookup-method to &next
618    drop
619    0 ?do
620        inst class 2dup
621        &init execute
622        &next execute  drop to inst
623    loop
624;
625
626\ free storage allocated to a heap instance by alloc or alloc-array
627\ NOTE: not protected against errors like FREEing something that's
628\ really in the dictionary.
629: free   \ ( instance class -- )
630    drop free
631    abort" free failed "
632;
633
634\ Instance aliases for common class methods
635\ Upcast to parent class
636: super     ( instance class -- instance parent-class )
637    meta  metaclass => get-super ;
638
639: pedigree  ( instance class -- )
640    object => class
641    metaclass => pedigree ;
642
643: size      ( instance class -- sizeof-instance )
644    object => class
645    metaclass => get-size ;
646
647: methods   ( instance class -- )
648    object => class
649    metaclass => methods ;
650
651\ Array indexing methods...
652\ Usage examples:
653\ 10 object-array --> index
654\ obj --> next
655\
656: index   ( n instance class -- instance[n] class )
657    locals| class inst |
658    inst class
659    object => class
660    metaclass => get-size  *   ( n*size )
661    inst +  class ;
662
663: next   ( instance[n] class -- instance[n+1] class )
664    locals| class inst |
665    inst class
666    object => class
667    metaclass => get-size
668    inst +
669    class ;
670
671: prev   ( instance[n] class -- instance[n-1] class )
672    locals| class inst |
673    inst class
674    object => class
675    metaclass => get-size
676    inst swap -
677    class ;
678
679: debug   ( 2this --  ?? )
680    find-method-xt debug-xt ;
681
682previous set-current
683\ E N D   O B J E C T
684
685\ reset to default search order
686only definitions
687
688\ redefine oop in default search order to put OOP words in the search order and make them
689\ the compiling wordlist...
690
691: oo   only also oop definitions ;
692
693\ #endif
694