# forthling > A Forth-2012 runtime in Rust, for people and AI agents. The core has no dependencies and no `unsafe`; untrusted code runs in a sandbox with a limit on steps (the same result on every machine), time and output, and with files only in memory. It has JSON words, a tutorial, SQLite in memory, an MCP server (`forthling-mcp`) and a playground in the browser (WebAssembly). MIT licence. This file is for language models that write Forth for forthling, mostly through the MCP server. Everything here was run in forthling. ## Using the MCP server - `forth_eval` runs code (`code`, and optionally `data`); `forth_stack` shows the stacks; `forth_words` lists the words defined in the session; `forth_help` gives the stack effect and a line of explanation of a word (`HELP name` in Forth); `forth_reset` starts over. - The session keeps its definitions, variables and stacks between calls. Define words in one call and use them in the next. - Each result has the output, the data stack (the top last), the float stack, whether a definition is still open (`compiling`), and the error if there was one: `{"code": -13, "message": "undefined word", "detail": "FOO"}`. - An error empties the stacks, but the result shows them as they were at the error (`stack`, `fstack`), where it happened (`line`, `column`, `token`) and the words that were running (`words`, such as `MAIN > STEP > /`). A runaway stack or recursion shows only the top 32 cells and the innermost 32 words (`stack_depth`, `words_depth` give the full count). An undefined word that is in a word list outside the search order gets a `hint`, such as `ALSO JSON finds it`. Run small pieces, look at the stack, then go on. - To see what code does word by word, pass `trace: true` to `forth_eval`: the result then has `trace`, each word that ran with its nesting `depth` and the data stack when it started (at most 1000 words; the insides of built-in words are left out). In code, `ALSO DEBUG TRACE-ON … TRACE-OFF` prints the same as output. - To look at one place, put `BREAK` there (after `ALSO DEBUG`): it does not stop the program, but the result has `breaks`, the stacks and the running words at each `BREAK` (at most 100). - To find a fault: `~~` prints the place and the data stack when it runs, `BT` the running words, `ASSERT( code )` throws -2 with the place when the code leaves false, `WHERE name` says where a word was defined, and `FIND-NAME`, `LATEST`, `NAME>LINK` walk the names. - `steps` and `max_depth` in each result tell how many steps the call took and how deep the data stack went: a depth that grows with each pass of a loop shows a value left behind. - A line of code, and so a string in `S" "`, is at most 1024 characters. Give larger input, such as a JSON document, as the `data` argument, and read it with `DATA ( -- c-addr u )`, an `ALLOCATE`d copy (`FREE` it). It stays for later calls until another call sends data. - Files are in memory, never on the disk, at most 4194304 bytes in all, and stay until `forth_reset`. Give source files as the `files` argument of `forth_eval`, `{"square.fs": ": SQUARE DUP * ;"}`, then `INCLUDE square.fs`. The FILE words work on them too: `S" out.txt" W/O CREATE-FILE THROW VALUE OUT S" hi" OUT WRITE-LINE THROW OUT CLOSE-FILE THROW`, and a later call can read the file back. - Each call may take at most 10000000 steps, 5000 ms and 1048576 bytes of output; the session's definitions and the like at most 4194304 bytes in all. A limit reached throws -258 (detail `steps`, `time` or `output`); `CATCH` cannot go on after it. ## Forth in a few lines - Words are separated by spaces and are not case sensitive. Numbers go on the data stack; words take them off and put results back. `.` prints and drops the top; `.S` shows the stack without changing it. - `: NAME ... ;` defines a word. `IF ELSE THEN`, `DO LOOP`, `?DO`, `BEGIN UNTIL`, `BEGIN WHILE REPEAT`, `CASE OF ENDOF ENDCASE` work only inside a definition. - `10 0 DO I . LOOP` counts from 0 to 9: the limit first, then the start. Use `?DO` when the count may be 0. - Flags: true is -1, false is 0. `1 2 < .` prints -1. - Strings: `S" text"` gives `( c-addr u )`, and `TYPE` prints it. `." text"` prints, but only inside a definition; outside one use `.( text)`. `S\" a \"quote\"\n"` takes escapes. - Cells are 64 bits. `123.` (with a dot) is a double-cell number, not a float; a float is `1.5e0` or `3e0`, on the float stack, printed with `F.`. - `VARIABLE X 5 X ! X @ .` and `5 VALUE N 7 TO N N .` and `42 CONSTANT ANSWER`. - Errors: `THROW` with a code, `CATCH` to catch it: `' WORD CATCH` gives 0 or the code. `1 0 /` throws -10. - Comments: `\ to the end of the line` and `( inside parentheses )`. ## Words inspired by other Forths Other Forths have words that Forth-2012 lacks, and forthling has many of them, such as `+DO`, `-DO`, `FOR ... NEXT`, `BOUNDS`, `SCAN`, `STR=`, `S+`, `2NIP`, `THIRD`, `<=`, `UMAX`, `ROL`, `[IFDEF]`, `ALIAS`, `RANDOM` and `SLURP-FILE`. `limit start +DO ... LOOP` skips the loop when start is not below limit; `n FOR ... NEXT` runs n+1 times with `I` from n down to 0. `S+` and `APPEND` return allocated strings: `FREE` them. ## Use locals for more than two or three values Keeping track of the stack in your head goes wrong easily. Instead of `ROT`, `PICK`, `ROLL` and long chains of `SWAP` and `OVER`, name the inputs: ```forth : HYP2 {: a b -- c :} a a * b b * + ; 3 4 HYP2 . \ 25 : COUNT-UP {: n -- :} n 0 ?DO I . LOOP ; 3 COUNT-UP \ 0 1 2 ``` `{: a b | tmp -- out :}`: the names before `|` take their values from the stack (the last name gets the top), the names after it start at 0, and what follows `--` is only a comment. `x TO a` assigns. Locals work only inside a definition. ## Quotations: nameless words in place `[: ... ;]` makes a nameless word and leaves its xt, inside a definition or at the prompt. Use it where a word needs an xt and a named definition would be too much. ```forth : APPLY-TWICE ( x xt -- y ) DUP >R EXECUTE R> EXECUTE ; 3 [: 2 * ;] APPLY-TWICE . \ 12 ``` A quotation does not see the locals of the definition around it; pass values on the stack. ## JSON `ALSO JSON` makes the JSON words visible. A JSON value is its text, `( c-addr u )`; the reading words give back pieces of it. A missing key or a value of the wrong kind gives the flag 0, not an error. ```forth ALSO JSON : DOC S\" {\"users\": [{\"name\": \"Ada\", \"born\": 1815}]}" ; DOC S" users[0].born" JSON@ DROP JSON>NUMBER DROP . \ 1815 DOC S" users[0].name" JSON@ DROP JSON>STRING DROP TYPE \ Ada (ALLOCATEd) DOC S" users" JSON@ DROP JSON-LENGTH . \ 1 JSON{ S" sum" JSON-KEY 6 JSON-NUMBER S" ok" JSON-KEY -1 JSON-FLAG JSON} \ prints {"sum":6,"ok":true} \ ( c-addr u ) [1,2], ALLOCATEd ``` Reading: `JSON-VALID? ( json -- flag )`, `JSON-TYPE ( json -- n )` (0 null, 1 boolean, 2 number, 3 string, 4 array, 5 object, -1 no JSON), `JSON-GET ( json c-addr u -- json' flag )`, `JSON-NTH ( json n -- json' flag )`, `JSON-NAME ( json n -- c-addr u flag )`, `JSON-LENGTH ( json -- n )`, `JSON@ ( json c-addr u -- json' flag )` with a path such as `a.b[2]`, `JSON>NUMBER ( json -- n flag )`, `JSON>FLOAT ( json -- flag ) ( F: -- r )`, `JSON>FLAG ( json -- flag ok )`, `JSON>STRING ( json -- c-addr u flag )`. Writing goes to the output, or between `` to a string; the commas come by themselves: `JSON{ JSON} JSON[ JSON]`, `JSON-KEY ( c-addr u -- )`, `JSON-STRING ( c-addr u -- )`, `JSON-NUMBER ( n -- )`, `JSON-FLOAT ( F: r -- )`, `JSON-FLAG ( flag -- )`, `JSON-NULL`, `JSON-RAW ( json -- )`. ## Functional words `ALSO FUNCTIONAL` gives `MAP FOLD RUNNING-FOLD FILTER RANGE` on the data stack. The loop runs in Rust and the token is called once per item. The token's stack effect is checked after every call. A primitive token (`' +`, `' INVERT`, `' MAX`) is called directly, without the inner interpreter. A quotation of one primitive is the same token. ```forth ALSO FUNCTIONAL 1 6 RANGE [: DUP * ;] MAP 5 ' + FOLD . \ 55 (RANGE gives 1 2 3 4 5 5) 1 10 RANGE [: 2 MOD 0= ;] FILTER .S \ <5> 2 4 6 8 4 1 2 3 4 4 ' + RUNNING-FOLD .S \ <9> 2 4 6 8 4 1 3 6 10 ``` - `MAP ( x1 .. xn n xt -- y1 .. yn )`, `xt` is `( x -- y )`. - `FOLD ( x1 .. xn n xt -- r )`, `xt` is `( acc x -- acc' )`; `x1` is the first `acc`, so `n` must be at least 1. - `RUNNING-FOLD ( x1 .. xn n xt -- r1 .. rn )`: like `FOLD`, keeping every partial result. - `FILTER ( x1 .. xn n xt -- y1 .. ym m )`, `xt` is `( x -- flag )`. - `RANGE ( a b -- a .. b-1 n )`. - `ANY? ALL? ( x1 .. xn n xt -- flag )` and `FIND ( .. n xt -- x true | false )` stop at the answer; `COUNT-IF ( .. n xt -- m )`; `FLAT-MAP ( .. n xt -- y1 .. ym m )` with `xt` `( x -- y1 .. yk k )`; `ITERATE ( x n xt -- x1 .. xn )`; `REVERSE ( x1 .. xn n -- xn .. x1 )`; `TAKE` and `DROP-N ( x1 .. xm m n -- .. k )`; `ZIP ( a1 .. an b1 .. bn n -- a1 b1 .. an bn 2n )`; `SUM` and `PRODUCT ( x1 .. xn n -- r )` (0 and 1 for n = 0); `MIN-OF` and `MAX-OF`. `5 8 12 3 [: 6 > ;] FIND .S` shows `<2> 8 -1`. - `MAP-CELLS ( addr n xt -- )`, `FOLD-CELLS ( addr n xt -- r )`, `RUNNING-FOLD-CELLS ( addr n xt -- )` and `FILTER-CELLS ( addr n xt -- m )` are the same on a block of n cells in memory, for large amounts of data. `MAP-CELLS`, `RUNNING-FOLD-CELLS` and `FILTER-CELLS` change the block in place; `FILTER-CELLS` moves the cells that pass to the front and `m` is how many. `CREATE A 3 , 1 , 4 , 1 , 5 , A 5 ' MAX FOLD-CELLS .` prints 5. Also on a block, without changing it: `ANY?-CELLS ALL?-CELLS FIND-CELLS COUNT-IF-CELLS SUM-CELLS PRODUCT-CELLS`; `REVERSE-CELLS ( addr n -- )` works in place. ## Dynamic strings `ALSO DYNAMIC-STRINGS` gives the Gforth `$` words: strings that grow and shrink, and arrays of them. A string lives in one cell that holds 0 (empty) or the address of an `ALLOCATE`d block, so it uses the heap and counts towards the memory limit; `$FREE` gives the memory back. ```forth ALSO DYNAMIC-STRINGS $VARIABLE S S" Hello" S $! S" , world" S $+! S $@ TYPE \ Hello, world ``` - `$VARIABLE`, `$! ( c-addr u a -- )`, `$@ ( a -- c-addr u )`, `$+! ( c-addr u a -- )`, `$@LEN ( a -- u )`, `$!LEN ( u a -- )`, `$+!LEN ( u a -- c-addr )`, `$INS ( c-addr u a pos -- )`, `$DEL ( a pos u -- )`, `$INIT` and `$FREE ( a -- )`. - `$SPLIT ( c-addr u char -- c-addr u1 c-addr2 u2 )` and `$ITER ( .. a char xt -- .. )`, where `xt` is `( c-addr u -- )` for each part. - `$TMP ( xt -- c-addr u )` and `$EXEC ( xt a -- )` catch what `xt` prints; `$SLURP ( fid a -- )` and `$SLURP-FILE ( c-addr u a -- )` read a file. - Arrays: `$[]VARIABLE`, `$[]! ( c-addr u n a -- )`, `$[]@ ( n a -- c-addr u )`, `$[]+! ( c-addr u a -- )`, `$[]# ( a -- n )`, `$[]MAP ( a xt -- )`, `$[]SLURP`, `$[]SLURP-FILE` and `$[]FREE`. ## Operating system `ALSO OPERATING-SYSTEM` gives the Gforth words for the machine: `SYSTEM ( c-addr u -- )` and `SH` run a command and set `$?`, `OPEN-PIPE` and `CLOSE-PIPE` read the output of a command or write to its input, `GETENV` reads an environment variable, `ARGC ARGV ARG NEXT-ARG` give the command line, `OPEN-DIR READ-DIR CLOSE-DIR GET-DIR SET-DIR MKDIR-PARENTS` work on directories, `BASENAME DIRNAME FILENAME-MATCH` on file names, and `STDIN STDOUT STDERR` are file ids. The forthling program has them; the files it still has to include are its arguments, so a script takes the next one with `NEXT-ARG`. They are closed by default: they need `Permission::System`, which the sandbox, the MCP server and the playground deny (-259, or that ior). ```forth ALSO OPERATING-SYSTEM S" echo hello" R/O OPEN-PIPE THROW DUP >R PAD 80 ROT READ-LINE THROW DROP PAD SWAP TYPE CR \ hello R> CLOSE-PIPE THROW . \ 0, the exit status ``` ## Benchmarks `ALSO BENCHMARK` times your own code, with no permission needed (in the playground too, where a browser rounds the clock: time a word that runs for milliseconds): `BENCH ( xt -- microseconds )` runs a word once, `BENCH-BEST ( n xt -- microseconds )` gives the shortest of n runs. For example `: SQUARES 1000000 0 DO I DUP * DROP LOOP ; 5 ' SQUARES BENCH-BEST .`. An error in the word comes through. The files `benches/loop.fs`, `benches/fib.fs` and `benches/definitions.fs` time the interpreter itself. ## Tests `ALSO TESTING` checks your own code with the Forth-2012 test syntax: `T{ 3 SQUARE -> 9 }T` passes when the cells before `->` equal the cells after it. A wrong result prints a line with the place, the expected and the actual cells (`FAIL input:7 (squares): incorrect result: expected <1> 24, got <1> 25`) and the run goes on; `TESTING text` names a group and `TEST-SUMMARY` prints the counts. In the MCP server, `forth_eval` gives the counts and the failures as `tests` in the structured result. `forthling --test file.fs` runs a file and exits with 1 if a test failed. No permission is needed. An error inside a test is not caught. ## Tutorial `ALSO TUTORIAL` gives a Forth course of 20 lessons, ten for beginners and ten beyond, which an agent can walk a person through: `LESSONS` lists them, `n LESSON` shows lesson n (text, code to try, a task), `CHECK` tests the answer the person defined and says what is still wrong, `HINT` gives a hint and `NEXT` the next lesson. ## SQLite `ALSO SQLITE` gives SQLite words, for databases in memory only: `S" :memory:" DB-OPEN THROW VALUE DB`, then `DB-PREPARE`, `DB-STEP`, `DB-COLUMN` and the like, with errors as an `ior` in the style of the FILE words. ## Regular expressions `ALSO REGEX` gives regular expressions that match in time linear in the text, so no pattern can hang a program: `RE-MATCH?`, `RE-FIND`, `RE-GROUP`, `RE-COUNT` and `RE-REPLACE`. The text comes first, then the pattern, each as `( c-addr u )`: `S" order 66" S" [0-9]+" RE-FIND` gives `6 2 true` (the start and the length of the match). A bad pattern throws -262. ## HTTP `ALSO HTTP` gives an HTTP client where the host program allows it: `HTTP-GET ( url-addr url-u -- body-addr body-u status )`, `HTTP-POST ( url-addr url-u body-addr body-u type-addr type-u -- body-addr' body-u' status )`, `HTTP-HEADER` (a header for the next request) and `HTTP-RESPONSE-HEADER`. It is closed by default: it works only for the hosts the program was started with (`--allow-host`), never in the sandbox of `Forth::sandboxed()` or in the playground. The body is ALLOCATEd (FREE it); a status such as 404 is no error; a failed request throws -263, a host that is not allowed -259. ## Links - [README](https://github.com/DeSchaker/forthling/blob/main/README.md): features, the sandbox, standard compliance - [The MCP server](https://github.com/DeSchaker/forthling/blob/main/mcp/README.md): installing (`cargo install forthling-mcp`), tools, limits - [SQLite words](https://github.com/DeSchaker/forthling/blob/main/sqlite/README.md) - [Regular expression words](https://github.com/DeSchaker/forthling/blob/main/regex/README.md) - [HTTP words](https://github.com/DeSchaker/forthling/blob/main/http/README.md) - [Playground](https://deschaker.github.io/forthling/): Forth in the browser - [Forth-2012 standard](https://forth-standard.org/standard/words)