UNPKG

webforth

Version:

Forth implemented in Javascript

341 lines (315 loc) 13.5 kB
// eslint-disable-next-line import/extensions import fs from 'fs'; import { Forth, ForthNodeExtensions } from './index.js'; // Normally this would be: import Forth from 'webforth'; /* This extends the standard Forth class to add capability for Files. It can't be fully integrated because of its dependence on "fs" which cannot be imported (I don't think) on a browser. */ /* Files design decisions At this point no local data is stored - File descriptors are returned. If reqd, this could shift to storing the fd in a CREATE DOES> structure See: https://github.com/mitra42/webForth/issues/34 https://nodejs.org/api/fs.html and https://man7.org/linux/man-pages/man2/open.2.html */ const lineEnding = ` `; const fsDescriptors = {}; // Filled with info available to JS only (opaque to Forth) const fsExtensions = [ { f: function JSerrToCounted(err) { // Place error or zero on stack if (err) { this.JStoCounted(err.message); } else { this.SPpush(0); } } }, { n: 'OPEN-FILE', // c-addr u fam -- fileid ior ; https://forth-standard.org/standard/file/OPEN-FILE eg. : X ... S" TEST.FTH" R/W OPEN-FILE ABORT" OPEN-FILE FAILED" ... ; f() { // TODO refactor out the common parts of this - probably a Promise with the results of a callback. const fam = this.SPpop(); const filepath = this.SPpopString(); return new Promise( (resolve) => fs.open(filepath, fam, // # TODO-FILES figure out how map flags unix wants to a constant see https://man7.org/linux/man-pages/man2/open.2.html and header files it points at (err, fd) => { fsDescriptors[fd] = { filepath, fam, position: 0 }; this.SPpush(fd || 0); // will be indeterminate if error // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); }), ); }, }, { n: 'CLOSE-FILE', // fileid -- ior ; https://forth-standard.org/standard/file/CLOSE-FILE f() { return new Promise((resolve) => { const fd = this.SPpop(); fs.close(fd, (err) => { delete fsDescriptors[fd]; // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); }); }); }, }, { n: 'DELETE-FILE', // c-addr u -- ior ; https://forth-standard.org/standard/file/DELETE-FILE f() { return new Promise((resolve) => { const filename = this.SPpopString(); fs.unlink(filename, (err) => { // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); }); }); }, }, /* { n: 'exists-file', // c-addr u -- F f() { const u = SPpop(); const filepath = this.SPpopString(); return new Promise((resolve) => fs.access(filepath, fs.constants.F_OK, (err) => {this.SPpush(err ? l.FALSE : l.TRUE); resolve(); } ))} }, */ { n: 'CREATE-FILE', // c-addr u fam -- fileid ior ; https://forth-standard.org/standard/file/CREATE-FILE) f() { const fam = this.SPpop() | fs.constants.O_CREAT | fs.constants.O_TRUNC; const filepath = this.SPpopString(); return new Promise((resolve) => fs.open(filepath, fam, 0o666, (err, fd) => { fsDescriptors[fd] = { filepath, fam, position: 0 }; this.SPpush(fd || 0); // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'FILE-SIZE', // fileid -- ud ior ; https://forth-standard.org/standard/file/FILE-SIZE ) f() { const fd = this.SPpop(); return new Promise((resolve) => fs.fstat(fd, (err, stats) => { this.SPpushD(stats ? stats.size : 0); // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'FLUSH-FILE', // fileid -- ior ; https://forth-standard.org/standard/file/FLUSH-FILE ) f() { const fd = this.SPpop(); return new Promise((resolve) => fs.fsync(fd, (err) => { // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'RENAME-FILE', // c-addr1 u1 c-addr2 u2 -- ior ; https://forth-standard.org/standard/file/RENAME-FILE ) f() { const filepath2 = this.SPpopString(); const filepath1 = this.SPpopString(); return new Promise((resolve) => fs.rename(filepath1, filepath2, (err) => { // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'RESIZE-FILE', // ud fileid -- ior ; https://forth-standard.org/standard/file/RESIZE-FILE ) // TODO This is probably not compliant - FILE-SIZE probably wont match if len > current size f() { const fd = this.SPpop(); const len = this.SPpopD(); fsDescriptors[fd].position = Math.min(fsDescriptors[fd].position, len); return new Promise((resolve) => fs.truncate(fd, len, (err) => { // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'READ-FILE', // ( c-addr u1 fileid -- u2 ior ) https://forth-standard.org/standard/file/READ-FILE f() { const fd = this.SPpop(); const len = this.SPpop(); const caddr = this.SPpop(); const buf = this.m.buff8(caddr, len); // THis is exposing something that probably shouldnt be return new Promise((resolve) => fs.read(fd, buf, 0, len, fsDescriptors[fd].position, (err, bytesRead, unusedBuf) => { if (!err) { fsDescriptors[fd].position += bytesRead; } this.SPpush(bytesRead || 0); // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'WRITE-FILE', // ( c-addr u fileid -- ior ; https://forth-standard.org/standard/file/WRITE-FILE ) f() { const fd = this.SPpop(); const len = this.SPpop(); const caddr = this.SPpop(); const buf = this.m.buff8(caddr, len); return new Promise((resolve) => fs.write(fd, buf, 0, len, fsDescriptors[fd].position, (err, bytesWritten, unusedBuf) => { if (!err) { fsDescriptors[fd].position += bytesWritten; } // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'write-cr', // ( fileid -- ior ; https://forth-standard.org/standard/file/WRITE-FILE ) f() { const fd = this.SPpop(); return new Promise((resolve) => fs.write(fd, lineEnding, fsDescriptors[fd].position, 'utf8', (err, bytesWritten, unusedBuf) => { if (!err) { fsDescriptors[fd].position += bytesWritten; } // noinspection JSUnresolvedFunction this.JSerrToCounted(err); resolve(); })); }, }, { n: 'FILE-POSITION', // fileid - ud iod ; https://forth-standard.org/standard/file/FILE-POSITION and https://www.npmjs.com/package/fs-ext) f() { // fileid - ud iod ; https://forth-standard.org/standard/file/FILE-POSITION and https://www.npmjs.com/package/fs-ext) const fd = this.SPpop(); this.SPpushD(fsDescriptors[fd].position); this.SPpush(0); }, }, { n: 'REPOSITION-FILE', // ud fileid -- ior ; https://forth-standard.org/standard/file/REPOSITION-FILE ) f() { const fd = this.SPpop(); // noinspection UnnecessaryLocalVariableJS const pos = this.SPpopD(); // Cant reposition in node so set internal position which is used on each read/write fsDescriptors[fd].position = pos; // console.log("Setting fd:", fd,"to pos", pos, "#TIB=", this.Ufetch(16)); this.SPpush(0); }, }, { n: 'R/O', constant: fs.constants.O_RDONLY }, { n: 'R/W', constant: fs.constants.O_RDWR }, { n: 'W/O', constant: fs.constants.O_WRONLY }, ]; const filesExtension = ` : BIN ; \\ No-op its always binary : WRITE-LINE ( c-addr u fileid -- ior ; https://forth-standard.org/standard/file/WRITE-LINE ) DUP >R WRITE-FILE R> SWAP ?DUP 0= IF write-cr ELSE DROP THEN ; : eof? ( fd -- F ) DUP FILE-POSITION THROW ( fd udouble ) ROT FILE-SIZE THROW ( udouble udouble ) ud< 0= ; : reposRelativeFile ( ud fd - ior ) DUP >R FILE-POSITION ( ud ud' ior ^ fd ) ?DUP IF R> DROP >R 2DROP 2DROP R> EXIT THEN ( ud ud' ^ fd ) D+ R> REPOSITION-FILE ( ior ) ; :NONAME ( fd - ior; Move fd back to >IN, so reading will start where left off : unreadFile ) SOURCE + 1024 SOURCE NIP - ( fd TIB+#TIB 1024-#TIB) skipCRLF ( ptr after crlf, number to end of buf) NIP 1024 SWAP - >IN @ - 0 DNEGATE ROT ( ud fd ; size of remaining buf + crlf* ) 0 >IN ! 0 #TIB ! ( Reset pointers so dont try and read it) reposRelativeFile ( ior ) ; ' unreadFile DEFER! ( vector forward reference ) :NONAME ( caddr umax fd -- u2 flag ior ; https://forth-standard.org/standard/file/READ-LINE : READ-LINE) DUP eof? IF DROP 2DROP 0 0 0 EXIT THEN ( caddr umax fd ) >R OVER SWAP R@ ( caddr caddr umax fd ^ fd ) READ-FILE ( caddr u ior ^ fd ) ?DUP IF R> DROP EXIT THEN ( caddr u ^ fd ) 2DUP skipToCRLF ( c u c' u' ^ fd ) DUP >R SWAP >R - R> R> ( caddr u~ caddr' u' ^ fd ; u~ is size up to but excluding first crlf u' is amount from crlf to end ) skipCRLF ( caddr u~ caddr" u" ^ fd ; u" is size from first after CRLF to end) NIP 0 DNEGATE R> reposRelativeFile ( position file to after crlf ) >R NIP TRUE R> ( u~ T ior ) ; ' READ-LINE DEFER! vCREATE buf 1024 vALLOT : INCLUDE-FILE ( i * x fileid -- j * x ; https://forth-standard.org/standard/file/INCLUDE-FILE ) sourcePush buf 0 0 4 RESTORE-INPUT THROW ; : INCLUDED ( caddr u -- ; https://forth-standard.org/standard/file/INCLUDED e.g. $" filename" INCLUDED ) R/O OPEN-FILE ( fileid ior ) THROW INCLUDE-FILE ; : INCLUDE PARSE-NAME INCLUDED ; ( i*x "name" -- j*x ; https://forth-standard.org/standard/file/INCLUDE ) ( TODO-34-FILES blocks - this would be handled in SOURCE I think https://forth-standard.org/standard/block) : ALLOCATE ( u -- a ior; Shortcut to https://forth-standard.org/standard/memory/ALLOCATE rewrite if want to use FREE) HERE SWAP ALLOT 0 ; : n2sign DUP IF 0< IF -1 ELSE 1 THEN THEN ; ( u -- -1 | 0 | 1) : COMPARE ( c-addr1 u1 c-addr2 u2 -- n ; https://forth-standard.org/standard/string/COMPARE ) ROT SWAP 2DUP 2>R \\ c1 c2 u1 u2 ^ u1,u2 ; save lengths MIN FOR AFT \\ c1 c2 ^ u' u1,u2 ; iterate over minimum length 1+ SWAP 1+ SWAP OVER C@ OVER C@ - \\ c1 c2 char1-char2 ?DUP IF \\ c1 c2 char1-char2 ^ u' u1,u2 NIP NIP R> DROP 2R> 2DROP \\ char1-char2 n2sign EXIT THEN \\ c1 c2 ^ u' u1,u2 THEN NEXT \\ c1 c2 ^ u1,u2 2DROP 2R> - \\ u1-u2 n2sign ; \\ TODO Non conformant definition should be -1|0|1 not 0|n \\ Implementation comes from https://forth-standard.org/standard/file/REQUIRED except ... : save-mem ( addr1 u -- addr2 u ) \\ gforth \\ copy a memory block into a newly allocated region in the heap SWAP >R HERE OVER ALLOT SWAP 2DUP R> ROT ROT MOVE ; : name-add ( addr u listp -- ) >R save-mem ( addr1 u ) 3 CELLS ALLOCATE THROW \\ allocate list node R@ @ OVER ! \\ set next pointer DUP R> ! \\ store current node in list var CELL+ 2! ; : name-present? ( addr u list -- f ) ROT ROT 2>R BEGIN ( list R: addr u ) DUP WHILE DUP CELL+ 2@ 2R@ COMPARE 0= IF DROP 2R> 2DROP TRUE EXIT THEN @ REPEAT ( DROP 0 ) 2R> 2DROP ; : name-join ( addr u list -- ) >R 2DUP R@ @ name-present? IF R> DROP 2DROP ELSE R> name-add THEN ; VARIABLE included-names 0 included-names ! : included ( i*x addr u -- j*x ) 2DUP included-names name-join INCLUDED ; : REQUIRED ( i*x addr u -- i*x ) 2DUP included-names @ name-present? 0= IF included ELSE 2DROP THEN ; : REQUIRE [COMPILE] S" REQUIRED ; ( ==== BELOW HERE STILL NEED DEFINING ==== ) ( : S\\" ( "ccc<quote>" -- c-addr u ; read and translate escape chars ) ( : ( ( needs to skip till gets to end of file ) ( TODO-34-FILES check second group of words on standard ) : TESTFILES S" TEST.FTH" W/O CREATE-FILE THROW ( fd ) DUP S" Hello world" ROT WRITE-LINE THROW ( fd ) DUP S" Hello earth" ROT WRITE-LINE THROW ( fd ) DUP FILE-POSITION THROW DROP . ." of " DUP FILE-SIZE THROW DROP . ." bytes " DUP FLUSH-FILE THROW CLOSE-FILE THROW ( ) S" TEST.FTH" S" TEST2.FTH" RENAME-FILE THROW ( ) S" TEST2.FTH" R/W OPEN-FILE THROW ( fd ) DUP buf 1024 ROT READ-FILE THROW ( fd u ) buf SWAP TYPE ( fd ) DUP FILE-POSITION THROW DROP . ." of " ( fd) DUP FILE-SIZE THROW DROP . ." bytes " ( fd) DUP 6 0 ROT REPOSITION-FILE THROW ( fd) DUP FILE-POSITION THROW DROP ." repositioned to " . ( fd -- reposition to "world" ) DUP buf 1024 ROT READ-FILE THROW ( fd u ) buf SWAP TYPE ( fd ) CLOSE-FILE THROW ( ) S" TEST2.FTH" R/O OPEN-FILE THROW BEGIN DUP buf 1024 ROT READ-LINE THROW ( fd u T|F ) WHILE buf SWAP TYPE CR REPEAT DROP ( fd ) CLOSE-FILE THROW ( ) S" TEST2.FTH" DELETE-FILE THROW ; : TESTER ." starting test" ['] TESTFILES CATCH ?DUP .$ ." After " ; `; class Forth_with_fs extends Forth { constructor(opts) { // This constructor just passes on the arguments for Forth mostly unchanged opts.rqFiles = -1; // Make sure hooks are there for files super(opts); // Note this will add any 'extensions' from opts (typically ForthNodeExtensions) // Has to add extensions be before compileForthInForth as uses READ-LINE fsExtensions.forEach((e) => this.extensionAdd(e)); } async initialize() { // TODO-34-FILES can we use EVALUATE or similar for filesExtension or is that self-reference await this.compileForthInForth(); await this.interpret(filesExtension); } } export { Forth_with_fs, ForthNodeExtensions };