1 S" FICL_WANT_OOP" ENVIRONMENT? drop [if]
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
6 .( loading ficl O-O extensions ) cr
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.
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
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
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
41 \ cell 2: size of instance's payload
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.
48 \ Overridden methods must maintain the same stack signature as
49 \ their predecessors. Ficl has no way of enforcing this, either.
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
55 \ Revised Nov 2001 - metaclass debug method now finds only metaclass methods
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:
68 \ :noname definition for code
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
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
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...
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.
92 : parse-method \ name run: ( -- c-addr u )
99 : (lookup-method) { class 2:name -- class 0 | class xt 1 | class xt -1 }
100 class name class cell+ @ ( class c-addr u wid )
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.
110 : lookup-method { class 2:name -- class xt }
111 class name (lookup-method) ( 0 | xt 1 | xt -1 )
113 name type ." not found in "
114 class body> >name type
119 : find-method-xt \ name ( class -- class xt )
120 parse-word lookup-method
123 : catch-method ( instance class c-addr u -- <method-signature> exc-flag )
127 : exec-method ( instance class c-addr u -- <method-signature> )
128 lookup-method execute
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.
135 : --> ( instance class -- ??? )
137 find-method-xt execute
139 parse-method postpone exec-method
143 \ Method lookup with CATCH in case of exceptions
144 : c-> ( instance class -- ?? exc-flag )
148 parse-method postpone catch-method
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...
157 \ my-instance next ( does whatever next does to my-instance by late binding )
159 : method create does> body> >name lookup-method execute ;
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.
168 \ : get-wid metaclass => .wid @ ;
170 \ my-class get-wid ( -- wid-of-my-class )
172 1 ficl-named-wordlist instance-vars
173 instance-vars dup >search ficl-set-current
175 : => \ c:( class meta -- ) run: ( -- ??? ) invokes compiled method
176 drop find-method-xt compile, drop
177 ; immediate compile-only
179 : my=> \ c:( -- ) run: ( -- ??? ) late bind compiled method of current-class
180 current-class @ dup postpone =>
181 ; immediate compile-only
183 \ Problem: my=[ assumes that each method except the last is an 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
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 )
201 ; immediate compile-only
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.
219 does> ( instance class addr[offset] -- addr[field] )
223 : addr-units: ( offset size "name" -- offset' )
228 : chars: \ ( offset nCells "name" -- offset' ) Create n char member.
231 : char: \ ( offset nCells "name" -- offset' ) Create 1 char member.
234 : cells: ( offset nCells "name" -- offset' )
235 cells >r aligned r> addr-units:
238 : cell: ( offset nCells "name" -- offset' )
241 \ Aggregate an object into the class...
242 \ Needs the class of the instance to create
243 \ Example: object obj: m_obj
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 )
253 : obj: { offset class meta -- offset' } \ "name"
254 create offset , class ,
255 class meta --> get-size offset +
259 \ Aggregate an array of objects into a class
261 \ 3 my-class array: my-array
262 \ Makes an instance variable array of 3 instances of my-class
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 +
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 ,
282 does> ( inst class pfa -- ptr-inst ptr-class )
283 2@ ( inst class ptr-class ptr-offset )
287 S" FICL_WANT_VCALL" ENVIRONMENT? drop [if]
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 -- )
296 : vcallr: 0x80000000 or vcall: ; \ Call with return address desired.
298 S" FICL_WANT_FLOAT" ENVIRONMENT? drop [if]
299 : vcallf: \ ( paramcnt -<name>- f: r )
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 )
307 [endif] \ FICL_WANT_FLOAT
308 [endif] \ FICL_WANT_VCALL
310 \ END-CLASS terminates construction of a class by storing
311 \ the size of its instance variables in the class's size field
312 \ ( -- old-wid addr[size] 0 )
314 : end-class ( old-wid addr[size] size -- )
316 search> drop \ pop struct builder wordlist
319 \ See resume-class (a metaclass method) below for usage
320 \ This is equivalent to end-class for now, but that will change
321 \ when we support vtable bindings.
322 : suspend-class ( old-wid addr[size] size -- ) end-class ;
325 \ E N D I N S T A N C E V A R I A B L E S
328 \ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
329 \ D O - D O - I N S T A N C E
330 \ Makes a class method that contains the code for an
331 \ instance of the class. This word gets compiled into
332 \ the wordlist of every class by the SUB method.
333 \ PRECONDITION: current-class contains the class address
334 \ why use a state variable instead of the stack?
335 \ >> Stack state is not well-defined during compilation (there are
336 \ >> control structure match codes on the stack, of undefined size
337 \ >> easiest way around this is use of this thread-local variable
339 : do-do-instance ( -- )
340 s" : .do-instance does> [ current-class @ ] literal ;"
344 \ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
345 \ ** M E T A C L A S S
346 \ Every class is an instance of metaclass. This lets
347 \ classes have methods that are different from those
348 \ of their instances.
349 \ Classes are IMMEDIATE to make early binding simpler
356 0 , \ NULL parent class
358 [ S" FICL_WANT_VCALL" ENVIRONMENT? drop ] [if]
359 4 cells , \ instance size
361 3 cells , \ instance size
366 \ now brand OBJECT's wordlist (so that ORDER can display it by name)
367 metaclass drop cell+ @ brand-wordlist
369 metaclass drop current-class !
373 \ C L A S S M E T H O D S
375 instance-vars >search
377 create .super ( class metaclass -- parent-class )
378 0 cells , do-instance-var
380 create .wid ( class metaclass -- wid ) \ return wid of class
381 1 cells , do-instance-var
383 S" FICL_WANT_VCALL" ENVIRONMENT? drop [if]
384 create .vtCount \ Number of VTABLE methods, if any
385 2 cells , do-instance-var
387 create .size ( class metaclass -- size ) \ return class's payload size
388 3 cells , do-instance-var
392 create .size ( class metaclass -- size ) \ return class's payload size
393 2 cells , do-instance-var
397 : get-size metaclass => .size @ ;
398 : get-wid metaclass => .wid @ ;
399 : get-super metaclass => .super @ ;
400 S" FICL_WANT_VCALL" ENVIRONMENT? drop [if]
401 : get-vtCount metaclass => .vtCount @ ;
402 : get-vtAdd metaclass => .vtCount ;
405 \ create an uninitialized instance of a class, leaving
406 \ the address of the new instance and its class
408 : instance ( class metaclass "name" -- instance class )
409 locals| meta parent |
411 here parent --> .do-instance \ ( inst class )
412 parent meta metaclass => get-size
413 allot \ allocate payload space
416 \ create an uninitialized array
417 : array ( n class metaclass "name" -- n instance class )
418 locals| meta parent nobj |
420 here parent --> .do-instance \ ( nobj inst class )
421 parent meta metaclass => get-size
422 nobj * allot \ allocate payload space
425 \ create an initialized instance
427 : new \ ( class metaclass "name" -- )
428 metaclass => instance --> init
431 \ create an initialized array of instances
432 : new-array ( n class metaclass "name" -- )
437 \ Create an anonymous initialized instance from the heap
438 : alloc \ ( class metaclass -- instance class )
440 class meta metaclass => get-size allocate ( -- addr fail-flag )
441 abort" allocate failed " ( -- addr )
445 \ Create an anonymous array of initialized instances from the heap
446 : alloc-array \ ( n class metaclass -- instance class )
447 locals| meta class nobj |
448 class meta metaclass => get-size
449 nobj * allocate ( -- addr fail-flag )
450 abort" allocate failed " ( -- addr )
451 nobj over class --> array-init
455 \ Create an anonymous initialized instance from the dictionary
456 : allot { 2:this -- 2:instance }
457 here ( instance-address )
458 this my=> get-size allot
459 this drop 2dup --> init
462 \ Create an anonymous array of initialized instances from the dictionary
463 : allot-array { nobj 2:this -- 2:instance }
464 here ( instance-address )
465 this my=> get-size nobj * allot
466 this drop 2dup ( 2instance 2instance )
467 nobj -rot --> array-init
470 \ create a proxy object with initialized payload address given
471 : ref ( instance-addr class metaclass "name" -- )
476 \ suspend-class and resume-class help to build mutually referent classes.
478 \ object subclass c-akbar
479 \ suspend-class ( put akbar on hold while we define jeff )
480 \ object subclass c-jeff
481 \ c-akbar ref: .akbar
482 \ ( and whatever else comprises this class )
483 \ end-class ( done with c-jeff )
484 \ c-akbar --> resume-class
486 \ ( and whatever else goes in c-akbar )
487 \ end-class ( done with c-akbar )
489 : resume-class { 2:this -- old-wid addr[size] size }
490 this --> .wid @ ficl-set-current ( old-wid )
491 this --> .size dup @ ( old-wid addr[size] size )
492 instance-vars >search
496 \ This method leaves the stack and search order ready for instance variable
497 \ building. Pushes the instance-vars wordlist onto the search order,
498 \ and sets the compilation wordlist to be the private wordlist of the
499 \ new class. The class's wordlist is deliberately NOT in the search order -
500 \ to prevent methods from getting used with wrong data.
501 \ Postcondition: leaves the address of the new class in current-class
502 : sub ( class metaclass "name" -- old-wid addr[size] size )
504 locals| wid meta parent |
505 parent meta metaclass => get-wid
506 wid wid-set-super \ set superclass
507 create immediate \ get the subclass name
508 wid brand-wordlist \ label the subclass wordlist
509 here current-class ! \ prep for do-do-instance
510 parent , \ save parent class
512 [ S" FICL_WANT_VCALL" ENVIRONMENT? drop ] [if]
513 parent meta --> get-vtCount ,
515 here parent meta --> get-size dup , ( addr[size] size )
516 metaclass => .do-instance
517 wid ficl-set-current -rot
519 instance-vars >search \ push struct builder wordlist
522 \ OFFSET-OF returns the offset of an instance variable
523 \ from the instance base address. If the next token is not
524 \ the name of in instance variable method, you get garbage
525 \ results -- there is no way at present to check for this error.
526 : offset-of ( class metaclass "name" -- offset )
527 drop find-method-xt nip >body @ ;
529 \ ID returns the string name cell-pair of its class
530 : id ( class metaclass -- c-addr u )
533 \ list methods of the class
534 : methods \ ( class meta -- )
537 class body> >name type ." methods:" cr
538 class meta --> get-wid >search words cr previous
539 class meta metaclass => get-super
544 \ list class's ancestors
545 : pedigree ( class meta -- )
548 class body> >name type space
549 class meta metaclass => get-super
554 \ decompile an instance method
555 : see ( class meta -- )
556 metaclass => get-wid >search see previous ;
558 \ debug a method of metaclass
559 \ Eg: my-class --> debug my-method
560 : debug ( class meta -- )
561 find-method-xt debug-xt ;
564 \ E N D M E T A C L A S S
566 \ ** META is a nickname for the address of METACLASS...
570 \ ** SUBCLASS is a nickname for a class's SUB method...
571 \ Subclass compilation ends when you invoke end-class
572 \ This method is late bound for safety...
575 S" FICL_WANT_VCALL" ENVIRONMENT? drop [if]
576 \ VTABLE Support extensions (Guy Carver)
577 \ object --> sub mine hasvtable
578 : hasvtable 4 + ; immediate
582 \ \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
584 \ Root of all classes
588 0 , \ NULL parent class
591 [ S" FICL_WANT_VCALL" ENVIRONMENT? drop ] [if]
597 \ now brand OBJECT's wordlist (so that ORDER can display it by name)
598 object drop cell+ @ brand-wordlist
600 object drop current-class !
602 instance-vars >search
604 \ O B J E C T M E T H O D S
605 \ Convert instance cell-pair to class cell-pair
606 \ Useful for binding class methods from an instance
607 : class ( instance class -- class metaclass )
610 \ default INIT method zero fills an instance
611 : init ( instance class -- )
613 metaclass => get-size ( inst size )
616 \ Apply INIT to an array of NOBJ objects...
618 : array-init ( nobj inst class -- )
619 0 dup locals| &init &next class inst |
621 \ bind methods outside the loop to save time
623 class s" init" lookup-method to &init
624 s" next" lookup-method to &next
629 &next execute drop to inst
633 \ free storage allocated to a heap instance by alloc or alloc-array
634 \ NOTE: not protected against errors like FREEing something that's
635 \ really in the dictionary.
636 : free \ ( instance class -- )
641 \ Instance aliases for common class methods
642 \ Upcast to parent class
643 : super ( instance class -- instance parent-class )
644 meta metaclass => get-super ;
646 : pedigree ( instance class -- )
648 metaclass => pedigree ;
650 : size ( instance class -- sizeof-instance )
652 metaclass => get-size ;
654 : methods ( instance class -- )
656 metaclass => methods ;
658 \ Array indexing methods...
660 \ 10 object-array --> index
663 : index ( n instance class -- instance[n] class )
667 metaclass => get-size * ( n*size )
670 : next ( instance[n] class -- instance[n+1] class )
674 metaclass => get-size
678 : prev ( instance[n] class -- instance[n-1] class )
682 metaclass => get-size
686 : debug ( 2this -- ?? )
687 find-method-xt debug-xt ;
692 \ reset to default search order
695 \ redefine oop in default search order to put OOP words in the search order and make them
696 \ the compiling wordlist...
698 : oo only also oop definitions ;