{"id":22295852,"url":"https://github.com/meithecatte/miniforth","last_synced_at":"2025-04-09T06:11:45.746Z","repository":{"id":43104887,"uuid":"369614034","full_name":"meithecatte/miniforth","owner":"meithecatte","description":"A bootsector FORTH","archived":false,"fork":false,"pushed_at":"2025-03-19T18:31:47.000Z","size":317,"stargazers_count":139,"open_issues_count":4,"forks_count":9,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-02T04:07:09.139Z","etag":null,"topics":["bootsector","bootstrappable","forth"],"latest_commit_sha":null,"homepage":"","language":"Forth","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/meithecatte.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"NieDzejkob"}},"created_at":"2021-05-21T17:52:39.000Z","updated_at":"2025-03-29T02:31:36.000Z","dependencies_parsed_at":"2024-12-03T17:44:22.880Z","dependency_job_id":"f243bb28-78aa-447d-8bfe-f85c1fc7f064","html_url":"https://github.com/meithecatte/miniforth","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meithecatte%2Fminiforth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meithecatte%2Fminiforth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meithecatte%2Fminiforth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meithecatte%2Fminiforth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meithecatte","download_url":"https://codeload.github.com/meithecatte/miniforth/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247987285,"owners_count":21028895,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bootsector","bootstrappable","forth"],"created_at":"2024-12-03T17:43:20.711Z","updated_at":"2025-04-09T06:11:45.725Z","avatar_url":"https://github.com/meithecatte.png","language":"Forth","readme":"# miniforth\n\n`miniforth` is a real mode [FORTH] that fits in an MBR boot sector.\nThe following standard words are available:\n\n```\n- ! @ c! c@ dup swap u. \u003er r\u003e : ; load\n```\n\nAdditionally, there are two non-standard words.\n - `|` switches between interpreting and compilation, performing the roles of\n   both `[` and `]`.\n - `s: ( buf -- buf+len )` will copy the rest of the current input buffer to\n   `buf`, and terminate it with a null byte. The address of said null byte will\n   be pushed onto the stack. This is designed for saving the code being ran to\n   later put it in a disk block, when no block editor is available yet.\n\nThe dictionary is case-sensitive. If a word is not found, it is converted into a number\nwith no error checking. For example, `g` results in the decimal 16, extending\nthe `0123456789abcdef` of hexadecimal. On boot, the number base is set to hexadecimal.\n\nBackspace works, but not how you're used to — the erased input will be still visible on\nscreen until you write something else.\n\n*Various aspects of this project's internals are described in detail [on my blog][blog].*\n\n## Trying it out\n\nYou can either build a disk image yourself (see below), or download one from\n[the releases page].\n\nWhen Miniforth boots, no prompt will be shown on the screen. However, if what\nyou're typing is being shown on the screen, it is working. You can:\n\n - do some arithmetic:\n   ```\n   7 5 - u.\n   : negate  0 swap - ;\n   : +  negate - ;\n   69 42 + u.\n   ```\n - load additional functionality from disk: `1 load`\n   (see [*Onwards from miniforth*](#onwards-from-miniforth) below).\n\n## Building a disk image\n\nYou will need `yasm` and `python3`, which you can obtain with `nix-shell` or\nyour package manager of choice. Then run `./build.sh`.\n\nThis will create the following artifacts:\n\n- `build/boot.bin` - the built bootsector.\n- `build/uefix.bin` - the chainloader (see below).\n- `miniforth.img` - a disk image with the contents of `block*.fth` installed into\n  the blocks.\n- `build/boot.lst` - a listing with the raw bytes of each instruction.\n   Note that the `dd 0xdeadbeef` are removed by `scripts/compress.py`.\n\nThe build will print the number of used bytes, as well as the number of block files found.\nYou can run the resulting disk image in QEMU with `./run.sh`, or pass `./run.sh build/boot.bin`\nif you do not want to include the code from `*.fth` in your disk. QEMU will run in curses\nmode, exit with \u003ckbd\u003eAlt\u003c/kbd\u003e + \u003ckbd\u003e2\u003c/kbd\u003e, \u003ckbd\u003eq\u003c/kbd\u003e, \u003ckbd\u003eEnter\u003c/kbd\u003e.\n\n## Blocks\n\n`load ( blk -- )` loads a 1K block of FORTH source code from disk and executes it.\nAll other block operations are deferred to user code. Thus, after appropriate setup,\none can get an arbitrarily feature-rich system by simply typing `1 load` —\nsee [*Onwards from miniforth*](#onwards-from-miniforth) below.\n\nEach pair of sectors on disk forms a single block. Block number 0 is partially used\nby the MBR, and is thus reserved.\n\n## System variables\n\nDue to space constraints, variables such as `STATE` or `BASE` couldn't be exposed by creating\nseparate words. Depending on the variable, the address is either hardcoded or pushed onto\nthe stack on boot:\n\n - `\u003eIN` is a word at `0x7d00`. It stores the pointer to the first unparsed character\n   of the null-terminated input buffer.\n - The stack on boot is `LATEST STATE BASE HERE #DISK` (with `#DISK` on top).\n - `STATE` has a non-standard format - it is a byte, where `0` means compiling,\n   and `1` means interpreting.\n - `#DISK` is not a variable, but the saved disk number of the boot media\n\n## `-DAUTOLOAD`\n\nFor some usecases, it might be desirable for the bootsector to load the first\nblock of Forth code automatically. You can achieve this by building `boot.bin`\nwith `-DAUTOLOAD`. Unfortunately, this requires 7 more bytes of code, making\nminiforth go over the threshold of 446 bytes, which makes it no longer possible\nto put an MBR partition table in the boot sector.\n\nThe partition table is required for:\n - the filesystem code in `blocks/filesystem.fth`\n - booting in the BIOS compatibility mode of most UEFI implementations, due to\n   a common bug/misfeature.\n\nTo work around this, `scripts/mkdisk.py` will use the small chainloader in\n`uefix.s` when it detects that miniforth is larger than 446 bytes.\nInstead of the default disk layout, which looks like this:\n\n```\nLBA 0   - boot.bin\nLBA 1   - unused\nLBA 2-3 - Forth block 1\n...       ...\n```\n\n...the disk image will looks as follows:\n\n```\nLBA 0   - uefix.bin\nLBA 1   - boot.bin\nLBA 2-3 - Forth block 1\n...       ...\n```\n\n## Onwards from miniforth\n\nThe main goal of the project is bootstrapping a full system on top of Miniforth\nas a seed. Thus the repository also contains various Forth code that may run on\ntop of Miniforth and extend its capabilities.\n\n - In `blocks/bootstrap.fth` (`1 load`):\n   - A simple assembler is implemented, and then used to implement additional\n     primitives, which wouldn't fit in Miniforth itself. This includes control\n     flow words like `IF`/`THEN` and `BEGIN`/`UNTIL`, as well as calls to the BIOS\n     disk interrupt to allow manipulating the code on disk.\n\n     For the syntax of the assembler, see [*No branches? No problem — a Forth\n     assembler*][branch-blog].\n\n   - Exception handling is implemented, with semantics a little different from\n     standard Forth. See [*Contextful exceptions with Forth\n     metaprogramming*][exception-blog].\n   - A separate, more featureful outer interpreter overrides the one built into\n     Miniforth, to correct the ugly backspace behavior and handle things\n     such as uncaught exceptions and vocabularies.\n - In `blocks/grep.fth` (`2f load`), a way of searching for occurences of a\n   particular string in the code stored in the blocks is provided:\n   - `10 20 grep create` searches blocks `$10` through `$20` inclusive for\n     occurences of `create`\n   - If your search term includes spaces, use `grep\"` — the syntax is similar\n     to `s\"` string literals: `10 20 grep\" : \u003enumber\"`\n - In `editor.fth` (`30 load`), a vi-like block editor is implemented. It can be started\n   with e.g. `10 edit` to edit block 10.\n   - Non-standard keybindings:\n     - \u003ckbd\u003eQ\u003c/kbd\u003e to quit back to the Forth REPL.\n     - \u003ckbd\u003e[\u003c/kbd\u003e to look at the previous block.\n     - \u003ckbd\u003e]\u003c/kbd\u003e to look at the next block.\n   - After first use, you can use the shorthand `ed` to reopen the last-edited block.\n   - Use `run` to execute the last-edited block. This sets a flag to prevent\n     a chain of `--\u003e` from loading all the subsequent blocks.\n   - Changes are saved to disk whenever you use `run` or open a different block with `edit`\n     or the \u003ckbd\u003e[\u003c/kbd\u003e/\u003ckbd\u003e]\u003c/kbd\u003e keybinds. You can also trigger this\n     manually with `save`.\n - In `filesystem.fth` (`50 load`), there's support for a simple filesystem,\n   which is currently hardcoded to be in the first partition listed in the MBR.\n   Some limits are lower than you might expect, but for the purposes I'm\n   interested in, they shouldn't become a problem:\n   - Partition size: up to 128 MiB\n   - File size: 8184 KiB\n\n   One file can be open at a time. Directories are supported, but there isn't any\n   path parsing. For user-level file manipulation:\n   - `ls ( -- )` will print the list of files in the current directory.\n   - `chdir ( name len -- )` will enter a subdirectory.\n   - `.. ( -- )` will go back to the parent directory.\n   - `mkdir ( name len -- )` will create a directory.\n   - `exec ( name len -- )` will execute the contents of a file as Forth.\n   - `rm ( name len -- )` will delete a file.\n   - `rmdir ( name len -- )` will delete an empty directory. Recursive delete\n     is not implemented yet.\n   For writing programs involving files:\n   - `fopen ( name len -- )` will open an existing file, or throw an exception\n     if it doesn't exist.\n   - `fopen? ( name len -- t|f )` will instead return a boolean indicating\n     whether the file could be found.\n   - `fcreate ( name len -- )` will create a new file, or if it already exists,\n     truncate it to 0 bytes. The new file is opened.\n   - `fread ( buf len -- )` will read data starting at the current position\n\nAll this code was originally developed within Miniforth itself, which meant it was\nstored within a disk image — a format that's not very friendly to tooling like\nGit or GitHub's web interface. This disparity is handled by two Python scripts:\n\n - `scripts/mkdisk.py` takes the files and merges them into a bootable disk image;\n - `scripts/splitdisk.py` extracts the code from a disk image's blocks and splits\n   it into files.\n\n## Free bytes\n\nAt this moment, not counting the `55 AA` signature at the end, **444** bytes are used,\nleaving 2 bytes for any potential improvements.\n\nByte saving leaderboard:\n - Ilya Kurdyukov saved 24 bytes. Thanks!\n - Peter Ferrie saved 5 bytes. Thanks!\n - [An article][daa] by Sean Conner allowed me to save 2 bytes. Thanks!\n\nIf a feature is strongly desirable, potential tradeoffs include:\n\n - 7 bytes: Don't push the addresses of variables kept by self-modifying code. This\n   essentially changes the API with each edit (NOTE: it's 7 bytes because this makes it\n   beneficial to keep `\u003eIN` in the literal field of an instruction).\n - ?? bytes: Instead of storing the names of the primitives, let the user pick their own\n   names on boot. This would take very little new code — the decompressor would simply have\n   to borrow some code from `:`. However, reboots would become somewhat bothersome.\n - ?? bytes: Instead of providing `;` in the kernel, give a dictionary entry to `EXIT` and\n   terminate definitions with `\\` |` until `immediate` and `;` can be defined.\n\n[FORTH]: https://en.wikipedia.org/wiki/Forth_(programming_language)\n[blog]: https://compilercrim.es/bootstrap/\n[branch-blog]: https://compilercrim.es/bootstrap/branches/\n[exception-blog]: https://compilercrim.es/bootstrap/exception-context/\n[the releases page]: https://github.com/meithecatte/miniforth/releases/\n[daa]: https://boston.conman.org/2023/02/24.1\n","funding_links":["https://github.com/sponsors/NieDzejkob"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeithecatte%2Fminiforth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeithecatte%2Fminiforth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeithecatte%2Fminiforth/lists"}