duskos

dusk os fork
git clone git://git.alexwennerberg.com/duskos
Log | Files | Refs | README | LICENSE

commit 39758c47430b9d5cf0b8989ab82f983f1b64599b
parent 14ca909f3d424d6a926f12414c4ca0e38f9c17f2
Author: Virgil Dupras <hsoft@hardcoded.net>
Date:   Fri,  2 Dec 2022 21:09:53 -0500

comp/c: simplify Forth proxying mechanism

We scrap the "signature metadata" system and now we simply have C units (or the
stdlib) declare prototypes for Forth words they want to use.

See doc/cc/usage

Diffstat:
Mfs/comp/c/lib.fs | 58+++++++++++++++++++++++++++++++++++++---------------------
Mfs/comp/c/pgen.fs | 31++++++++++++++-----------------
Mfs/doc/cc/lib.txt | 13+++++++++++++
Mfs/doc/cc/usage.txt | 53++++++++++++++++++++++++++++++++++-------------------
Mfs/lib/meta.fs | 10----------
Mfs/sys/io.fs | 3---
Mfs/tests/comp/c/cc.fs | 3+--
Mfs/tests/comp/c/lib.fs | 7+++++++
Mfs/tests/comp/c/test.c | 11+++--------
Mfs/tests/lib/meta.fs | 11-----------
Mfs/xcomp/bootlo.fs | 22+++++++---------------
11 files changed, 116 insertions(+), 106 deletions(-)

diff --git a/fs/comp/c/lib.fs b/fs/comp/c/lib.fs @@ -4,52 +4,68 @@ ?f<< /lib/str.fs ?f<< /lib/fmt.fs -0 const NULL -(S str -- len ) +\ A few system word proxies +:c void abort(); +:c unsigned int min(unsigned int a, unsigned int b); +:c unsigned int max(unsigned int a, unsigned int b); +:c void stype(char *str); +:c int StdIn(); +:c int StdOut(); +:c int ConsoleIn(); +:c int ConsoleOut(); +:c unsigned char stdin(); +:c void stdout(unsigned char c); + : strlen c@ ; -(S a c n -- ) -: memset swap fill ; +:c unsigned int strlen(char *str); -$100 MemFile :new const _sfile +: memset swap fill ; +:c void memset(char *a, char c, unsigned int n); -(S hdl -- c ) : fgetc IO :getc ; -(S c hdl -- ) +:c char fgetc(int hdl); : fputc IO :putc ; -(S c hdl -- ) +:c void fputc(char c, int hdl); : fputback IO :putback ; -(S hdl -- str-or-0 ) +:c void fputback(char c, int hdl); : freadline IO :readline ; -(S str -- hdl ) +:c char* freadline(int hdl); : fopen curpath :find# Path :open ; -(S a n hdl -- ) +:c int fopen(char *path); + : fread IO :read ; -(S a n hdl -- ) +:c void fread(char *a, unsigned int n, int hdl); + : fwrite IO :write ; -(S 'a n hdl -- read-n ) +:c void fwrite(char *a, unsigned int n, int hdl); : freadbuf IO :readbuf ( 'a a? read-n ) dup if ( 'a a read-n ) rot> swap ! else ( 'a 0 ) nip then ; -(S a n hdl -- written-n ) +:c unsigned int freadbuf(char **a, unsigned int n, int hdl); : fwritebuf IO :writebuf ; -(S pos hdl -- ) +:c unsigned int fwritebuf(char *a, unsigned int n, int hdl); : fseek File :seek ; -(S hdl -- ) +:c void fseek(unsigned int pos, int hdl); : fclose IO :close ; +:c void fclose(int hdl); -(S str hdl -- ) : fputs IO :puts ; +:c void fputs(char *str, int hdl); : puts ( str ) StdOut fputs ; +:c void puts(char *str); : fprintf IO :printf ; +:c void fprintf(char *fmt, int hdl); : printf ( .. n1? n0? fmt -- ) StdOut fprintf ; +:c void printf(char *fmt); -(S ... fmt -- str ) +$100 MemFile :new const _sfile : sprintf 1 to _sfile MemFile pos _sfile fprintf _sfile MemFile pos 1- ( len ) _sfile MemFile :buf( tuck c! ; +:c char* sprintf(char *fmt); -(S c -- f ) : isdigit 0-9? ; +:c int isdigit(char c); :c void fscanf() { void *iohdl = pspop(); @@ -94,12 +110,12 @@ $100 MemFile :new const _sfile pspush(res); } -(S fmt -- f ) : scanf StdOut fscanf ; -(S fmt str -- f ) +:c int scanf(char* fmt); : sscanf 0 _sfile MemFile :seek c@+ _sfile IO :write 0 _sfile MemFile :seek _sfile fscanf ; +:c int sscanf(char* fmt, char *str); :c struct File { unsigned int putback; diff --git a/fs/comp/c/pgen.fs b/fs/comp/c/pgen.fs @@ -31,7 +31,6 @@ $400 const MAXLITSZ : spit ( a u -- ) swap >r >r begin r@ 40 mod not if nl> then 8b to@+ V1 .x1 next rdrop ; -: wordfunctype ( w -- type ) wordsig nip 1 = if TYPE_UINT else TYPE_VOID then ; \ Unary operators 7 const UOPSCNT @@ -195,15 +194,12 @@ alias noop parseFactor ( tok -- ) \ forward declaration \ we have a func call and its target in in vmop : _funcall ( -- ) - vmop type ctype? if - \ We either have a direct function signature or a pointer to it. - \ TODO: :funcptr? doesn't work correctly here. fix this - vmop type ctype' - dup CType :funcsig? not if CType type ctype' then - dup CType :funcsig? _assert CType type - else - vmop loc VM_CONSTANT = - if vmop arg wordfunctype else TYPE_VOID then then ( type ) + vmop type ctype? _assert + \ We either have a direct function signature or a pointer to it. + \ TODO: :funcptr? doesn't work correctly here. fix this + vmop type ctype' + dup CType :funcsig? not if CType type ctype' then + dup CType :funcsig? _assert CType type ( type ) ')' readChar? if 0 else ( type tok ) to nexttputback vmop :push >r \ V1=callop MAXARGCNT CELLSZ * Stack SZ + @@ -290,11 +286,7 @@ MAXLITSZ Stack :new structbind Stack _list nextt parseFactor ( opid ) \ vmop is set uopgentbl swap wexec endof of isIdent? \ lvalue, FunCall or macro - r@ findIdent ?dup if ctype>op else - r@ sysdict @ find ?dup not if r@ stype abort" not found" then - TYPE_VOID to vmop type const>op then - parsePostfixOp - endof + r@ findIdent ?dup _assert ctype>op parsePostfixOp endof r@ parse if const>op else _err then endcase ; current to parseFactor @@ -432,8 +424,13 @@ current to parseStatement 0 to _curfunc ; : parseFunctionProto ( ctype tok -- ) - ';' expectChar dup CType :incomplete! dup addSymbol - here swap to CType offset -1 vmjmp, ; + ';' expectChar dup addSymbol curstatic if + dup CType :incomplete! here ['] _err vmjmp, + \ allot a little extra space in case the replacement jump is wider + CELLSZ allot + else dup CType name sysdict @ find ?dup not if + CType name stype abort" not found" then then ( ctype addr ) + swap to CType offset ; : parseGlobalDecl ( ctype -- ) dup addSymbol diff --git a/fs/doc/cc/lib.txt b/fs/doc/cc/lib.txt @@ -12,6 +12,19 @@ ensure that it's loaded with `?f<< /comp/c/lib.fs`. ## Standard Library API +### Proxied Forth words + +void abort(); +unsigned int min(unsigned int a, unsigned int b); +unsigned int max(unsigned int a, unsigned int b); +void stype(char *str); +int StdIn(); +int StdOut(); +int ConsoleIn(); +int ConsoleOut(); +unsigned char stdin(); +void stdout(unsigned char c); + ### Strings unsigned char strlen(char *str) diff --git a/fs/doc/cc/usage.txt b/fs/doc/cc/usage.txt @@ -58,25 +58,6 @@ are a few differences: to write that?!?) must be written as "a ? (b ? c : d) : (e ? f : g)" * The keyword "static" has a slightly different meaning. See below. -## Calling Forth words - -Words from the system dictionary can be called. They are considered to have a -void return type and an unspecified number of arguments. - -Arguments to Forth words can be passed normally, but return values have to be -handled with pspop() and pspush(). Whenever you call such a function, you should -return to "PS normality" before using one of your function arguments, because if -you don't, PS offsets for those arguments will be wrong. - -For example, let's say that you want to call "max", a forth word with a -signature "a b -- n". You would do so like this: - -int mymax(int a, int b) { - max(a, b); - // don't use a or b before having called pspop(), they're broken. - return pspop(); -} - ## Pre-processor The pre-processor allows you to define text expansion macros which can then be @@ -100,6 +81,40 @@ is the exact same equivalent to: int bar() { return 42; } +## Function prototypes + +A function prototype is a function without a body. Examples: + + static int foo(short a, char b); + unsigned int max(unsigned int a, unsigned int b); + +There are two types of prototypes, static and non-static, with both a completely +different usage. + +Static prototypes are for forward declarations. When you declare them, it +allocates enough space for a jump. Then, when the real function is declared, it +writes a jump to itself in that reserved space. + +The static attribute of the prototype is not carried to the implementation +function. You can very well have a prototype (which uses "static") that is a +forward declaration to a non-static implementation. + +The non-static prototype is to allow C to call Forth words. By default, C has no +visibility to the Forth dictionary because Forth words don't have C function +signatures. When you declare a static function prototype, the CC looks into the +system dictionary for a word of that name and links that symbol to the found +word. There's an error if not found. + +You can only link words that have a signature compatible with C, that is: 0 or 1 +return value. + +Be aware that if you link words that do fancy things like shrinking PS or that +modifying RS, you are on risky grounds and you should know what you do. The best +approach with these situation is to proxy the word as "void()" and use "pspop()" +and "pspush()" for argument passing. + +Also, note that CC's lib (doc/cc/lib) already proxies quite a few system words. + ## Symbols, types and macro visibility and lifetime The C compiler create two kinds of artifacts: types and symbols. diff --git a/fs/lib/meta.fs b/fs/lib/meta.fs @@ -22,13 +22,3 @@ ENTRYSZ neg &+ 'emeta : .doc ( w -- ) emeta begin ( ll ) EMETA_DOCLINE swap findemeta ?dup while ( ll ) dup 'emetadata begin c@+ dup emit LF = until drop llnext repeat ; - -\ Signature -: wordsig ( w -- argcnt retcnt ) - emeta EMETA_SIGCNT swap findemeta dup if ( ll ) - 'emetadata dup c@ swap 1+ c@ else ( 0 ) 0 then ; - -\ Define signatures for some words defined before (S -: _ nextmeta @ ' 'emeta llappend 0 nextmeta ! ; -(S a b -- n ) _ max -(S a b -- n ) _ min diff --git a/fs/sys/io.fs b/fs/sys/io.fs @@ -21,7 +21,6 @@ struct+[ IO create _buf( $100 allot here value _)buf - (S hdl -- str-or-0 ) : :readline dup :getc dup 0< if ( EOF ) 2drop 0 exit then ( hdl c ) swap >r _buf( 1+ >r begin ( c ) \ V1=hdl V2=buf @@ -33,9 +32,7 @@ struct+[ IO -1 V1 :readbuf ?dup while ( a n ) V2 :write repeat rdrop rdrop ; ]struct -(S -- c ) : stdin StdIn IO :getc ; -(S c -- ) : stdout StdOut IO :putc ; : stdio$ ConsoleIn to StdIn ConsoleOut to StdOut ; diff --git a/fs/tests/comp/c/cc.fs b/fs/tests/comp/c/cc.fs @@ -39,8 +39,7 @@ global 1234 #eq globalinc 1236 #eq globalinc 1238 #eq 1 globalshort 2 #eq -42 142 sysword 142 #eq -42 142 funcsig 142 #eq +42 142 funcsig 184 #eq capture helloworld S" Hello World!" #s= create expected ," Null terminated\0" nullstr expected 16 []= # diff --git a/fs/tests/comp/c/lib.fs b/fs/tests/comp/c/lib.fs @@ -3,6 +3,12 @@ ?f<< comp/c/lib.fs testbegin \ Tests for the C library +\ "max" is a forth word defined in the system +:c int sysword(int a, int b) { + return max(a, b); +} +42 142 sysword 142 #eq + :c int foo() { return strlen("Hello World!"); } foo 12 #eq @@ -22,6 +28,7 @@ S" bar" 42 43 44 45 capture foo S" foo 45 2c 002b 0000002a bar" #s= capture foo S" Hello World!" #s= $20 MemFile :new const memfile +:c int memfile(); S" What about this?" const s :c void foo() { fputs("What about this?", memfile()); } foo diff --git a/fs/tests/comp/c/test.c b/fs/tests/comp/c/test.c @@ -156,18 +156,13 @@ short globalshort(int idx) { return shortarray[idx]; } -// "max" is a forth word defined in the system -int sysword(int a, int b) { - return max(a, b); -} - -typedef unsigned int (*MaxSig)(unsigned int, unsigned int); -// Same as above, but through a function signature typedef +typedef unsigned int (*AdderSig)(unsigned int, unsigned int); int funcsig(int a, int b) { - MaxSig fn = max; + AdderSig fn = adder; return fn(a, b); } +void stype(char *str); static char *msgs[1] = {"Hello World!"}; void helloworld() { stype(msgs[0]); diff --git a/fs/tests/lib/meta.fs b/fs/tests/lib/meta.fs @@ -1,17 +1,6 @@ ?f<< /tests/harness.fs ?f<< /lib/meta.fs testbegin -\ signatures -42 value foo -' foo wordsig 1 #eq 0 #eq -' here wordsig 1 #eq 0 #eq -' max wordsig 1 #eq 2 #eq -(S a b c -- d e ) -: someword ; -' someword wordsig 2 #eq 3 #eq -: someword ; -' someword wordsig 0 #eq 0 #eq - \ metadata : bar ; ' bar emeta 0 #eq diff --git a/fs/xcomp/bootlo.fs b/fs/xcomp/bootlo.fs @@ -104,6 +104,9 @@ create _ 0 , : toexec ( a w -- ) compiling if swap litn 0 toptr @! ?dup if nip then execute, else 0 toptr @! ?dup if nip then execute then ; +: value doer , immediate does> ['] @ toexec ; +: ivalue doer , immediate does> @ ['] @ toexec ; +HERE ivalue here : _ @ execute ; : alias ' doer , immediate does> ['] _ toexec ; : ialias doer , immediate does> @ ['] _ toexec ; @@ -154,8 +157,8 @@ $20 const SPC $0d const CR $0a const LF $08 const BS \ emit all chars of "str" : stype ( str -- ) c@+ rtype ; : S" ( comp: -- ) ( not-comp: -- str ) - compiling if compile (s) else HERE @ then - HERE @ 1 allot HERE @ ," HERE @ -^ ( 'len len ) swap c! ; immediate + compiling if compile (s) else here then + here 1 allot here ," here -^ ( 'len len ) swap c! ; immediate : ." compiling if [compile] S" compile stype else begin "< dup 0>= while emit repeat drop then ; immediate @@ -174,25 +177,14 @@ alias noop [then] begin 2dup llnext = not while llnext ?dup while repeat abort" llprev failed" then nip ; : llappend ( elem ll -- ) llend ! ; -: lladd ( ll -- newll ) HERE @ swap llappend HERE @ 0 , ; -: llinsert ( 'll -- newll ) HERE @ over @ , ( 'll newll ) dup rot ! ; +: lladd ( ll -- newll ) here swap llappend here 0 , ; +: llinsert ( 'll -- newll ) here over @ , ( 'll newll ) dup rot ! ; : llcnt ( ll -- count ) 0 >r begin ?dup while 1 to+ V1 llnext repeat r> ; \ Entry metadata 1 const EMETA_DOCLINE \ a doc strings that ends with LF \ a \\ comment goes before the creation of the word it comments : \\ nextmeta lladd drop EMETA_DOCLINE , begin IN< @ execute dup c, LF = until ; -2 const EMETA_SIGCNT \ 1b arg count, 1b result count -: sigcnt, ( argcnt retcnt ll -- ) lladd drop EMETA_SIGCNT , swap c, c, ; -: (S -1 begin ( cnt ) - 1+ word S" )" s= if abort" not a signature" then curword S" --" s= until - -1 begin ( cnt ) 1+ word S" )" s= until nextmeta sigcnt, ; - -: value 0 1 nextmeta sigcnt, doer , immediate does> ['] @ toexec ; -: ivalue 0 1 nextmeta sigcnt, doer , immediate does> @ ['] @ toexec ; -HERE ivalue here -\ re-declare with sigcnt -: const 0 1 nextmeta sigcnt, code litn exit, ; \ Alias chaining. See doc/usage. : _ ( 'target 'alias -- )