Output & Running It
Hello, World
A Forth program is a stream of words separated by spaces, and the interpreter runs each one as it reads it.
: starts defining a new word — here named GREET — and ; ends the definition. Inside it, ." prints the text up to the next " (the space after ." is required, because ." is itself a word), and CR ends the line. The last line simply names the word, which runs it.def greet():
print("Hello, World!")
greet(): GREET ." Hello, World!" CR ;
GREETThere is no
print that adds a newline for you: CR ("carriage return") is a separate word, and forgetting it leaves the cursor at the end of the line. The Python version needed a function only to keep the two columns parallel; in Forth, defining a word is how almost everything is written.Printing a Number
A number on its own does not print anything: it is pushed onto the data stack. The word
. ("dot") takes the number on top of the stack and prints it, followed by a space. So 42 . means "push 42, then print what is on top".print(42)
print(-7)
print(3 + 4)42 . CR
-7 . CR
3 4 + . CR. always prints a trailing space, which is why numbers printed in a row come out separated without any formatting code. 3 4 + . is the first taste of postfix order: the operands come first and the operation after them.Comments
Forth has two comment words.
\ skips the rest of the line, and ( skips everything up to the next ). Both are ordinary words, so each needs a space after it: (comment) without the space would be read as an unknown word named (comment).# A comment runs to the end of the line
width = 6 # so does this one
print(width * 7)\ A backslash comment runs to the end of the line
6 7 * . ( a parenthesized comment ends at the closing paren )
CRParenthesized comments are used constantly in real Forth, most of all for stack-effect comments such as
( n -- n ), which document what a word takes and leaves. They play the part that a signature and type hints play in Python.Characters and Spacing
EMIT prints the character whose code is on the stack — 42 is * and 65 is A — and SPACES prints that many spaces. STARS uses a counted loop (DO … LOOP, covered below) to emit a star that many times.print("*" * 5)
print(" " * 3 + "indented")
print(chr(65)): STARS ( count -- ) 0 DO 42 EMIT LOOP ;
: BANNER 5 STARS CR 3 SPACES ." indented" CR 65 EMIT CR ;
BANNERPython builds a string and prints it; Forth usually prints as it goes, one character or one number at a time. Since there is no string object to multiply, repetition is a loop.
Printing Text Outside a Definition
." is meant for use inside definitions. To print text immediately, from the top level of a program, the standard word is .(, which prints everything up to the closing ).print("Loading...")
print(2 * 21).( Loading...) CR
2 21 * . CRMany Forths accept
." at the top level too, but the standard leaves that undefined and some reject it. .( works everywhere, which is why it is the portable choice for a message printed while a file is loading.The Stack Instead Of Variables
Postfix Arithmetic
Every Forth operator works on the stack:
+ removes the top two numbers and pushes their sum. Reading 2 3 + 4 * left to right: push 2, push 3, add them (leaving 5), push 4, multiply (leaving 20). The order the words are written is the order of evaluation, so there are no parentheses and no precedence rules.print((2 + 3) * 4)
print(2 + 3 * 4)2 3 + 4 * . CR
2 3 4 * + . CRThis notation is called postfix, or Reverse Polish Notation, and HP calculators used it for decades. The second line is the same arithmetic with the multiplication written first — which is all "precedence" ever meant.
DUP, DROP, SWAP and OVER
Because values have no names, a Forth program reorganizes the stack instead. The four essential words are
DUP (copy the top item), DROP (discard it), SWAP (exchange the top two) and OVER (copy the second item onto the top). In 10 3 OVER - +, OVER makes the stack 10 3 10, - leaves 10 −7, and + leaves 3.number = 7
print(number * number) # DUP uses the value twice
first, second = 10, 3
print(second - first) # SWAP reverses the operands
first, second = 10, 3
print(first + (second - first)) # OVER reuses the first value
values = [1, 2]
values.pop()
print(values[-1]) # DROP discards the top value7 DUP * . CR \ DUP copies the top item
10 3 SWAP - . CR \ SWAP exchanges the top two
10 3 OVER - + . CR \ OVER copies the second item to the top
1 2 DROP . CR \ DROP throws the top item awayWhere Python would write a variable name a second time, Forth writes
DUP. Learning to see a calculation as a sequence of stack shapes is the single biggest adjustment a Python programmer has to make.Looking at the Stack
.S prints everything on the stack, bottom first, without removing any of it, and DEPTH pushes how many items there were. Both exist for exactly the moment when a word leaves the stack in a shape you did not expect.stack = []
stack.append(1)
stack.append(2)
stack.append(3)
print(*stack)
print(len(stack))1 2 3
.S CR
DEPTH . CRA Python list used as a stack is a fair model:
append is a push and pop is what every Forth word does to its arguments. DEPTH reports 3, not 4, because it counts the stack before pushing its own answer. The exact format of .S is not standardized, and some Forths prefix it with the depth, as in <3> 1 2 3.ROT, NIP and TUCK
Three more shufflers cover most of what remains.
ROT rotates the third item up to the top (1 2 3 becomes 2 3 1). NIP removes the item under the top. TUCK slips a copy of the top item underneath the second (1 2 becomes 2 1 2). SHOW3 prints three items in their stack order, bottom first.first, second, third = 1, 2, 3
first, second, third = second, third, first # ROT
print(first, second, third)
first, second = 1, 2
print(second) # NIP
first, second = 1, 2
print(second, first, second) # TUCK: SHOW3 ( a b c -- ) ROT . SWAP . . CR ;
1 2 3 ROT SHOW3 \ ROT brings the third item to the top
1 2 NIP . CR \ NIP drops the second item
1 2 TUCK SHOW3 \ TUCK copies the top under the secondA stack-effect comment such as
( a b c -- ) names the items only for the reader — the names do not exist in the program. When a word needs ROT more than once or twice, Forth programmers take it as a sign that the word is doing too much and should be split.Turning an Expression Into Stack Operations
Both inputs are needed twice, so
2DUP copies the top pair: 7 3 becomes 7 3 7 3. + turns the top pair into 10, leaving 7 3 10. Two ROTs move the 10 underneath (10 7 3), - leaves 10 4, and * leaves 40.def difference_of_squares(first, second):
return (first + second) * (first - second)
print(difference_of_squares(7, 3)): DIFFERENCE-OF-SQUARES ( first second -- result )
2DUP + ROT ROT - * ;
7 3 DIFFERENCE-OF-SQUARES . CRThe Python function names its parameters and uses each name twice; the Forth word duplicates the values and routes them into place. Word names may contain hyphens, and any other character except a space, so a name like
DIFFERENCE-OF-SQUARES needs no underscores.Words Instead Of Functions
Defining a Word
A word takes its arguments from the stack and leaves its results there, so there is no parameter list and no
return. SQUARE duplicates the number and multiplies. CUBE uses SQUARE exactly the way it uses a built-in word — there is no difference between the two.def square(number):
return number * number
def cube(number):
return number * square(number)
print(square(9))
print(cube(3)): SQUARE ( n -- n*n ) DUP * ;
: CUBE ( n -- n*n*n ) DUP SQUARE * ;
9 SQUARE . CR
3 CUBE . CRThe stack-effect comment
( n -- n*n ) is the documentation: inputs to the left of --, outputs to the right. Nothing checks it, so a word that leaves the wrong number of items fails later, somewhere else, which is why Forth style favors very short words that are easy to verify by eye.Returning More Than One Value
A word may leave any number of results, because returning is simply leaving values on the stack.
/MOD is a built-in that leaves the remainder and then the quotient; DIVIDE swaps them so the quotient is underneath. The final SWAP . . prints the quotient first to match Python's order.def divide(dividend, divisor):
return dividend // divisor, dividend % divisor
quotient, remainder = divide(17, 5)
print(quotient, remainder): DIVIDE ( dividend divisor -- quotient remainder )
/MOD SWAP ;
17 5 DIVIDE SWAP . . CRPython packs two results into a tuple and unpacks them; Forth never builds the tuple at all. The price is that the caller has to know the order, which is exactly what the stack-effect comment is for.
Returning Early
EXIT leaves the current word immediately, like a bare return. 0< replaces the number with a true or false flag, and IF … THEN runs its body only when the flag is true.def describe(number):
if number < 0:
print("negative")
return
print("zero or more")
describe(-4)
describe(8): DESCRIBE ( n -- )
0< IF ." negative" CR EXIT THEN
." zero or more" CR ;
-4 DESCRIBE
8 DESCRIBETHEN marks the end of the IF, not the start of its body — it reads oddly at first, like "if so, do this, then carry on". Inside a DO loop, EXIT must be preceded by UNLOOP, because the loop keeps its counter on the return stack that EXIT uses to find its way back.Recursion
While a word is being defined, its own name is not yet visible, so a word calls itself with
RECURSE. 1- subtracts one. When the number is 1 or less, ELSE drops it and leaves 1 instead.def factorial(number):
if number <= 1:
return 1
return number * factorial(number - 1)
print(factorial(10)): FACTORIAL ( n -- n! )
DUP 1 > IF DUP 1- RECURSE * ELSE DROP 1 THEN ;
10 FACTORIAL . CRThe name being invisible is deliberate. It is what lets a new definition call an older word of the same name — a new
DUP can be defined in terms of the original DUP — which Python's late-binding lookup could never do.Redefining a Word
Defining a word a second time does not replace the first definition — it adds a newer one that hides it. Words compiled earlier keep calling the definition that existed when they were compiled.
def greeting():
return "hello"
def welcome():
print(greeting())
def greeting():
return "howdy"
welcome()
print(greeting()): GREETING ." hello" ;
: WELCOME GREETING CR ;
: GREETING ." howdy" ;
WELCOME
GREETING CRPython looks
greeting up by name every time welcome runs, so it sees the new function and prints howdy twice. Forth resolved GREETING once, when WELCOME was compiled, so WELCOME still says hello while a fresh call prints howdy. The dictionary is an append-only history, not a table of current bindings.Integer Arithmetic
The Arithmetic Words
The usual operators are words that take two numbers and leave one. The one to watch is
-: 17 5 - is 17 minus 5, because the item underneath is always the left-hand operand. / is integer division and MOD the remainder. NEGATE flips the sign, since a - in front of a word would just be part of its name.print(17 + 5, 17 - 5, 17 * 5)
print(17 // 5, 17 % 5)
print(abs(-9), min(3, 8), max(3, 8), -(12))17 5 + . 17 5 - . 17 5 * . CR
17 5 / . 17 5 MOD . CR
-9 ABS . 3 8 MIN . 3 8 MAX . 12 NEGATE . CRThere is no floating point in core Forth.
/ always produces an integer, the way // does in Python — a Python programmer who reaches for / expecting 3.4 gets 3.Which Way Division Rounds
Python's
// rounds toward negative infinity, so -7 // 2 is −4. Plain / in Forth is allowed to round either way — the standard leaves the choice to each implementation — so a Forth program that cares names the rule. FM/MOD is floored division and SM/REM is symmetric (truncating toward zero). Both divide a double-width number, which S>D makes from an ordinary one, and both leave the remainder under the quotient.import math
print(-7 // 2, -7 % 2)
print(int(-7 / 2), int(math.fmod(-7, 2))): FLOORED ( n d -- ) >R S>D R> FM/MOD . . CR ;
: TRUNCATED ( n d -- ) >R S>D R> SM/REM . . CR ;
-7 2 FLOORED
-7 2 TRUNCATED>R and R> park the divisor on the return stack for a moment while S>D works on the number beneath it. The floored line prints -4 1, matching Python's // and %; the truncated line prints -3 -1, matching int(-7 / 2) and math.fmod, which is how C divides.Numbers Have a Fixed Size
Every value on the Forth stack is a cell: a fixed-size integer, the natural word size of the machine. Arithmetic past the end of a cell wraps around silently.
LSHIFT shifts bits left, so 1 31 LSHIFT 1- is the largest positive 32-bit number.largest = 2**31 - 1
print(largest)
print(largest + 1)
print(2**100): LARGEST ( -- n ) 1 31 LSHIFT 1- ;
LARGEST . CR
LARGEST 1+ . CRPython integers grow as large as memory allows, so
2**100 is simply a big number. Forth has no such thing: in a 32-bit Forth, one more than the largest positive number is the most negative one, as shown here, and on a 64-bit Forth the same program wraps at a much larger value. Which one you get is a property of the system, not the language.Fractions Without Floating Point
Forth's answer to fractions is to work in whole units — cents instead of dollars — and scale by ratios.
*/ multiplies by the second number and divides by the third, and it keeps the intermediate product in double width so the multiplication cannot overflow before the division brings it back down.price = 19.99
tax_rate = 0.0825
print(round(price * tax_rate, 2))
price_in_cents = 1999
print(price_in_cents * 825 // 10000): TAX ( cents -- cents ) 825 10000 */ ;
1999 TAX . CRCharles Moore, who created Forth, argued that most programs using floating point only needed a ratio. The second Python line does the same integer arithmetic and prints the same 164; the first shows why money in binary floating point needs
round at all.Number Bases
Forth has a single current number base, stored in the variable
BASE, and it governs both reading numbers in the source and printing them with .. HEX and DECIMAL set it to 16 and 10; 2 BASE ! stores any other base directly.print(hex(255))
print(int("ff", 16))
print(bin(10))255 HEX . DECIMAL CR
HEX FF DECIMAL . CR
: BINARY ( n -- ) 2 BASE ! . DECIMAL ;
10 BINARY CRBecause the base is global state, forgetting to switch back is a classic Forth bug: every number read afterward is silently in the wrong base. That is why each line here returns to
DECIMAL immediately — the second line reads FF in hexadecimal and switches back before printing, so the 255 comes out in decimal —, where Python's hex() and int(…, 16) never change anything around them.Bitwise Operations
AND, OR and XOR work bit by bit, and INVERT flips every bit. LSHIFT and RSHIFT take the number and then the shift count.print(12 & 10, 12 | 10, 12 ^ 10)
print(~0)
print(1 << 4, 64 >> 3)12 10 AND . 12 10 OR . 12 10 XOR . CR
0 INVERT . CR
1 4 LSHIFT . 64 3 RSHIFT . CRThese are the same words Forth uses for logic, because true and false are just particular bit patterns — which the next section is about.
0 INVERT is −1: all bits set.True Is -1
True, False and Comparisons
A comparison in Forth leaves a flag on the stack, and a flag is an ordinary number:
FALSE is 0 and TRUE is −1. There is no separate boolean type.print(int(True), int(False))
print(int(3 < 5), int(3 == 5))TRUE . FALSE . CR
3 5 < . 3 5 = . CRPython's
True is 1 when used as a number; Forth's is −1, which is every bit set. That choice is what makes the bitwise words double as logical ones.Any Nonzero Number Counts as True
IF treats zero as false and any other number as true, just as Python treats 0 as falsy.for value in [0, 1, -5, 42]:
print(value, "yes" if value else "no"): YES-OR-NO ( n -- ) DUP . IF ." yes" ELSE ." no" THEN CR ;
0 YES-OR-NO
1 YES-OR-NO
-5 YES-OR-NO
42 YES-OR-NOThe resemblance ends with numbers: Python also treats empty strings, empty lists and
None as falsy, while Forth has none of those things — everything on the stack is a number, and the only false number is zero.AND Is Bitwise
Forth's
AND is the bitwise one. With real flags (0 and −1) that is the same as a logical "and", but 1 and 2 share no bits, so 1 2 AND is 0 — false — even though both numbers count as true. 0<> turns any number into a proper flag first.def both(first, second):
print("both true" if first & second else "not both")
both(1, 2)
both(1 != 0, 2 != 0): BOTH? ( a b -- ) AND IF ." both true" ELSE ." not both" THEN CR ;
1 2 BOTH?
1 0<> 2 0<> BOTH?The Python example falls into the same trap with
&, its bitwise operator, and is rescued the same way, by comparing first; Python's and would have said "both true" either way. Forth has only the bitwise words, so the rule is: combine flags, never raw numbers. A trailing ? is the customary name for a word that asks a question.Range Checks
WITHIN takes a number, a low bound and a high bound, and leaves true when the number is at least the low bound and less than the high one — the same half-open range as Python's 13 <= age < 20, and as range(13, 20).for age in [12, 13, 19, 20]:
print(age, 13 <= age < 20): TEENAGER? ( age -- flag ) 13 20 WITHIN ;
: SHOW ( age -- ) DUP . TEENAGER? . CR ;
12 SHOW 13 SHOW 19 SHOW 20 SHOWPython's chained comparison is the rare case where Python is more concise than Forth, but the meaning is identical, down to which end is inclusive.
Conditionals
IF, ELSE and THEN
The condition comes first, as a flag on the stack; then
IF, the true branch, ELSE, the false branch, and THEN to close. 0= is true when the number is zero.def parity(number):
if number % 2 == 0:
print(number, "is even")
else:
print(number, "is odd")
parity(10)
parity(7): PARITY ( n -- )
DUP . 2 MOD 0= IF ." is even" ELSE ." is odd" THEN CR ;
10 PARITY
7 PARITYIF, ELSE and THEN only work inside a definition. They are compiled into jumps when the word is defined, so there is nothing for them to do if typed on their own at the top level.Several Choices
There is no
elif. The usual Forth shape is a series of tests that each EXIT once they have handled the value. The first test needs the temperature again afterward, so it DUPs it and DROPs the copy when it matches; the second test consumes it.def classify(temperature):
if temperature < 0:
return "freezing"
elif temperature < 20:
return "cool"
else:
return "warm"
for temperature in [-5, 12, 30]:
print(classify(temperature)): CLASSIFY ( temperature -- )
DUP 0< IF DROP ." freezing" CR EXIT THEN
20 < IF ." cool" CR EXIT THEN
." warm" CR ;
-5 CLASSIFY 12 CLASSIFY 30 CLASSIFYNesting
IF … ELSE … THEN inside the ELSE branches also works, but each level adds another THEN at the end. Many Forths add a CASE … OF … ENDOF … ENDCASE structure for matching exact values, though it is an optional part of the standard and not every Forth has it.Only If Nonzero
?DUP duplicates the top item only if it is not zero. That makes the stack come out right on both branches: a nonzero count is still there to print after IF consumes its copy, and a zero leaves nothing behind to clean up.def report(count):
if count:
print(count, "items")
else:
print("nothing")
report(3)
report(0): REPORT ( count -- )
?DUP IF . ." items" ELSE ." nothing" THEN CR ;
3 REPORT
0 REPORTSmall words like
?DUP exist because Forth cannot give a value a name to test it and then use it — every such pattern becomes a stack pattern, and the common ones get a word of their own.Loops
Counted Loops
DO … LOOP takes the limit and then the start: 5 0 DO is range(0, 5). Inside the loop, I pushes the current index. The limit is excluded, as in Python.for index in range(5):
print(index, end=" ")
print(): COUNT-UP 5 0 DO I . LOOP CR ;
COUNT-UPThe limit comes first because it is the value most often computed by earlier code, and it sits deeper on the stack.
I is a word, not a variable — it reads the loop counter, which lives on the return stack.Stepping and Counting Down
+LOOP adds the number on the stack to the index instead of adding one, so it can step by any amount, including negative ones.for index in range(0, 20, 5):
print(index, end=" ")
print()
for index in range(10, 0, -3):
print(index, end=" ")
print(): BY-FIVES 20 0 DO I . 5 +LOOP CR ;
: DOWNWARD 0 10 DO I . -3 +LOOP CR ;
BY-FIVES
DOWNWARDA negative step has a surprise in store:
0 10 DO … -3 +LOOP runs 10, 7, 4 and 1 — here it matches Python — but a count-down loop stops only once it crosses the boundary between the limit and the limit minus one. When the limit is reached exactly, as with 0 9 DO … -3 +LOOP, Forth runs the 0 as well, where Python's range(9, 0, -3) would not.Nested Loops
In a nested loop,
I is the index of the innermost loop and J is the index of the loop around it. Both are read from the return stack, where each DO keeps its counter.for row in range(1, 4):
for column in range(1, 4):
print(row * column, end=" ")
print(): MULTIPLICATION-TABLE
4 1 DO
4 1 DO J I * . LOOP CR
LOOP ;
MULTIPLICATION-TABLEThere is no way to name the counters, so three levels deep the words are
I, J and — in many Forths — K. Anything deeper is a hint to move the inner loop into a word of its own.Loops With a Condition
BEGIN … WHILE … REPEAT is a while loop with the test in the middle: the code between BEGIN and WHILE computes a flag, and the loop continues while it is true. The step count rides underneath the number, and SWAP 1+ SWAP reaches under to increment it.number = 27
steps = 0
while number != 1:
number = number // 2 if number % 2 == 0 else 3 * number + 1
steps += 1
print(steps): COLLATZ-STEP ( n -- n' )
DUP 2 MOD 0= IF 2 / ELSE 3 * 1+ THEN ;
: COLLATZ-STEPS ( n -- steps )
0 SWAP \ steps n
BEGIN DUP 1 <> WHILE
COLLATZ-STEP SWAP 1+ SWAP
REPEAT DROP ;
27 COLLATZ-STEPS . CRKeeping two changing values on the stack and reaching past one to update the other is typical Forth. The alternative,
BEGIN … UNTIL, tests at the end like a do-while loop and repeats until the flag becomes true.Breaking Out of a Loop
LEAVE ends a DO loop at once, like break. The arguments arrive in the order DO itself wants — the stop value, then the start — so the word does nothing to them before the loop begins.def first_multiple_of_seven(start, stop):
for number in range(start, stop):
if number % 7 == 0:
print("found", number)
break
else:
print("none")
first_multiple_of_seven(30, 40): FIRST-MULTIPLE-OF-SEVEN ( stop start -- )
DO
I 7 MOD 0= IF ." found " I . CR LEAVE THEN
LOOP ;
40 30 FIRST-MULTIPLE-OF-SEVENPython's
for … else has no Forth counterpart; the Forth habit is to leave a flag on the stack saying whether anything was found. Designing a word's argument order around what it does first, as here, removes a SWAP from every call.Variables, Constants & Values
Variables Hold an Address
VARIABLE SCORE reserves one cell of memory and defines SCORE as a word that pushes that cell's address. Nothing is read or written until you ask: ! ("store") writes a value to an address, @ ("fetch") reads one, and +! adds to what is stored there.score = 0
score = 10
score += 5
print(score)VARIABLE SCORE
10 SCORE !
5 SCORE +!
SCORE @ . CRSaying the name of a Python variable gives its value; saying the name of a Forth variable gives its location, and fetching is a separate step. This is the same distinction as a C pointer and the value it points to, made explicit on every line.
Constants
CONSTANT takes the number on the stack and defines a word that pushes it. Unlike a variable, a constant's name gives the value itself, so no @ is needed.SECONDS_PER_HOUR = 60 * 60
print(3 * SECONDS_PER_HOUR)60 60 * CONSTANT SECONDS-PER-HOUR
3 SECONDS-PER-HOUR * . CRPython's uppercase name is only a convention, and nothing stops a later assignment. A Forth constant is a word, and the only way to "change" it is to define a new word with the same name, which — as with any redefinition — leaves already-compiled code using the old value.
Values: Variables That Read Like Constants
A
VALUE is the middle ground: its name pushes the current value, like a constant, and TO changes it, like a variable.speed = 30
print(speed)
speed = 55
print(speed)30 VALUE SPEED
SPEED . CR
55 TO SPEED
SPEED . CRValues are the closest Forth comes to an ordinary Python variable, and they are the usual choice for a setting that is read often and changed rarely.
TO is unusual among Forth words in that it acts on the name that follows it.A Temporary Place to Put Something
>R moves the top item to the return stack — the stack Forth uses to remember where each word should return to — and R> brings it back. In between, the data stack is free to work on what was underneath.def weighted_sum(first, second, weight):
saved = weight
total = first + second
return total * saved
print(weighted_sum(3, 4, 10)): WEIGHTED-SUM ( first second weight -- n )
>R + R> * ;
3 4 10 WEIGHTED-SUM . CRThe return stack is the closest thing Forth has to a local variable, and it comes with a rule: everything moved there must be moved back before the word ends, or the word "returns" to whatever number was left behind and crashes.
Arrays Are Memory You Reserve
Reserving an Array
CREATE SCORES defines a word that pushes an address, and 5 CELLS ALLOT reserves room for five cells right after it. CELLS converts a count of cells into a count of bytes. SCORE turns an index into the address of that element, which ! and @ then use like any other address.scores = [0] * 5
scores[2] = 90
scores[4] = 75
print(scores[2], scores[4])CREATE SCORES 5 CELLS ALLOT
: SCORE ( index -- address ) CELLS SCORES + ;
90 2 SCORE !
75 4 SCORE !
2 SCORE @ . 4 SCORE @ . CRA Forth array is nothing but arithmetic on an address: the element at index 2 lives
2 CELLS past the start. There is no length stored anywhere and no bounds check, so writing to index 7 would quietly overwrite whatever was defined next. Python lists carry their size and grow on demand; this memory never moves and never grows.An Array With Initial Values
, ("comma") stores a number in the next free cell of memory and moves past it, so a row of commas after CREATE builds an initialized array one element at a time. The loop fetches each element and adds it to a running total kept on the stack.primes = [2, 3, 5, 7, 11]
total = 0
for prime in primes:
total += prime
print(total)CREATE PRIMES 2 , 3 , 5 , 7 , 11 ,
5 CONSTANT PRIME-COUNT
: SUM-PRIMES ( -- total )
0 PRIME-COUNT 0 DO PRIMES I CELLS + @ + LOOP ;
SUM-PRIMES . CRBecause the array does not know its own length, the count is a separate
CONSTANT. Commas are also how Forth compiles code: a definition is a sequence of values laid down in memory, which is why the same word serves both purposes.Changing Every Element in Place
A word that works on an array receives its address and its count, the Forth equivalent of passing a list.
2 PICK copies the item two below the top — here the factor — without disturbing anything, and 2DROP removes the address and factor once the loop is done.levels = [1, 2, 3, 4]
for index in range(len(levels)):
levels[index] *= 10
print(*levels)CREATE LEVELS 1 , 2 , 3 , 4 ,
: SCALE-ALL ( address count factor -- )
SWAP 0 DO \ address factor
OVER I CELLS + DUP @ 2 PICK * SWAP !
LOOP 2DROP ;
: SHOW-ALL ( address count -- )
0 DO DUP I CELLS + @ . LOOP DROP CR ;
LEVELS 4 10 SCALE-ALL
LEVELS 4 SHOW-ALLPassing "address and count" is the universal Forth convention for any sequence, strings included, as the next section shows. Nothing is copied:
SCALE-ALL changes the one and only array, just as the Python loop changes the list it was given.A Record Is a Set of Offsets
A record is a block of cells, and a field is the distance from the start of the block.
POINT-X adds nothing, because x is the first cell, so its body is empty; POINT-Y adds one cell with CELL+. Applying a field word to a record's address gives the address of that field.from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
origin = Point(3, 4)
origin.y += 10
print(origin.x, origin.y): POINT-X ( point -- address ) ;
: POINT-Y ( point -- address ) CELL+ ;
CREATE ORIGIN 3 , 4 ,
10 ORIGIN POINT-Y +!
ORIGIN POINT-X @ . ORIGIN POINT-Y @ . CRThis is exactly what a C struct compiles down to, written out by hand. Forth 2012 adds
+FIELD and FIELD: to generate such words, but the idea is only ever "address plus offset" — there is no object, no type, and nothing to stop POINT-Y being applied to something that is not a point.Strings Are An Address And A Length
A String Literal
S" compiles the text into the word and, when the word runs, leaves two numbers on the stack: the address of the first character and the number of characters. TYPE takes that pair and prints it. NIP drops the address and keeps the length.greeting = "Hello, Forth"
print(greeting)
print(len(greeting)): GREETING ( -- address length ) S" Hello, Forth" ;
GREETING TYPE CR
GREETING NIP . CRThere is no string object. "A string" in Forth means an address and a length sitting on the stack, and every word that handles text takes and leaves that pair. The characters are plain bytes, so a length is a count of bytes rather than of Unicode characters.
Looking at Each Character
C@ fetches a single byte, so address I + C@ is the character at index I. [CHAR] s compiles the code of the letter s. The count is kept under the address, and SWAP 1+ SWAP reaches past the address to increment it.text = "mississippi"
print(text.count("s")): COUNT-CHARACTER ( address length character -- count )
0 2SWAP \ character count address length
0 DO
DUP I + C@ 3 PICK = IF SWAP 1+ SWAP THEN
LOOP DROP NIP ;
: DEMO S" mississippi" [CHAR] s COUNT-CHARACTER . CR ;
DEMOPython's
str.count is one of dozens of string methods; core Forth has almost none, and writing this loop is the normal way to get it. In practice every Forth programmer keeps a small personal library of words like this one, which is precisely what the language expects.Changing Case
A string literal's bytes should not be changed in place, so
MOVE first copies them into WORKSPACE, a buffer reserved with ALLOT. MOVE takes a source address, a destination address and a length. UPPERCASE then subtracts 32 from every byte between a and z — the distance between lowercase and uppercase letters in ASCII — and stores it back with C!.word = "forth"
print(word.upper())CREATE WORKSPACE 32 ALLOT
: UPPERCASE ( address length -- )
0 DO
DUP I + C@
DUP [CHAR] a [CHAR] z 1+ WITHIN IF 32 - THEN
OVER I + C!
LOOP DROP ;
: DEMO
S" forth" WORKSPACE SWAP MOVE
WORKSPACE 5 UPPERCASE
WORKSPACE 5 TYPE CR ;
DEMOPython's
upper() understands every alphabet in Unicode; this word understands 26 letters, and says so openly. Choosing to handle only ASCII is a design decision Forth makes you take explicitly, where Python has already taken the broad one for you.Joining Strings
Joining strings means copying bytes into a buffer that is large enough, and keeping track of how much of it is in use.
APPEND copies a string to the end of what is already in MESSAGE, then adds its length to MESSAGE-LENGTH. The length is saved on the return stack across the MOVE, which consumes it.name = "Ada"
message = "Hello, " + name + "!"
print(message)
print(len(message))CREATE MESSAGE 64 ALLOT
VARIABLE MESSAGE-LENGTH
: APPEND ( address length -- )
DUP >R MESSAGE MESSAGE-LENGTH @ + SWAP MOVE
R> MESSAGE-LENGTH +! ;
: DEMO
0 MESSAGE-LENGTH !
S" Hello, " APPEND S" Ada" APPEND S" !" APPEND
MESSAGE MESSAGE-LENGTH @ TYPE CR
MESSAGE-LENGTH @ . CR ;
DEMOPython's
+ allocates a brand new string of the right size every time. Forth has no allocator in its core, so the buffer is sized up front — and nothing stops an APPEND from writing past its 64 bytes, which is the classic buffer overflow in its purest form.Comparing Two Strings
Two strings are equal when their lengths match and every byte matches. The first line compares the lengths and gives up early if they differ. The loop then compares byte by byte, and
UNLOOP must come before an EXIT from inside a DO loop, so the loop's counters are cleared off the return stack first.def same_text(first, second):
return first == second
print(same_text("apple", "apple"))
print(same_text("apple", "apply")): SAME-TEXT? ( address1 length1 address2 length2 -- flag )
ROT OVER <> IF DROP 2DROP FALSE EXIT THEN \ lengths differ
0 ?DO
OVER I + C@ OVER I + C@ <> IF 2DROP FALSE UNLOOP EXIT THEN
LOOP 2DROP TRUE ;
: DEMO
S" apple" S" apple" SAME-TEXT? . CR
S" apple" S" apply" SAME-TEXT? . CR ;
DEMOMany Forths provide
COMPARE, which also orders strings the way Python's < does, in the optional string word set. Writing the loop once shows what it costs: Python's == on strings is this same loop, run in C.Turning Text Into a Number
>NUMBER converts digits into a double-width number, starting from a double-width value already on the stack — the 0 0 here. It stops at the first character that is not a digit and leaves whatever is left of the string, so 2DROP discards the unconverted remainder and DROP keeps the low half of the result.text = "1234"
number = int(text)
print(number + 1): TEXT>NUMBER ( address length -- n )
0 0 2SWAP >NUMBER 2DROP DROP ;
: DEMO S" 1234" TEXT>NUMBER 1+ . CR ;
DEMOPython's
int() raises ValueError on "12ab"; >NUMBER simply stops at the a and reports how many characters it did not use, leaving the caller to decide whether that counts as an error. The word honors BASE, so the same text means something else in HEX.Formatting Numbers
A Number as Text
Forth converts a number to text with pictured numeric output.
<# begins an empty picture, #S converts all the digits, and #> finishes, leaving an address and length ready for TYPE. The 0 before it widens the number to double width, which is what these words work on.count = 42
print(f"[{count}]"): .BRACKETED ( n -- )
." [" 0 <# #S #> TYPE ." ]" CR ;
42 .BRACKETED. is itself defined this way, plus a trailing space. Building the text explicitly is how to print a number with nothing after it. A word whose name starts with a dot is, by convention, a word that prints something.Fixed Digits and Separators
Each
# converts exactly one digit, supplying a zero when the number has run out, so # # always produces two digits. HOLD inserts a character. The picture is built from the right: in .MONEY the two cent digits come first, then the decimal point, then the remaining dollars, and the dollar sign last.hours, minutes = 9, 5
print(f"{hours:02}:{minutes:02}")
cents = 12345
print(f"${cents // 100}.{cents % 100:02}"): TWO-DIGITS ( n -- ) 0 <# # # #> TYPE ;
: .TIME ( hours minutes -- ) SWAP TWO-DIGITS ." :" TWO-DIGITS CR ;
: .MONEY ( cents -- )
0 <# # # [CHAR] . HOLD #S [CHAR] $ HOLD #> TYPE CR ;
9 5 .TIME
12345 .MONEYPython's format specifications describe the result left to right; Forth's pictured output describes the process right to left, the order in which digits fall out of repeated division by ten.
Right-Aligned Columns
To right-align, build the text first, then print as many spaces as the width has left over.
SIGN adds a minus sign when the number it is given is negative, so the digits are converted from the absolute value while the original number waits underneath, and ROT brings it up for SIGN at the end. OVER - computes the width minus the text length.for amount in [7, 120, -45, 3000]:
print(f"{amount:>6}|"): .RIGHT ( n width -- )
>R DUP ABS S>D <# #S ROT SIGN #>
R> OVER - SPACES TYPE ;
: DEMO
7 6 .RIGHT ." |" CR 120 6 .RIGHT ." |" CR
-45 6 .RIGHT ." |" CR 3000 6 .RIGHT ." |" CR ;
DEMOMany Forths define exactly this word under the name
.R; it is not in the core word set, and seeing it built from <# and SIGN shows why it never needed to be.Words As Values
A Reference to a Word
' ("tick") reads the next word's name and pushes its execution token — a number identifying the word — instead of running it. EXECUTE takes an execution token off the stack and runs that word.def square(number):
return number * number
operation = square
print(operation(12)): SQUARE ( n -- n*n ) DUP * ;
' SQUARE 12 SWAP EXECUTE . CRAn execution token is Forth's function object, but it is only a number: it can be stored in a variable, kept in an array or passed to another word, and nothing records what kind of word it refers to. Inside a definition the same thing is written
['] SQUARE, because ' would try to read the name when the definition runs rather than when it is compiled.Passing a Word to a Word
SHOW-EACH takes an array, its length and an execution token, and runs that word on every element. Built-in words have execution tokens exactly like defined ones, so ' NEGATE works as well as ' SQUARE.def square(number):
return number * number
def negate(number):
return -number
def show_each(numbers, operation):
print(*[operation(number) for number in numbers])
values = [1, 2, 3, 4]
show_each(values, square)
show_each(values, negate): SQUARE ( n -- n*n ) DUP * ;
CREATE VALUES 1 , 2 , 3 , 4 ,
: SHOW-EACH ( address count xt -- )
SWAP 0 DO \ address xt
OVER I CELLS + @ OVER EXECUTE .
LOOP 2DROP CR ;
VALUES 4 ' SQUARE SHOW-EACH
VALUES 4 ' NEGATE SHOW-EACHThis is
map, with the result printed instead of collected — collecting would need somewhere to put it, and Forth makes you decide where. The Python example needs negate as a function of its own, where Forth passes the built-in word directly.An Anonymous Word
:NONAME compiles a definition that has no name and, at the ;, leaves its execution token on the stack. VALUE TRIPLE stores that token under a name of our choosing, and TRIPLE EXECUTE runs it.triple = lambda number: number * 3
print(triple(14)):NONAME ( n -- n*3 ) 3 * ; VALUE TRIPLE
14 TRIPLE EXECUTE . CRIt resembles a
lambda, with one important difference: the word captures nothing. Forth has no closures, because a definition cannot see the stack items or loop counters that existed where it was written — it only ever sees what is on the stack when it runs.A Word You Can Swap Out Later
DEFER GREETING creates a word whose behavior is filled in later. IS stores an execution token into it. START-DAY is compiled once, but because GREETING looks up its current behavior every time it runs, changing it with IS changes what START-DAY does.def polite_greeting():
print("Good morning.")
def casual_greeting():
print("Hey!")
greeting = polite_greeting
def start_day():
greeting()
start_day()
greeting = casual_greeting
start_day()DEFER GREETING
: START-DAY GREETING ;
:NONAME ." Good morning." CR ; IS GREETING
START-DAY
:NONAME ." Hey!" CR ; IS GREETING
START-DAYAn ordinary redefinition, as shown earlier, would not reach
START-DAY at all. A deferred word gives Forth the late binding Python has everywhere, but only in the places a programmer asks for it.A Table of Words
An array of execution tokens is a dispatch table.
' + , lays the token for + down in memory, exactly the way , laid down numbers in the arrays above. CALCULATE fetches the token at the chosen index and executes it, and the arithmetic word finds its two operands already waiting on the stack.import operator
operations = [operator.add, operator.sub, operator.mul]
def calculate(first, second, choice):
return operations[choice](first, second)
print(calculate(20, 4, 0), calculate(20, 4, 1), calculate(20, 4, 2))CREATE OPERATIONS ' + , ' - , ' * ,
: CALCULATE ( first second choice -- n )
CELLS OPERATIONS + @ EXECUTE ;
20 4 0 CALCULATE . 20 4 1 CALCULATE . 20 4 2 CALCULATE . CRPython's
operator module exists to turn + into a function that can be stored. In Forth, + was a word all along, with nothing to convert.Words That Write Words
A Word That Makes Words
COUNTER is a defining word: using it defines a new word. The part before DOES> runs when COUNTER APPLES is executed — it creates APPLES and stores a 0 in its body. The part after DOES> becomes the behavior of every word made this way; it starts with the address of that word's own body on the stack, then increments the number stored there and leaves the new value.def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
apples = make_counter()
pears = make_counter()
apples()
apples()
print(apples(), pears()): COUNTER ( "name" -- )
CREATE 0 ,
DOES> ( -- count ) DUP @ 1+ DUP ROT ! ;
COUNTER APPLES
COUNTER PEARS
APPLES DROP APPLES DROP
APPLES . PEARS . CRThis is Forth's answer to both closures and classes. Each counter owns its own cell of memory, the way each Python closure owns its own
count, and VARIABLE and CONSTANT are defining words built the same way. The stack comment ( "name" -- ) records that the word reads a name from the source text.Adding a New Control Structure
An
IMMEDIATE word runs while other words are being compiled, instead of being compiled into them. POSTPONE compiles a word into the definition in progress. So when CHECK is compiled, UNLESS runs and writes 0= IF into CHECK — a new keyword that closes with THEN just like IF.def unless(condition, action):
if not condition:
action()
unless(3 > 5, lambda: print("first is not greater"))
unless(5 > 3, lambda: print("never printed")): UNLESS POSTPONE 0= POSTPONE IF ; IMMEDIATE
: CHECK ( a b -- )
> UNLESS ." first is not greater" CR THEN ;
3 5 CHECK
5 3 CHECKPython cannot add syntax, so the nearest equivalent passes the body as a function and runs it later. The Forth version is a true control structure with no function call at all;
IF, DO and ; are themselves immediate words, defined this same way.Computing While Compiling
Inside a definition,
[ switches the interpreter back to running words instead of compiling them, and ] switches compiling back on. The arithmetic between them runs once, while WEEKS>SECONDS is being compiled, and LITERAL compiles the answer into the word as a plain number.SECONDS_PER_WEEK = 60 * 60 * 24 * 7
def weeks_to_seconds(weeks):
return weeks * SECONDS_PER_WEEK
print(weeks_to_seconds(3)): WEEKS>SECONDS ( weeks -- seconds )
[ 60 60 * 24 * 7 * ] LITERAL * ;
3 WEEKS>SECONDS . CRPython computes
SECONDS_PER_WEEK when the module is imported, and CPython also folds constant expressions like 60 * 60 on its own. Forth hands the switch between compiling and running to the programmer, in the middle of a line.Running Code From a String
EVALUATE takes a string and interprets it exactly as if it had been part of the program's source, so the text can define words, print or leave values on the stack.source = "print(6 * 7)"
exec(source): DEMO S" 6 7 * . CR" EVALUATE ;
DEMOPython's
exec runs code inside a separate namespace. EVALUATE has no such boundary: it shares the one stack and the one dictionary, so whatever it leaves or defines is simply there afterward. A Forth outer interpreter is not much more than a loop around the same idea.When Things Go Wrong
An Unknown Word Stops Everything
When the interpreter meets a word it cannot find in the dictionary and cannot read as a number, it reports it and abandons the rest of the input. The first
GREET has already run by then; the last never does.def greet():
print("hi")
try:
greet()
gret()
greet()
except NameError as error:
print(error): GREET ." hi" CR ;
GREET
GRET
GREETThe effect matches an uncaught
NameError, which the Python example catches only so both columns stop at the same point. The difference is when it is detected: Python notices a misspelled name only when that line runs, while Forth notices a misspelling inside a definition as soon as the definition is compiled, before anything calls it.Stopping With a Message
ABORT" takes a flag and, if it is true, prints its message, empties both stacks and returns control to the outermost interpreter, skipping everything still pending. If the flag is false it does nothing and the word carries on.def check_age(age):
if age < 0:
raise ValueError("age cannot be negative")
print("age", age)
try:
check_age(30)
check_age(-1)
check_age(40)
except ValueError as error:
print(error): CHECK-AGE ( age -- )
DUP 0< ABORT" age cannot be negative"
." age " . CR ;
30 CHECK-AGE
-1 CHECK-AGE
40 CHECK-AGEAn
ABORT" is a raise that no one can catch: it always unwinds all the way out, which in an interactive Forth means back to the prompt, as the Python example's single outer try does. The failure shown is the expected one.Reporting Failure With a Flag
The everyday Forth way to report failure is to leave a flag on top of the results.
SAFE-DIVIDE leaves the quotient and TRUE when it can divide, and only FALSE when it cannot. The vertical bar in the stack comment separates the two possible outcomes.def safe_divide(dividend, divisor):
try:
return dividend // divisor
except ZeroDivisionError:
return None
for divisor in [4, 0]:
result = safe_divide(20, divisor)
print("no result" if result is None else result): SAFE-DIVIDE ( dividend divisor -- quotient true | false )
DUP 0= IF 2DROP FALSE ELSE / TRUE THEN ;
: SHOW ( divisor -- )
20 SWAP SAFE-DIVIDE IF . ELSE ." no result" THEN CR ;
4 SHOW
0 SHOWThe caller must test the flag before it can use anything else, much as it must check for
None in Python. A word whose results change shape depending on success is common in Forth and would be considered strange in Python, where a function returns one value either way.CATCH and THROW
Forth's optional exception word set adds
CATCH and THROW. CATCH executes a word and leaves 0 if it finished normally; THROW with a nonzero number unwinds back to the nearest CATCH, which leaves that number instead. An exception is identified by a number, not a class. Programs use positive numbers, because −1 through −4095 are reserved for the standard and the system.class EmptyTankError(Exception):
pass
def drive(fuel):
if fuel == 0:
raise EmptyTankError()
print("driving")
for fuel in [5, 0]:
try:
drive(fuel)
except EmptyTankError:
print("out of fuel")1 CONSTANT EMPTY-TANK
: DRIVE ( fuel -- )
0= IF EMPTY-TANK THROW THEN ." driving" CR ;
: TRY-TO-DRIVE ( fuel -- )
['] DRIVE CATCH
EMPTY-TANK = IF DROP ." out of fuel" CR THEN ;
5 TRY-TO-DRIVE
0 TRY-TO-DRIVEWhen
CATCH reports a throw, it restores the data stack to its depth before the call, so the argument DRIVE consumed is back as an unknown value; the DROP removes it. This is how Forth unwinds safely without knowing what the abandoned word left behind. A program that hits a problem with no CATCH in place behaves like ABORT.Turtle Graphics
Drawing a Square
Turtle graphics come from Logo, the 1967 teaching language, and Python ships them as its
turtle module. A turtle has a position and a heading: FORWARD moves it the given distance, drawing a line, and RIGHT turns it clockwise by a number of degrees. Here those are Forth words that take their arguments from the stack like any other.import turtle
pen = turtle.Turtle()
for side in range(4):
pen.forward(100)
pen.right(90)
turtle.done(): SQUARE ( size -- )
4 0 DO DUP FORWARD 90 RIGHT LOOP DROP ;
100 SQUAREThe turtle words on this page are the vocabulary of Thurtle, a turtle graphics environment for Forth:
FORWARD, BACKWARD, LEFT, RIGHT, PENUP, PENDOWN, SETXY, SETHEADING, SETPENSIZE, SETPENCOLOR, SETSCREENCOLOR, HIDETURTLE and SHOWTURTLE. Each is a one-line Forth word that passes its numbers out to the drawing.Words Built From Words
A star is five lines with a 144-degree turn after each.
HOP lifts the pen, moves and puts it down again, so the turtle can travel without drawing. RING-OF-STARS repeats a star and a hop six times, turning 60 degrees each time to make the circle. SETPENCOLOR takes a color as a single number, written most easily in hexadecimal.import turtle
pen = turtle.Turtle()
pen.pencolor("#d32f2f")
def star(size):
for point in range(5):
pen.forward(size)
pen.right(144)
for step in range(6):
star(60)
pen.penup()
pen.forward(90)
pen.right(60)
pen.pendown()
turtle.done()HEX D32F2F DECIMAL SETPENCOLOR
: STAR ( size -- ) 5 0 DO DUP FORWARD 144 RIGHT LOOP DROP ;
: HOP ( distance -- ) PENUP FORWARD PENDOWN ;
: RING-OF-STARS
6 0 DO 60 STAR 90 HOP 60 RIGHT LOOP ;
RING-OF-STARSEvery word here is two or three words deep, and each can be tried on its own —
60 STAR alone draws one star. That habit of building a vocabulary of tiny, separately testable words is the whole of Forth programming style, and turtle graphics make it visible.A Recursive Tree
Each branch draws its trunk, turns left and draws a smaller tree, turns right and draws another, then turns back and retraces its trunk so the turtle ends exactly where it started.
3 4 */ scales the length to three quarters. The length stays on the stack through both recursive calls because each call begins with DUP, and the final BACKWARD consumes it.import turtle
pen = turtle.Turtle()
pen.left(90)
def tree(length):
if length < 8:
return
pen.forward(length)
pen.left(25)
tree(length * 3 // 4)
pen.right(50)
tree(length * 3 // 4)
pen.left(25)
pen.backward(length)
tree(80)
turtle.done(): TREE ( length -- )
DUP 8 < IF DROP EXIT THEN
DUP FORWARD
25 LEFT DUP 3 4 */ RECURSE
50 RIGHT DUP 3 4 */ RECURSE
25 LEFT
BACKWARD ;
80 TREEThe Python version needs
pen.left(90) to point upward first, because Python's turtle starts facing east; this turtle starts facing north, as Logo's did. Recursion that leaves the turtle where it found it is the same discipline as a word that leaves the stack the way it found it.Gotchas For Python Programmers
A Loop That Should Run Zero Times
?DO is DO with a safety check: if the start and the limit are equal, it skips the loop entirely, as range(0) does.def stars(count):
for star in range(count):
print("*", end="")
print("|")
stars(3)
stars(0): STARS ( count -- )
0 ?DO [CHAR] * EMIT LOOP ." |" CR ;
3 STARS
0 STARSPlain
DO runs its body at least once, and 0 0 DO does not stop until the index wraps all the way around — about four billion times on a 32-bit Forth. Use ?DO whenever the count might be zero.A Word That Leaves Something Behind
OVER copies the first argument, so AVERAGE consumes two numbers but leaves two: its answer and a stray 10 underneath. . prints the answer, and DEPTH reveals the item still on the stack.def average(first, second):
total = first + second
return total // 2
print(average(10, 20)): AVERAGE ( a b -- n ) OVER + 2 / ; \ a bug: OVER should not be here
10 20 AVERAGE . CR
DEPTH . CRPython counts arguments and return values for you and complains when they are wrong. Forth does not: this word prints a correct-looking 15 while quietly corrupting the stack for whatever runs next. Checking
DEPTH after testing a new word is the Forth habit that catches it.= Does Not Compare Strings
= compares two cells, and a string is two cells — an address and a length. Comparing the lengths says they match; comparing the addresses says the two literals live in different places. Neither says anything about the characters.first = "".join(["c", "a", "t"])
second = "cat"
print(first == second)
print(first is second): FIRST S" cat" ;
: SECOND S" cat" ;
: DEMO
FIRST NIP SECOND NIP = . CR \ the lengths are equal
FIRST DROP SECOND DROP = . CR ; \ the addresses are not
DEMOThis is Python's
is against ==: is asks whether two names refer to the same object, and on strings built at runtime it is false even when the text matches. Forth has only the is kind of comparison built in; comparing contents means a loop like the SAME-TEXT? word shown earlier.Anything Can Be a Word Name
When Forth reads a token, it looks it up in the dictionary before trying to read it as a number. Defining a word named
3 therefore changes what 3 means for everything that follows.# In Python, 3 is always 3.
three = 4
print(3 + 3, three + three): 3 4 ;
3 3 + . CRA word name is any sequence of characters without a space, so
1+, ?DUP, >R and even 3 are all legal names. It is the same rule that makes 2DUP a single word rather than a number followed by DUP — and the reason a missing space is so often the cause of an "undefined word" error.