~vdupras/duskos

f440785490b4da379d7d4ea3b6bf704834d64d06 — Virgil Dupras 15 days ago 1a8c9e8
Introduce new ":[methods]" pattern in structures

See doc/struct. This new pattern doesn't improve code density (the opposite in
fact), but it allows to avoid painful code repetition, with the worst offender
being the IO struct. Whenever the structure of those structs that are widely
extended would change, one would have to go modify all extending structs.

Not anymore.
M fs/doc/dict.txt => fs/doc/dict.txt +4 -0
@@ 154,11 154,14 @@ allot0     u --          Allot u and fill this space with zeroes.
move       src dst u --  Copy u bytes from address src to address dst, moving
                         upwards.
move,      src u --      Copy u bytes to "here" and increase "here" by u.
-move,     src u --      Rewind "here" by u, then call "move,".
fill       a u c --      Fill range [a, a+u] with byte c.
align4     n --          Allot 0, 1, 2 or 3 bytes so that "here+n" is divisible
                         by 4.
nc,        n --          Parse n numbers from input stream and write them as
                         8-bit values.
nabort,    n --          Write address of word "abort" n times. Used for method
                         placeholders.
[c]?       c a u -- i    Search for character c in range [a, a+u] and yield its
                         index or -1 if not found.



@@ 268,6 271,7 @@ find       name 'dict -- word-or-0
  match was found.
'          "x" -- w  Find x in system dictionary and error out if not found.
[']        "x" --    *I* Find x and compile its address as a literal.
'"         "x" --    *I* Compile x as a string followed by a call to '.
w>e        w -- e    Yield an entry (linked list pointer) from a word reference.
e>w        e -- w    Yield a word reference (executable) from an entry.
entry      'dict s --

M fs/doc/struct.txt => fs/doc/struct.txt +87 -1
@@ 163,11 163,97 @@ Warning: do not augment a struct with new fields if it has already been extended
by another struct because this will generate slot conflicts. You can augment a
struct that has been extended, that will work, but only with non-field words.

## Name conventions
## Structure conventions

What is described above is the structure mechanism. That's how it works. If you
look at how structures are used in Dusk OS, you'll see some patterns emerge that
aren't described above. Those are conventions. You don't have to follow them in
your own code, but if you want to interact with Dusk structures, you need to
know about them.

A structure begins with an uppercase letter and a word in a namespace that is
intended to be called from the outside (so, not only methods) begin with a ":".

A structure "constructor" has the name ":new". Is is expected to consume
initialization arguments from PS and yield a single element: the address of the
created structure.

Structure allocation is often made directly to "here" (with dynamic allocation
details left to the caller), but not always.

When a structure extends another, it's common practive for the extending struct
to call its base struct's ":new" method and add to it.

Structure methods initialization is a bit tricky. The whole point of those
methods is to allow a structure that extends another structure to override them.
For this to be done harmoniously, the "extender" struct, at *compile* time,
needs to make a list of those methods it want to write, and overwrite the
methods that were written by the base struct. For simplicity, this is often done
using a combination of "S[" and "-move,". Example:

    struct[ MyAbstract
      smethod :foo
      : :new here ['] abort , ;
    ]struct
    extends MyAbstract struct[ MyImpl
      sfield myfield
      : impl drop 42 ;
      : :new MyAbstract :new S[ ' impl , ]S c@+ -move, 54 ( myfield ) , ;
    ]struct
    MyImpl :new dup :foo . \ prints 42
    myfield . \ prints 54

For this to work, methods need to be the last fields of the struct (but
"extenders" can add other fields on top of it), which is another convention that
Dusk OS follows.

Because the pattern above requires every struct "extender" to repeat the name
of the methods and because this is tedious and verbose, another convention
emerges from that: Method implementations have the name of the method, minus the
":" prefix, and base structs have a ":[methods]" word that dynamically look for
those names and write them. Then, "extenders" call this method at compile time
and write this string down in their ":new" implementation. This results in code
that looks like this:

    struct[ MyAbstract
      smethod :foo
      : :new here ['] abort , ;
      : :[methods] '" foo" , ;
    ]struct
    extends MyAbstract struct[ MyImpl
      sfield myfield
      : foo drop 42 ;
      : :new MyAbstract :new S[ :[methods] , ]S c@+ -move, 54 ( myfield ) , ;
    ]struct

With one "extender", the gain is slim (null in fact), but as extenders multiply,
this patterns allows us to minimize code duplication and make changes in base
structures much easier.

Note that this pattern only works for direct extension overrides (the most
frequent case). If a struct extender needs to override a method from a structure
more than 1 level deep in the hierarchy, the pattern is a bit more complex and
involves base structures creating a "METHSZ" constant. Here's an example:

    struct[ A
      sfield a
      SZ &+ :methods(
      smethod :foo
      : :new here 0 ( a ) , ['] abort , ;
      : :[methods] '" foo" , ;
    ]struct
    extends A struct[ B
      sfield b
      : :new A :new 0 ( b ) , ;
    ]struct
    extends B struct[ C
      sfield c
      : :new B :new 0 ( c ) ,
        S[ A :[methods] ]S c@+ dip over A :methods( | move ;
    ]struct

So, a bit more verbose than the direct variant, but it happens much less often.

## API

struct[ "name"  -- Create a struct named "name" and enter its definition

M fs/drv/fbgrid/fbgrid.fs => fs/drv/fbgrid/fbgrid.fs +1 -4
@@ 73,10 73,7 @@ extends Grid struct[ FbGrid
    repeat rdrop rdrop rdrop ;
  : :new ( -- grid )
    screen width 8 / screen height 8 / Grid :new
    ['] cell! over ['] :cell! sfield!
    ['] cursor! over ['] :cursor! sfield!
    ['] newln over ['] :newln sfield!
    ['] highlight over ['] :highlight sfield! ;
    S[ :[methods] ]S c@+ -move, ;
]struct

: fbgrid$ screen :activate FbGrid :new ['] grid rebind

M fs/drv/pc/com.fs => fs/drv/pc/com.fs +3 -4
@@ 19,10 19,9 @@ $3f8 const COMPORT

extends IO struct[ COM1
  create _buf 0 c,
  : _readbuf ( n self -- a? read-n )
  : readbuf ( n self -- a? read-n )
    2drop com>? if _buf tuck c! 1 else 0 then ;
  : _writebuf ( a n self -- written-n )
    2drop c@ >com 1 ;
  : :new here 0 , ['] _readbuf , ['] _writebuf , ['] drop dup , , ;
  : writebuf ( a n self -- written-n ) 2drop c@ >com 1 ;
  : :new IO :new S[ :[methods] ]S c@+ -move, ;
]struct
COM1 :new structbind COM1 com1

M fs/drv/pc/vesa.fs => fs/drv/pc/vesa.fs +8 -12
@@ 102,7 102,7 @@ $111 value vesamode

extends Screen struct[ VESA2Screen \ for VBE2
  \ Here, we deal only with linear modes
  : _activate ( self -- )
  : activate ( self -- )
    vesamode _modeinfo dup VBEModeInfo :linear? _assert ( mode )
    _curmode :self VBEModeInfo SZ move
    0 vesamode $4000 or $4f02 int10h ( self bx ax )


@@ 112,12 112,10 @@ extends Screen struct[ VESA2Screen \ for VBE2
    _curmode pitch over to pitch
    _curmode framebuffer swap to buffer ;

  : _deactivate ( self -- ) 0 to buffer vgatext! ;
  : deactivate ( self -- ) 0 to buffer vgatext! ;

  : :new ( -- screen )
    0 0 COLOR_RGB565 Screen :new ( screen )
    ['] _activate over ['] :activate sfield!
    ['] _deactivate over ['] :deactivate sfield! ;
    0 0 COLOR_RGB565 Screen :new ( screen ) S[ :[methods] ]S c@+ -move, ;
]struct

$a0000 const VESABANK \ for nonlinear modes, the address of the 64K bank


@@ 130,7 128,7 @@ extends Screen struct[ VESA1Screen \ for VBE1.2
  0 value bank \ currently activated bank
  1 value bankmult

  : _activate ( self -- )
  : activate ( self -- )
    vesamode _modeinfo dup VBEModeInfo :linear? not _assert
    dup VBEModeInfo winfuncptr _assert ( mode )
    _curmode :self VBEModeInfo SZ move


@@ 142,19 140,17 @@ extends Screen struct[ VESA1Screen \ for VBE1.2
    $40 _curmode granularity / to bankmult
    VESABANK swap to buffer ;

  : _deactivate ( self -- ) 0 to buffer vgatext! ;
  : deactivate ( self -- ) 0 to buffer vgatext! ;

  : _bank! ( n -- )
    dup bankmult * 0 $4f05 int10h 2drop ( dup 1 $4f05 int10h 2drop ) to bank ;

  : _?bank! ( off -- ) 16 rshift dup bank = if drop else _bank! then ;

  : _xyoffbank ( x y self -- n ) _xyoff dup _?bank! $ffff and ;
  : xyoff ( x y self -- n ) Plane xyoff dup _?bank! $ffff and ;

  : :new ( -- screen )
    0 0 COLOR_RGB565 Screen :new ( screen )
    0 ( bank ) ,
    ['] _activate over ['] :activate sfield!
    ['] _deactivate over ['] :deactivate sfield!
    ['] _xyoffbank over ['] :xyoff sfield! ;
    S[ :[methods] ]S c@+ -move, 0 ( bank ) ,
    S[ Plane :[methods] ]S c@+ dip over Plane :methods( | move ;
]struct

M fs/drv/pc/vga.fs => fs/drv/pc/vga.fs +1 -6
@@ 19,12 19,7 @@ extends Grid struct[ VgaGrid
  create _tbl $f , $7f ,
  : highlight ( f pos -- ) << _mem( + 1+ swap bool CELLSZ * _tbl + @ swap c! ;

  : :new ( -- grid )
    _COLS _LINES Grid :new
    ['] cell! over ['] :cell! sfield!
    ['] cursor! over ['] :cursor! sfield!
    ['] newln over ['] :newln sfield! 
    ['] highlight over ['] :highlight sfield! ;
  : :new ( -- grid ) _COLS _LINES Grid :new S[ :[methods] ]S c@+ -move, ;
]struct

\ Set video mode to text mode, 80x25

M fs/fs/fat.fs => fs/fs/fat.fs +8 -11
@@ 113,12 113,12 @@ $e5 const DIRFREE
  r> findfreedirentry dup DirEntry SZ 0 fill ( direntry )
  fnbuf( over DirEntry NAMESZ move ( direntry ) ;

:realias :newfile ( dirid name self -- id ) >r
:realias newfile ( dirid name self -- id ) >r
  r@ _newentry ( dirent ) r@ writecursector r> :getid ;

: _makedir ( dirent -- dirent ) $10 over to DirEntry attr ;

:realias :newdir ( dirid name self -- id )
:realias newdir ( dirid name self -- id )
  r! allocatecluster0 >r ( dirid name ) \ V1=self V2=cluster
  V1 _newentry ( dirent ) _makedir ( dirent )
  V2 over to DirEntry cluster V1 writecursector ( dirent )


@@ 142,10 142,10 @@ $e5 const DIRFREE
  swap r@ :FirstSectorOfCluster ( dst sec )
  swap r@ secpercluster swap r> writesectors ;

:realias :info ( id self -- info ) FATInfo :read ;
:realias info ( id self -- info ) FATInfo :read ;

\ TODO: deallocate the chain before clearing the entry
:realias :remove ( id self -- )
:realias remove ( id self -- )
  tuck :getdirentry ( dirent ) DIRFREE swap c! writecursector ;

\ Read next sector if a sequential read is available, else return false.


@@ 159,7 159,7 @@ $e5 const DIRFREE
  dup DirEntry :lastentry? if drop 0 else
    dup DirEntry :iterable? not if V1 _next then then ( entry ) rdrop ;

:realias :iter ( dirid previd self -- id-or-0 ) >r >r \ V1=self V2=previd
:realias iter ( dirid previd self -- id-or-0 ) >r >r \ V1=self V2=previd
  V1 :getdirentry V1 :readdir V1 :dirwin :buf( DirEntry SZ - V2 if begin ( entry )
      V1 _next dup while dup V1 :getid V2 <> while repeat then then ( entry-or-0 )
  dup if V1 _next dup if V1 :getid then then 2rdrop ;


@@ 204,7 204,7 @@ create _FATTemplate
]struct

struct+[ FATFile
  : _flush ( hdl -- )
  :realias flush ( hdl -- )
    r! :dirty? not if rdrop exit then ( )
    \ save buffer
    r@ cluster r@ :buf( r@ :fat writecluster ( )


@@ 213,7 213,6 @@ struct+[ FATFile
    r@ :fat writecursector
    \ undirty the cursor
    r@ flags $fffffffd and to r> flags ;
  current ' :flush realias

  \ grow fcursor to newsz, if needed
  : _grow ( newsz self -- )


@@ 227,7 226,7 @@ struct+[ FATFile
    V1 size V1 :fat :ClusterSize / ?dup if
      for ( cluster ) V1 :fat FAT@+ next then ( cluster ) drop rdrop ;

  : _writebuf ( buf n self -- n )
  :realias writebuf ( buf n self -- n )
    dup :free? if 2drop drop 0 exit then ( buf n self )
    r! pos over + r@ _grow ( src n )
    \ TODO: this seek below doesn't seem right. The buffer should be at all times


@@ 237,13 236,11 @@ struct+[ FATFile
    r@ :)buf r@ :ptr - ( src n nmax )
    min ( src n ) r@ :ptr swap ( src dst n )
    r! move r> ( n ) r@ pos over + r> :seek ;
  current ' :writebuf realias

  \ TODO: deallocate truncated FATs if appropriate
  : _truncate ( self -- )
  :realias truncate ( self -- )
    dup pos ( self pos )
    2dup swap to size ( self pos )
    over :dirent to DirEntry filesize ( self )
    :fat writecursector ;
  current ' :truncate realias
]struct

M fs/fs/fatlo.fs => fs/fs/fatlo.fs +20 -21
@@ 137,7 137,7 @@ $18 const HDRSZ
  swap r@ :FirstSectorOfCluster ( dst sec )
  swap r@ secpercluster swap r> :readsectors ;

: :child ( dirid name self -- id-or-0 ) >r
: child ( dirid name self -- id-or-0 ) >r
  fnbuf! r@ :getdirentry r@ :readdir r@ :findindir
  dup if r@ :getid then rdrop ;
]struct


@@ 162,15 162,15 @@ extends File struct[ FATFile
  : :dirent ( self -- dirent ) bi entryoff | :fat :getdirentry ;
  : :cluster0 ( self -- cl ) :dirent DirEntry cluster ;

  alias abort :writebuf
  alias drop :flush
  alias abort :truncate
  alias abort writebuf
  alias drop flush
  alias abort truncate

  : _poscluster ( self -- idx ) bi pos | bufsz / ;
  : _inbounds? ( self -- f ) bi _poscluster | clusteridx = ;
  \ set self to pos. If new pos crosses cluster boundaries compared to current
  \ pos, flush current buffer and read a new sector from disk.
  : :seek ( pos self -- )
  : seek ( pos self -- )
    dup :free? if 2drop exit then >r ( pos ) \ V1=self
    dup 0< if abort" can't seek to negative pos" then
    V1 to pos V1 _inbounds? not if


@@ 178,7 178,7 @@ extends File struct[ FATFile
      V1 :cluster0 ( idx cl ) swap for ( cl ) V1 :fat :FAT@ next ( cl )
      dup V1 :buf( V1 :fat :readcluster ( cl ) V1 to cluster then rdrop ;

  : :readbuf ( n self -- a? n )
  : readbuf ( n self -- a? n )
    dup :free? if 2drop 0 exit then ( n self )
    bi+ size | pos - ( n self maxn )
    dup 1- 0< if ( EOF ) 2drop drop 0 exit then swap >r ( n maxn ) \ V1=self


@@ 187,19 187,19 @@ extends File struct[ FATFile
    r@ :ptr r@ :)buf over - ( n a nmax )
    rot min ( a n ) dup r> to+ pos ( a n ) ;

  : :close ( self -- ) dup :flush :release ;
  : close ( self -- ) dup :flush :release ;
  : :open ( direntry self -- )
    r! :hold dup V1 :fat :getid ( dirent entryoff ) \ V1=self
    r@ to entryoff DirEntry filesize r@ to size ( )
    0 to r@ pos -1 to r> clusteridx ;

  : :new ( fat -- hdl )
    0 align4 dup to' FAT lastcursor lladd ( fat newll ) drop here >r
    0 ( putback ) , ['] :readbuf , ['] :writebuf , ['] :flush , ['] :close ,
    0 ( pos ) , 0 ( size ) , 0 ( bufptr ) , dup FAT :ClusterSize ( bufsz ) ,
    ['] :seek , ['] :truncate , ( fat ) , 0 ( flags ) , 0 ( cluster ) ,
    0 align4 dup to' FAT lastcursor lladd drop ( fat )
    File :new >r S[ :[methods] ]S c@+ -move, ( fat ) \ V1=hdl
    S[ IO :[methods] ]S c@+ r@ IO :methods( swap move
    dup ( fat ) , 0 ( flags ) , 0 ( cluster ) ,
    -1 ( clusteridx ) , 0 ( entryoff ) ,
    here r@ to bufptr r@ bufsz allot r> ;
    here r@ to bufptr FAT :ClusterSize dup allot r@ to bufsz r> ;
]struct

struct+[ FAT


@@ 208,21 208,20 @@ struct+[ FAT
      ?dup while dup CELLSZ + FATFile :free? not while llnext repeat
      nip CELLSZ + else FATFile :new then ;

  : :open ( id self -- hdl )
  : open ( id self -- hdl )
    tuck :getdirentry swap :findfreecursor ( dirent hdl )
    tuck FATFile :open ;

  alias abort :info
  alias abort :iter
  alias abort :newfile
  alias abort :newdir
  alias abort :remove
  alias abort info
  alias abort iter
  alias abort newfile
  alias abort newdir
  alias abort remove

  : :mountvolume ( drv -- fs )
    dup SectorWindow :new over SectorWindow :new rot
    here >r ( fatwin dirwin rot ) \ V1=fs
    dup , 0 ( flags ) , ['] :child , ['] :info , ['] :open ,
    ['] :iter , ['] :newfile , ['] :newdir , ['] :remove ,
    dup Filesystem :new >r ( fatwin dirwin drv ) \ V1=fs
    S[ :[methods] ]S c@+ -move,
    0 ( bufcluster ) , 0 ( lastcursor ) , rot ( fatwin ) , swap ( dirwin ) ,
    \ At this point, "here" points to the FAT-header-to-be. Read the first sector
    \ directly in "here": we'll have the header right here!

M fs/gr/plane.fs => fs/gr/plane.fs +4 -2
@@ 13,11 13,12 @@ extends Rect struct[ Plane
  sfield ty
  sfield color
  sfield buffer
  SZ &+ :methods(
  smethod :xyoff ( x y self -- n )

  : _colorbytes ( id -- nbytes ) colorbpp >> >> >> ;

  : _xyoff ( x y self -- n )
  : xyoff ( x y self -- n )
    tuck pitch * rot> encoding _colorbytes * + ;

  : _addr ( self -- a )


@@ 26,7 27,8 @@ extends Rect struct[ Plane
  : :new ( width height encoding -- plane )
    >r >r >r 0 0 r> r> Rect :new ( rect )
    r> ( encoding ) dup , _colorbytes over Rect width * ( pitch ) ,
    0 ( tx ) , 0 ( ty ) , 0 ( color ) , 0 ( buffer ) , ['] _xyoff , ;
    0 ( tx ) , 0 ( ty ) , 0 ( color ) , 0 ( buffer ) , ['] xyoff , ;
  : :[methods] '" xyoff" , ;

  : :allotbuf ( self -- ) >r \ V1=self
    here r@ pitch r@ height * allot r> to buffer ;

M fs/lib/file.fs => fs/lib/file.fs +18 -17
@@ 2,36 2,37 @@

extends File struct[ MemFile
  : _maxn ( n hdl -- real-n ) >r V1 pos + V1 size min r> pos - ;
  : _readbuf ( n hdl -- a? read-n )
  : readbuf ( n hdl -- a? read-n )
    >r V1 _maxn ( read-n ) dup if V1 :ptr swap dup V1 to+ pos then rdrop ;
  : _writebuf ( a n hdl -- written-n )
  : writebuf ( a n hdl -- written-n )
    >r V1 _maxn ( a write-n ) dup if ( a write-n )
      tuck V1 :ptr swap ( write-n a dst n ) move ( write-n ) dup V1 to+ pos
      else nip then rdrop ;
  : _seek ( pos hdl -- ) to pos ;
  : :new ( sz -- hdl ) here swap ( hdl sz )
    0 ( putback ) , ['] _readbuf , ['] _writebuf , ['] drop , ['] drop ,
    0 ( pos ) , dup ( size ) , over SZ + ( bufptr ) , dup ( bufsz ) ,
    ['] _seek , ( sz ) allot ;
  : seek ( pos hdl -- ) to pos ;
  alias drop truncate
  : :new ( sz -- hdl )
    File :new >r S[ :[methods] ]S c@+ -move, ( sz ) \ V1=hdl
    S[ IO :[methods] ]S c@+ r@ IO :methods( swap move
    dup to r@ size dup to r@ bufsz here to r@ bufptr allot r> ;
]struct

extends File struct[ DriveFile
  sfield secwin
  : :secwin [compile] secwin [compile] SectorWindow ; immediate
  : _flush :secwin :flush ;
  : _seek ( pos self -- ) to pos ;
  : _readbuf ( n self -- a? read-n )
  : flush :secwin :flush ;
  : seek ( pos self -- ) to pos ;
  : readbuf ( n self -- a? read-n )
    over if
      swap >r bi+ pos | :secwin :seek ( self a? n ) r> min
      dup if rot over swap to+ pos then
      else drop then ;
  : _writebuf ( a n self -- written-n )
    r! _readbuf ( src dst? n ) dup if
  : writebuf ( a n self -- written-n )
    r! readbuf ( src dst? n ) dup if
      r! move r> r> :secwin :dirty! else nip rdrop then ;
  alias drop truncate
  : :new ( drv -- hdl )
    SectorWindow :new here ( secwin hdl )
    0 ( putback ) , ['] _readbuf , ['] _writebuf , ['] _flush , ['] drop ,
    0 ( pos ) , -1 ( size ) , over SectorWindow :buf( ( bufptr ) ,
    over SectorWindow :drv secsz ( bufsz ) , ['] _seek , ['] drop ( truncate ) ,
    swap ( secwin ) , dup 0 -1 rot :secwin :move ;
    SectorWindow :new File :new >r S[ :[methods] ]S c@+ -move, ( secwin ) ,
    S[ IO :[methods] ]S c@+ r@ IO :methods( swap move
    -1 to r@ size r@ :secwin :buf( to r@ bufptr
    r@ :secwin :drv secsz to r@ bufsz 0 -1 r@ :secwin :move r> ;
]struct

M fs/sys/grid.fs => fs/sys/grid.fs +3 -1
@@ 39,9 39,11 @@ extends ByteWriter struct[ Grid
  : :spitoff ( self -- )
    ['] _emit swap ['] :writebyte sfield! ;

  alias 2drop highlight
  : :[methods] '" cell!" , '" cursor!" , '" newln" , '" highlight" , ;
  : :new ( cols lines -- grid )
    ['] _emit ByteWriter :new rot ( cols ) , swap ( lines ) , 0 , 0 ,
    ['] abort , ['] abort , ['] abort , ['] 2drop , ;
    4 nabort, ;

  : _dbgnum! ( n pos self -- )
    tuck :spiton tuck :.x :spitoff ;

M fs/sys/io.fs => fs/sys/io.fs +10 -10
@@ 48,14 48,14 @@ extends IO struct[ SumIO
  sfield fn
  sfield res

  : _writebuf ( a n hdl -- written-n )
  alias _ioerr readbuf
  : writebuf ( a n hdl -- written-n )
    dup >r fn >r swap >r dup >r V1 res for ( n r ) \ V1=hdl V2=fn V3=a
      8b to@+ V3 V2 execute next ( n r )
    V1 to res 2rdrop 2rdrop ( written-n ) ;
  \ fn sig: ( sum c -- sum )
  : :new ( 'fn -- hdl ) here swap ( hdl 'fn )
    0 ( putback ) , ['] _ioerr , ['] _writebuf , ['] drop , ['] drop ,
    ( 'fn ) , 0 , ;
  : :new ( 'fn -- hdl )
    IO :new S[ :[methods] ]S c@+ -move, swap ( 'fn ) , 0 , ;
]struct

extends IO struct[ MemIO


@@ 64,9 64,9 @@ extends IO struct[ MemIO
  sfield ptr

  : _bounds ( n hdl -- n ) dup )buf swap ptr - min ;
  : _readbuf ( n hdl -- a? read-n ) >r \ V1=self
  : readbuf ( n hdl -- a? read-n ) >r \ V1=self
    V1 _bounds dup if r@ ptr swap dup to+ r> ptr else rdrop then ;
  : _writebuf ( a n hdl -- written-n ) >r \ V1=self
  : writebuf ( a n hdl -- written-n ) >r \ V1=self
    V1 _bounds dup if ( a n )
      dup >r V1 ptr ( src n dst ) swap move \ V2=n
      r> dup r> ( n n hdl ) to+ ptr ( written-n )


@@ 75,17 75,17 @@ extends IO struct[ MemIO
  : :eof? dup )buf swap ptr = ;
  : :rewind ( hdl -- ) dup buf( swap to ptr ;
  : :new ( a u -- hdl )
    here >r 0 ( putback ) , ['] _readbuf , ['] _writebuf , ['] drop , ['] drop ,
    IO :new >r S[ :[methods] ]S c@+ -move,
    over , over + , , r> ;
]struct

extends IO struct[ ByteWriter
  smethod :writebyte ( c self -- )

  : _writebuf ( a n self -- written-n ) >r dup >r \ V1=self V2=n
  alias _ioerr readbuf
  : writebuf ( a n self -- written-n ) >r r! \ V1=self V2=n
    for ( a ) c@+ V1 :writebyte next drop r> rdrop ;

  : :new ( writebyte -- writer )
    here 0 ( putback ) , ['] _ioerr , ['] _writebuf , ['] drop , ['] drop ,
    swap ( writebyte ) , ;
    IO :new S[ :[methods] ]S c@+ -move, swap ( writebyte ) , ;
]struct

M fs/sys/rdln.fs => fs/sys/rdln.fs +2 -2
@@ 23,10 23,10 @@ extends MemIO struct[ Rdln
    begin key V1 :lntype until
    V1 ptr V1 to )buf V1 :rewind r> )buf 1- c@ ESC <> ;
  : _readbuf2 ( n hdl -- a? read-n )
    dup :eof? if begin ."  ok\n" dup :typeline until nl> then _readbuf ;
    dup :eof? if begin ."  ok\n" dup :typeline until nl> then readbuf ;
  : :reset ['] _readbuf2 over ['] :readbuf sfield! dup )buf swap to ptr ;
  : :interpret
    ['] _readbuf over ['] :readbuf sfield!
    ['] readbuf over ['] :readbuf sfield!
    MemIO :interpret :reset ;
  : :new MemIO :new dup :reset ;
]struct

M fs/sys/screen.fs => fs/sys/screen.fs +3 -4
@@ 6,11 6,10 @@ extends Plane struct[ Screen
  smethod :deactivate ( self -- )
  smethod :activated? ( self -- f )

  : _activated? ( self -- f ) buffer bool ;

  : activated? ( self -- f ) buffer bool ;
  : :new ( width height encoding -- screen )
    Plane :new ( screen )
    ['] abort , ['] abort , ['] _activated? , ;
    Plane :new ( screen ) S[ :[methods] ]S c@+ -move, 3 nabort, ;
  : :[methods] '" activate" , '" deactivate" , '" activated?" , ;
]struct

0 structbind Screen screen

M fs/tests/kernel.fs => fs/tests/kernel.fs +11 -0
@@ 158,4 158,15 @@ extends Foo struct[ Bazooka
create data3 7 , 9 c, ' mybleh , 999 ,
data3 Bazooka bling 999 #eq
data3 Bazooka baz 9 #eq

\ Test abstract method override mechanism
struct[ MyAbstract
  smethod :foo
  : :[new] '" foo" , ;
]struct
extends MyAbstract struct[ MyImpl
  : foo drop 42 ;
  : :new here S[ MyAbstract :[new] ]S c@+ move, ;
]struct
MyImpl :new MyAbstract :foo 42 #eq
testend

M fs/text/ed.fs => fs/text/ed.fs +6 -6
@@ 84,7 84,7 @@ extends IO struct[ Edbuf
  : :lastline? bi :lpos | :linecnt 1- = ;
  : _eof? ( self -- f ) bi :lastline? | _eol? and ;
  create _lf LF c,
  : _readbuf ( n self -- a? read-n ) >r ( n ) \ V1=self
  : readbuf ( n self -- a? read-n ) >r ( n ) \ V1=self
    r@ _eof? if rdrop drop 0 exit then
    r@ _eol? if drop 1 r@ :godown 0 r> _cpos! _lf 1 exit then
    r@ :sel Line cnt r@ :cpos - ( n1 n2 )


@@ 101,7 101,7 @@ extends IO struct[ Edbuf
    tuck V1 :cpos V2 Line :insert ( u )
    rdrop r> to+ pos ;

  : _writebuf ( a n self -- written-n ) >r \ V1=self
  : writebuf ( a n self -- written-n ) >r \ V1=self
    2dup LF rot> [c]? ( a u idx ) dup 0< if
      drop tuck r> _writeline ( written-n )
    else ( a u idx )


@@ 128,11 128,11 @@ extends IO struct[ Edbuf
      lpos V1 :lpos V1 :sel Line cnt bool + ( linehi linelo )
      tuck - 1+ swap V1 lines Array :delete V1 _ensureline then rdrop ;

  alias :empty close
  : :new ( -- edbuf )
    Arena :new Line SZ $200 Array :new here ( arena lines edbuf ) >r
    0 ( putback ) , ['] _readbuf , ['] _writebuf , ['] drop , ['] :empty ,
    swap ( arena ) , ( lines ) , 0 ( pos ) ,
    r> dup :empty ;
    Arena :new Line SZ $200 Array :new
    IO :new S[ :[methods] ]S c@+ -move, ( arena lines edbuf )
    rot ( arena ) , swap ( lines ) , 0 ( pos ) , dup :empty ;

  : :goleft ( n self -- ) dup :cpos rot - max0 swap _cpos! ;
  : :goright ( n self -- ) dup :cpos rot + over _cpos! _cbounds ;

M fs/xcomp/bootlo.fs => fs/xcomp/bootlo.fs +34 -18
@@ 253,23 253,25 @@ alias drop emit
: nl> LF emit ; : spc> SPC emit ;
:realias rtype ( a u ) for c@+ emit next drop ;
: stype ( str -- ) c@+ rtype ;
create _escapes 'n' c, 'r' c, '0' c,
create _repl    LF  c, CR  c, 0   c,
create _escapes 3 nc, 'n' 'r' '0'
create _repl    3 nc, LF  CR  0
: "< ( -- c )
  in< dup '"' = if drop -1 else dup '\' = if
    drop in< dup _escapes 3 [c]? dup 0>= if nip _repl + c@ else drop then
  then then ;
: ," begin "< dup -1 <> while c, repeat drop ;
code (s) r@ W>A, W) 8b) @, 1 W+n, RSP) +, rdrop W<>A, branchA,
: _S compiling if compile (s) else here then here 1 allot here ;
: S[ _S [compile] [ ; immediate
: ]S ( str -- ) here -^ ( 'len len ) swap c! ;
: S" _S ," [compile] ]S ; immediate
: _S[ compiling if compile (s) else here then here 1 allot here ;
: S[ _S[ [compile] [ ; immediate
: _]S ( str -- ) here -^ ( 'len len ) swap c! ;
: ]S _]S ] ;
: S" _S[ ," _]S ; immediate
: ."
  compiling if [compile] S" compile stype else
    begin "< dup 0>= while emit repeat drop then ; immediate
: abort" [compile] ." compile abort ; immediate
: word" [compile] S" NEXTWORD litn compile ! ; immediate
: '" [compile] word" compile ' ; immediate

code []= ( a1 a2 u -- f )
  W=0>Z, 0 Z) branchC, PSP) @!, W>A, begin \ P+4=a1 P+0=u A=a2


@@ 289,6 291,7 @@ code move ( src dst u -- )
  8 ps+, drop, exit,

: move, ( src u -- ) here swap dup allot move ;
: -move, ( src u -- ) here over - swap move ;

\ Structures
0 value _extends


@@ 321,7 324,7 @@ code move ( src dst u -- )
  else
    word" :self" code exit, \ :self is our root
    sysdict @ to _curroot then
  word" SZ" code _cur e>w structsz' litn compile @ exit,
  word" SZ" code _cur e>w structsz' litn W) @, exit,
does> ( 'struct )
    _structfind
    dup 1- c@ $80 and not compiling and \ compile only if not immediate


@@ 361,11 364,12 @@ create _ 0 , EMETA_8B , EMETA_16B ,
  does> CELLSZ + @ over + @ execute ;
: ssmethod doer CELLSZ STRUCTFIELD_STATICMETHOD _sfield
  does> CELLSZ + @ swap + @ execute ;
: nabort, ( n -- ) ['] abort swap for dup , next drop ;

\ 4b link to struct
\ 4b link to data
: structbind ( 'data -- ) ' doer , , immediate does> ( 'bind -- *to* )
  @+ swap compiling if litn compile @ else @ swap then execute ;
  @+ swap compiling if dup, m) @, else @ swap then execute ;
: rebind ( 'data 'bind -- ) does' CELLSZ + ! ;

struct[ Drive


@@ 373,10 377,13 @@ struct[ Drive
  sfield seccnt
  smethod :sec@ ( sec dst drv -- )
  smethod :sec! ( sec src drv -- )
  : :new ( secsz seccnt -- drv ) here rot , swap , ;
  : :[methods] '" sec@" , '" sec!" , ;
]struct

struct[ IO
  sfield putback
  SZ &+ :methods(
  smethod :readbuf   ( n hdl -- a? read-n )
  smethod :writebuf  ( a n hdl -- written-n )
  smethod :flush     ( hdl -- )


@@ 384,23 391,25 @@ struct[ IO
  : :getc ( hdl -- c )
    dup putback ?dup if ( hdl c ) 0 rot to putback else ( hdl )
      1 swap :readbuf if c@ else -1 ( EOF ) then then ;
  : :new here 0 ( putback ) , 4 nabort, ;
  alias drop close
  alias drop flush
  : :[methods] '" readbuf" , '" writebuf" , '" flush" , '" close" , ;
]struct

extends IO struct[ Pipe
  sfield readio
  sfield writeio

  : _readbuf readio :readbuf ;
  : _writebuf writeio :writebuf ;
  : _flush writeio :flush ;
  : readbuf readio :readbuf ;
  : writebuf writeio :writebuf ;
  : flush writeio :flush ;
  : :new ( readio writeio -- pipe )
    here 0 , ['] _readbuf , ['] _writebuf , ['] _flush , ['] drop ,
    rot ( readio ) , swap ( writeio ) , ;
    IO :new S[ :[methods] ]S c@+ -move, rot ( readio ) , swap ( writeio ) , ;
  : _chain! ( w1 'w2 -- ) dup @ rot swap chain swap ! ;
  : :addrfilter ( w self -- ) CELLSZ + _chain! ;
  : :addwfilter ( w self -- ) CELLSZ << + _chain! ;
  : :filters$ ( self -- )
    ['] _readbuf swap CELLSZ + !+ ['] _writebuf swap ! ;
  : :filters$ ( self -- ) ['] readbuf swap CELLSZ + !+ ['] writebuf swap ! ;
]struct

: _ioerr abort" Invalid I/O" ;


@@ 426,7 435,7 @@ struct+[ IO

struct[ Filesystem
  sfield drv
  sfield flags \ b0=writeable
  sfield flags
  smethod :child
  smethod :info
  smethod :open


@@ 436,12 445,16 @@ struct[ Filesystem
  smethod :remove
  : :drv [compile] drv [compile] Drive ; immediate
  : :writeable? flags 1 and ;
  : :new ( drv -- fs ) here swap ( drv ) , 0 ( flags ) , 7 nabort, ;
  : :[methods]
    '" child" , '" info" , '" open" , '" iter" ,
    '" newfile" , '" newdir" , '" remove" , ;
]struct
\ bootfs holds a reference to boot FS. This is used until the full sys/file
\ subsystem takes over with Path mechanics.
0 value bootfs \ has to be set before first use
0 value bootfs
extends IO struct[ File
  sfield pos      \ offset from beginning of file
  sfield pos
  sfield size
  sfield bufptr
  sfield bufsz


@@ 450,6 463,9 @@ extends IO struct[ File
  : :buf( bufptr ;
  : :)buf bi :buf( | bufsz + ;
  : :ptr bi+ pos | bufsz mod swap :buf( + ;
  : :new ( -- hdl )
    IO :new 0 ( pos ) , 0 ( size ) , 0 ( bufptr ) , 0 ( bufsz ) , 2 nabort, ;
  : :[methods] '" seek" , '" truncate" , ;
]struct

\ File loading

M posix/glue.fs => posix/glue.fs +12 -14
@@ 1,17 1,15 @@
: _ doer ' , does> nip @ execute ;
_ _:child _fchild    _ _:open _fopen  _ _:info _finfo _ _:iter _fiter

create _POSIXFS
  0 , 0 ,
  ' _:child ,
  ' _:info ,
  ' _:open ,
  ' _:iter ,
  ' abort ,
  ' abort ,
  ' abort ,

_POSIXFS to bootfs
extends Filesystem struct[ POSIXFS
  : _ doer ' , does> nip @ execute ;
  _ child _fchild
  _ open _fopen
  _ info _finfo
  _ iter _fiter
  alias abort newfile
  alias abort newdir
  alias abort remove
  : :new 0 Filesystem :new S[ :[methods] ]S c@+ -move, ;
]struct
POSIXFS :new to bootfs

: mountImage ( imgname -- drv )
  _mountdrv here 512 , -1 , ['] _drv@ , ['] _drv! , ;