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