HackerReposA FIELD GUIDE TO OPEN SOURCE
ProSponsor the shelf♧ 0
← Back to top all-time
kragen
stoneknifeforth
Forth2009–2025
kragen / stoneknifeforth

stoneknifeforth

a tiny self-hosted Forth implementation

11Distinct people
26Mentions
2Explicit recommenders
444GitHub stars
REPOSITORY AT A GLANCE

What to know before exploring

What it is

a tiny self-hosted Forth implementation

GitHub

Where it is used

No documented product use was verified in the last source check.

What people are saying

i wasn't able to get a runnable forth to less than a couple of pages written in itself https://github.com/kragen/stoneknifeforth but schönfinkel's ski-combinators are maybe the…

kragen used a recommendation phrase. 2 indexed comments match. This is a phrase match, not a verdict.

No classified warning is attached to this page. Read the discussions before treating silence as approval.

Also in these discussions

Mentioned in the same Hacker News threads. That is not proof they are substitutes.

This week on Hacker News

Nov 7–Nov 14 UTC · Direct repository links and separately labeled related project stories. Indexed through 11/14/2026, 5:04:57 PM UTC.

No direct repository links were found in the indexed HN discussions over the past seven days. Older discussions are available below.

Mentions over time

HN mentions by year26 dated mentions

Observed mentions: 2009-07-10 11:31:46 UTC to 2025-10-07 14:09:58 UTC.

02462009-01-01 00:00:00 UTC to 2010-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20092010-01-01 00:00:00 UTC to 2011-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2011-01-01 00:00:00 UTC to 2012-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2012-01-01 00:00:00 UTC to 2013-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2013-01-01 00:00:00 UTC to 2014-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2014-01-01 00:00:00 UTC to 2015-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2015-01-01 00:00:00 UTC to 2016-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2016-01-01 00:00:00 UTC to 2017-01-01 00:00:00 UTC (exclusive): 1 mentions; 1 submissions, 0 comments2017-01-01 00:00:00 UTC to 2018-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments20172018-01-01 00:00:00 UTC to 2019-01-01 00:00:00 UTC (exclusive): 0 mentions; 0 submissions, 0 comments2019-01-01 00:00:00 UTC to 2020-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2020-01-01 00:00:00 UTC to 2021-01-01 00:00:00 UTC (exclusive): 2 mentions; 1 submissions, 1 comments2021-01-01 00:00:00 UTC to 2022-01-01 00:00:00 UTC (exclusive): 5 mentions; 1 submissions, 4 comments2022-01-01 00:00:00 UTC to 2023-01-01 00:00:00 UTC (exclusive): 6 mentions; 1 submissions, 5 comments2023-01-01 00:00:00 UTC to 2024-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2024-01-01 00:00:00 UTC to 2025-01-01 00:00:00 UTC (exclusive): 1 mentions; 0 submissions, 1 comments2025-01-01 00:00:00 UTC to 2026-01-01 00:00:00 UTC (exclusive): 2 mentions; 0 submissions, 2 comments2025

Counts come from the repository’s dated source evidence. Index cutoff: 2026-11-02 08:16:59 UTC.

View mention counts and date ranges
UTC ranges include their start and exclude their end. Select a period to filter the source evidence.
PeriodStart (UTC)End (exclusive, UTC)MentionsSubmissionsComments
20092009-01-01 00:00:00 UTC2010-01-01 00:00:00 UTC101
20102010-01-01 00:00:00 UTC2011-01-01 00:00:00 UTC101
20112011-01-01 00:00:00 UTC2012-01-01 00:00:00 UTC202
20122012-01-01 00:00:00 UTC2013-01-01 00:00:00 UTC000
20132013-01-01 00:00:00 UTC2014-01-01 00:00:00 UTC101
20142014-01-01 00:00:00 UTC2015-01-01 00:00:00 UTC000
20152015-01-01 00:00:00 UTC2016-01-01 00:00:00 UTC000
20162016-01-01 00:00:00 UTC2017-01-01 00:00:00 UTC110
20172017-01-01 00:00:00 UTC2018-01-01 00:00:00 UTC101
20182018-01-01 00:00:00 UTC2019-01-01 00:00:00 UTC000
20192019-01-01 00:00:00 UTC2020-01-01 00:00:00 UTC101
20202020-01-01 00:00:00 UTC2021-01-01 00:00:00 UTC211
20212021-01-01 00:00:00 UTC2022-01-01 00:00:00 UTC514
20222022-01-01 00:00:00 UTC2023-01-01 00:00:00 UTC615
20232023-01-01 00:00:00 UTC2024-01-01 00:00:00 UTC202
20242024-01-01 00:00:00 UTC2025-01-01 00:00:00 UTC101
20252025-01-01 00:00:00 UTC2026-01-01 00:00:00 UTC202

What Hacker News says

26 sources
Having written a Forth-like native-code compiler (https://github.com/kragen/stoneknifeforth) and also a game in Gforth, I can't claim to be a Forth expert, but I agree. A lot of the weaknesses of the language (such as no compile-time checking of even the number of arguments to a subroutine, much less their types, and its terseness) are shored up by the interactivity of the environment. Some of the short words in Forth like ? only make sense in that context. And some things that might otherwise be weaknesses, like variables being static, become strengths in that context. (If you store into x in a word you're testing, you can see what got stored there with x ?.) More generally, Forth, considered as a programming language, is not very good compared to other languages of a similar level, such as C or assembly. Its strength is as an inter
Read the rest
active environment. (And none of what I've said here implies that you have to use a block editor.) This is all fairly abstract, but it's not the first time I've talked about it, so I did an ASCIIcast last year demonstrating me writing a square root subroutine: https://asciinema.org/a/621404 I screwed it up in the middle and had to debug it, which is where Forth's interactivity shines. You may want to watch it on double speed, though. You can also get the pleasure of watching me learn that Gforth has readline-like command-line history! That said, I think you're exaggerating a bit. I haven't seen a Forth compiler that's only a few hundred bytes, and I don't think the blocking PIO in Chuck's IDE driver is a good way to access an IDE disk, although it's adequate for initial bootstrapping.
In Compiling a Forth
This is pretty impressive! I don't think it takes billions of dollars in development to write a C compiler, but it does seem like everyone's first C compiler does take at least a year, so US$100k is a good ballpark. I agree that Forth is about an order of magnitude easier. You probably shouldn't consider https://github.com/kragen/stoneknifeforth to actually be a Forth (it's not interactive and doesn't have immediate words) but it did compile itself into a working ELF executable and it did take me almost exactly a month to write (October 02008, specifically). I could probably write a real Forth now in a month. I would be very excited to read your small Forth novella. Or your small Haskell novella.
In Easy Forth (2015)
In Konilo: A personal computing system in Forth
a problem that a lot of these series run into is that the author runs out of steam before they finish writing them. crenshaw's otherwise excellent series suffers from this, for example so far the author of this one has only written the first chapter i've written a few didactic compilers that are complete enough to compile themselves, though not complete enough to compile anything else; these may be of more interest to someone who's looking for how to make writing a compiler surprisingly easy, though they do lack the extensive explanatory text in the linked blog post - http://canonical.org/~kragen/sw/urscheme (from a subset of scheme to at&t-syntax i386 assembly, 1553 lines of code) - https://github.com/kragen/stoneknifeforth (from a forth-like language to an i386 linux elf executable, 132 lines of code) - https://github.com/kragen/peg-bo
Read the rest
otstrap/blob/master/peg.md (from a peg grammar with semantic actions to javascript, 66 lines of code) - https://github.com/kragen/peg-bootstrap/blob/master/handaxew... (tangles a literate program in virtually any plain-text format, such as markdown, html, restructuredtext, or tex, into virtually any plain-text programming language, 208 lines of code; used to tangle both itself and the peg compiler compiler above) - http://canonical.org/~kragen/sw/dev3/meta5ix.m5 (from meta5ix, a language for writing parsers derived from val schorre's meta-ii, to meta5ix assembly, 18 lines of code; it is a top-down parsing language with limited backtracking, like pegs, but less expressive and implementable without heap allocation or the ability to backtrack past the beginning of a line; see http://canonical.org/~kragen/sw/dev3/meta5ixrun.py for an m5asm interpreter) - http://canonical.org/~kragen/sw/dev3/meta5ix2c2.m5 (from a slightly different variation of meta5ix to c, 133 lines of code) most of these do take the same ast-free approach as the linked blog post and crenshaw, just emitting code as they finish parsing a production (or, in the case of stoneknifeforth, a token), but ur-scheme in particular does not; it uses scheme s-expressions as its syntax tree, because some kind of syntax tree is necessary to be able to allocate some local variables on the stack while also supporting closures. peg-bootstrap can certainly handle code that builds an ast with its semantic actions, but its implementation of itself just glues together snippets of javascript text also worth mentioning in this connection: - darius bacon's work, especially parson (for example, https://github.com/darius/parson/blob/master/eg_calc_compile...) - the oberon compiler in wirth and gutknecht's oberon book - the later chapters of sicp
In Writing a Compiler is Surprisingly Easy (part 1)
kragen·Nov 9, 2022Recommendation phrase
i wasn't able to get a runnable forth to less than a couple of pages written in itself https://github.com/kragen/stoneknifeforth but schönfinkel's ski-combinators are maybe the simplest practical basis s f g x → f x (g x) k a b → a i = s k k (one of many possible definitions) maybe wolfram knows of a simpler alternative my favorite is abadi and cardelli's object-calculus on which i based bicicleta. it has two reduction rules rather than the λ-calculus's one. using a hybrid of bicicleta's syntax and abadi and cardelli's {f = ς(v)b, g = ς(v)c, ...}{f = ς(v)d} → {f = ς(v)d, g = ς(v)c, ...} {f = ς(v)b, g = ς(v)c, ...}.f → b[{f = ς(v)b, g = ς(v)c, ...}/v] the first of these derives an inherited object with a new definition for method f. the second one invokes method f on an object, which is evaluated by repla
Read the rest
cing its self-variable v with the object itself throughout its body, using the standard β-reduction semantics with α-renaming that we're familiar with from the λ-calculus (b[x/y] means b but with x replacing y) despite the simplicity of the semantics the ς-calculus is enormously more usable for actually writing down functions than the λ-calculus. here's factorial(10) in the notation above (untested) {fac = ς(env){ n = ς(_)3, return = ς(o) (o.n < 2).if_true{ then = ς(_)1 else = ς(_)o.n * env.fac{n = ς(_)o.n - 1}.return }.return } }.fac{n = ς(10)}.return bicicleta has a lot of syntactic sugar which reduces this to (tested) {env: fac = {fac: arg1 = 3 '()' = (fac.arg1 < 2).if_true( then = 1 else = fac.arg1 * env.fac(fac.arg1 - 1) ) }}.fac(10) in particular to be able to define infix operators inside the language, bicicleta rewrites x * y as x.'*'{arg1 = y}.'()' and analogously for -, <, etc. they published a bunch of papers and a book on this but they were more interested in static typing than anything else. a paper on one imperative variation of the thing is http://lucacardelli.name/Papers/PrimObjImpSIPL.A4.pdf you can implement a turing machine in a few lines of c, making it internally simpler than the other alternatives, but as you point out it's unusable except as a compilation target or to prove theorems about
In Lisp as the Maxwell’s Equations of Software (2012)
Well, Yossi is probably a better programmer than I am, but I think I'm probably better at Forth than he was, and I think he was Doing It Wrong. And he sort of admits this: he was doing big-bang compilation rather than poking interactively at the hardware, he left out all the metaprogramming stuff, and he was trying to use the stack for local variables because he designed the hardware with a two-cycle memory fetch and no registers for local variables: : mean_std ( sum2 sum inv_len -- mean std ) \ precise_mean = sum * inv_len; tuck u* \ sum2 inv_len precise_mean \ mean = precise_mean >> FRAC; dup FRAC rshift -rot3 \ mean sum2 inv_len precise_mean \ var = (((unsigned long long)sum2 * inv_len) >> FRAC) - (precise_mean * precise_mean >> (FRAC*2)); dup um* nip FRAC 2 * 32 - rshift -rot \ mean precise_mea
Read the rest
n^2 sum2 inv_len um* 32 FRAC - lshift swap FRAC rshift or \ mean precise_mean^2 sum*inv_len swap - isqrt \ mean std ; I've done all these things (except designing the hardware) and I agree that it can be very painful. I did some of them in 02008, for example: https://github.com/kragen/stoneknifeforth The thing is, though, you can also not do all those things. You can use variables, and they don't even have to be allocated on a stack (unless you're writing a recursive function, which you usually aren't), and all the NIP TUCK ROT goes away, and with it all the Memory Championship tricks. You can test each definition interactively as you write it, and then the fact that the language is absurdly error-prone hardly matters. You can use metaprogramming so that your code is as DRY as a nun's pochola. You can use the interactivity of Forth to quickly validate your hypotheses about not just your code but also the hardware in a way you can't do with C. You can do it with GDB, but Forth is a lot faster than GDBscript, but that's not saying much because even Bash is a lot faster than GDBscript. But Yossi was just using Forth as a programming language, like a C without local variables or type checking, not an embedded operating system. And, as I said, that's really not Forth's strength. Bash and Tcl aren't good programming languages, either. If you try to use Tcl as a substitute for C you will also be very sad. But the way they're used, that isn't that important. I explained a more limited version of this 12 years ago: https://yosefk.com/blog/my-history-with-forth-stack-machines... So, I don't think Forth is only useful when you have the freedom to change the problem, though programs in any language do become an awful lot easier when you have that freedom.
In Stop Writing Dead Programs [video]
>"This is StoneKnifeForth, a very simple language inspired by Forth. It is not expected to be useful; instead, its purpose is to show how simple a compiler can be. The compiler is a bit under two pages of code when the comments are removed. This package includes a “metacircular compiler” which is written in StoneKnifeForth and compiles StoneKnifeForth to an x86 Linux ELF executable." [...] "The output executable is 4063 bytes, containing about 1400 instructions." My thoughts: That would just about fit on a VIC-20... https://github.com/kragen/stoneknifeforth/blob/master/tinybo...
In StoneKnifeForth
I want to preface this comment with the fact that I'm terrible at teaching and know nothing about it, so it's entirely possible that your example compiler is much better for pedagogical purposes than the ones I'm going to suggest here. But I think they all have a significantly higher "power density", which at least from my point of view makes them more interesting and inspiring. I think mostly people are complaining that the language it compiles isn't a programming language, so it omits a lot of things that compilers for programming languages have to handle. And so it feels a bit like much ado about nothing: because there are no variables, loops, or values beyond single-bit values, the program it compiles can only ever produce a constant, predetermined output, and that output can only ever be 1 or 0. So the compiler is just sort of a p
Read the rest
articularly awkward and inefficient way to output that 1 or 0. A compiler for Brainf*** or SK-combinators or the untyped λ-calculus would be "more real" in that sense. More practical, and maybe also fitting into 150 lines, might be a language for something like a programmable calculator (untested sketch): program ::= stmt | stmt ";" program. stmt ::= "print" numexpr | variable ":=" numexpr | "while" numexpr comparator numexpr "{" program "}". comparator ::= "<" | ">" | "==" | "!=" | "<=" | ">=". numexpr ::= term | numexpr "+" term | numexpr "-" term. term ::= factor | term "*" factor | term "/" factor | term "%" factor. factor ::= atom | "-" atom | "+" atom | atom "^" factor. atom ::= "(" numexpr ")" | numeric_constant | variable. That's powerful enough to conveniently write, for example, a numerical root finding program for an arbitrary arithmetic expression. Maybe for pedagogical purposes (which, as I said, I am totally ignorant of) it would be useful to leave out one of the levels of precedence and some of the operators and have the students add them as an exercise. But I think that within a complexity budget of 150 lines of code you can maybe be even more ambitious than that. Darius Bacon's example compiler in https://github.com/darius/parson/blob/master/eg_calc_compile... is a bit more stripped down than that, but in its 32 lines of code it compiles arithmetic assignment statements to a three-address RISC-like code (though using an unbounded number of registers). https://github.com/darius/parson/blob/master/eg_calc_to_rpn.... is a 16-line version that compiles the same language to a stack machine like your tutorial example: from parson import Grammar, alter g = Grammar(r""" stmt* :end. stmt : ident '=' exp0 ';' :assign. exp0 : exp1 ('+' exp1 :'add')*. exp1 : exp2 ('*' exp2 :'mul')*. exp2 : '(' exp0 ')' | /(\d+)/ | ident :'fetch'. ident : /([A-Za-z]+)/. FNORD ~: /\s*/. """)(assign=alter(lambda name, *rpn: rpn + (name, 'store'))) ## print ' '.join(g('v = 42 * (5+3) + 2*2; v = v + 1;')) #. 42 5 3 add mul 2 2 mul add v store v fetch 1 add v store Of course that still contains no control flow, so it's still not a very convincing example of why you'd take the extra trouble to write a compiler rather than an interpreter. In 66 lines of code in https://github.com/kragen/peg-bootstrap/blob/master/peg.md I wrote an example compiler which compiles a PEG grammar into a JavaScript parser for that grammar. Admittedly those 66 lines do not include an implementation of JavaScript to run the code on. It compiles the language it's written in. In 132 lines of code in https://github.com/kragen/stoneknifeforth/blob/master/tinybo... I wrote an example compiler which compiles a crippled Forth dialect into i386 machine code, including an ELF header so you can run the result. It also compiles the language it's written in. It also doesn't include an i386 emulator to run it on. In 83 lines of code in http://canonical.org/~kragen/sw/dev3/neelcompiler.ml Neel Krishnaswami wrote a compiler from the untyped λ-calculus to a simple assembly language for a register machine. It also doesn't include an implementation of the assembly language. In 18 lines of code in http://canonical.org/~kragen/sw/dev3/meta5ix.m5, a simplification of Val Schorre's META-II, I wrote a compiler from grammar descriptions to an assembly code for a parsing-oriented virtual machine. It compiles the language it's written in. A Python interpreter for the machine is in http://canonical.org/~kragen/sw/dev3/meta5ixrun.py (109 lines of code) and a precompiled version of the compiler-compiler for bootstrapping is in http://canonical.org/~kragen/sw/dev3/meta5ix.generated.m5asm. It does recursive descent with only a single token of backtracking; a minimal summary of the most eccentric aspects of its syntax is that "" encloses text to expect on input, {} encloses text to output, <<>> encloses a token to parse from the input that might be copied to the output with $it (or unindented with @it), commas separate alternatives, and [x] is zero or more repetitions of x. - program: ["-" name @it ":" terms {return}, "#" [:notin ""]] - terms: term ["," {continue $choice} term] @choice - term: (factor {else $seq}, output) [factor {assert}, output] @seq - factor: string {literal $it} , "(" terms ")" , "[" @many terms {continue $many} "]" , name {call $it} , "<<" {begin} terms ">>" {end} , ":fnord" {fnord} factor , ":notin" string {notin $it} , ":between" string {between $it} - output: (quasiquote, "@" {dedent} (var, quasiquote)) {writeline} - quasiquote: "{" [(<<ch [ch]>>, "\" <<:notin "">>) {say "$it"}, "$" var] "}" - ch: :notin "$}\" - var: "it" {copyinput}, name {gen $it} - string: :fnord <<'"' [:notin '"'] '"', "'" [:notin "'"] "'">> - name: :fnord <<letter [letter, :between "09"]>> - letter: :between "az", :between "AZ", :between "__" A slightly incompatible variant of Meta5ix which instead compiles itself to C is in http://canonical.org/~kragen/sw/dev3/meta5ix2c.m5 (133 lines of code, depending on how you count). (No C compiler is included.) The precompiled C output for bootstrapping is in http://canonical.org/~kragen/sw/dev3/meta5ix2c.c. Meta5ix is extremely weak and limited, really only enough for a compiler front-end. Dave Long described META-II as "a field-improvised lever", which I think is still apt for Meta5ix. Now, again, I am totally ignorant about pedagogy, so I could be wrong about this, but I think that probably self-compiling compiler-compilers like most of my examples here are not the best tutorial examples, because they refer to themselves in complex and confusing ways. But I feel like they represent pretty good evidence that you can do something a lot more interesting in a 150-line-of-code compiler than evaluating a Boolean circuit.
In Show HN: A compiler and VM for a simple language, in 150 lines of code
Read the rest
84231, https://news.ycombinator.com/item?id=26674832, https://news.ycombinator.com/item?id=26672806, https://news.ycombinator.com/item?id=26671656, https://news.ycombinator.com/item?id=26654767, https://news.ycombinator.com/item?id=26596892, https://news.ycombinator.com/item?id=26587768, https://news.ycombinator.com/item?id=26581742, https://news.ycombinator.com/item?id=26561319, https://news.ycombinator.com/item?id=26547871, https://news.ycombinator.com/item?id=26543937, https://news.ycombinator.com/item?id=26580684, https://news.ycombinator.com/item?id=26528534, https://news.ycombinator.com/item?id=26525837, https://news.ycombinator.com/item?id=26525109, http://canonical.org/~kragen/sw/dev3/qvaders, https://news.ycombinator.com/item?id=26452393, http://canonical.org/~kragen/sw/dev3/skitch, https://news.ycombinator.com/item?id=26449902, http://canonical.org/~kragen/sw/dev3/meta5ixrun.py, https://news.ycombinator.com/item?id=26438596, http://canonical.org/~kragen/sw/dev3/mukanren.ml, https://news.ycombinator.com/item?id=26418271, http://canonical.org/~kragen/sw/dev3/kmregion.h, https://news.ycombinator.com/item?id=26299172, http://canonical.org/~kragen/sw/dev3/readprint.fs, https://news.ycombinator.com/item?id=26289195, https://news.ycombinator.com/item?id=26219950, https://news.ycombinator.com/item?id=26219000, https://news.ycombinator.com/item?id=26198567, https://news.ycombinator.com/item?id=26231940, https://news.ycombinator.com/item?id=26189525, https://news.ycombinator.com/item?id=26195060. I'm not going to link to particular comments in https://news.ycombinator.com/threads?id=giardini (it would be too easy to think I was cherry-picking the worst ones) but you can read through it yourself and see that the guy has a long history of almost nothing but incessant worthless trolling. If you can't tell the difference...
In TSMC looks to double down on U.S. chip factories as talks in Europe falter
I'm pretty sure Linux ELF has always allowed you to specify the initial load address. When I first wrote StoneKnifeForth https://github.com/kragen/stoneknifeforth its load address was 0x1000, but at some point Linux stopped allowing load addresses lower than 0x10000 by default (vm.mmap_min_addr). I originally wrote it in 02008, using the lower load address, and fixed it in 02017. It's still not using 0x804800 like normal executables but 0x20000. ASLR does not affect this. Maybe you mean that before ELF support, Linux a.out executables had to be loaded at a fixed virtual address? That's possible—I started using Linux daily in 01995, at which point a.out was already only supported for backward compatibility.
In An HTTP server in a single .c file

Showing the 15 most recent of 26 sources. Enable JavaScript to search and browse every indexed mention.

MAKE YOURSELF AT HOME

A few shortcuts

/
Search the collection
?
Open this guide
Esc
Close dialogs and navigation

Your saved repositories and interests stay in this browser.