The Machines · Runtime stack

seedbool

A reckoner seed: bool's binary layer — machines/seeds/seedbool.shoddy

the seedbool machine's icon

Summary

seedbool is a reckoner seed: the file that bridges one machine's words into the reckoner, the calculator engine. It bridges bool's binary layer: HEX BIN INBASE FROMBASE, BITAND BITOR BITXOR BITNOT SHL SHR SHRA ROTL ROTR, MASK GETFIELD BITAT POPCOUNT LOWBIT HIGHBIT CARD CARDWITH, SIGNED UNSIGNED SIGNEXTEND NEGW ADDW SUBW MULW CARRIES OVERFLOWS, GRAY FROMGRAY BCD FROMBCD PARITY PARITYBIT HAMMING, MINTERMS IMPLICANTS TABLE, and the six logic gates the engine core does not already own — NAND NOR XOR XNOR IMPLIES IFF. Its whole public surface is one function. Fold RckSeedBool over a state, and all forty-six words are in the dictionary.

It is the widest seed, because bool is the widest domain bridged. Bases, bit operations, masks, two's complement (the standard way a machine stores negative numbers), the codes, minimisation and gates share nothing but the value they work on. A keyboard that has one of them and not the others cannot finish the job.

Sixteen of these words read the session word width rather than taking one, and none of them takes a width argument. The width is how many bits wide a value is treated as. WIDTH is a reckoner core word beside FIX and DEG. 8 WIDTH sets it, WIDTH? reports it, and it starts at 32. That mode is what made the rotates, the arithmetic shift and the whole two's-complement family bridgeable at all. Each of them wants a width, and forty words each carrying one as an argument is not a bridge but a second dictionary.

Why It's Useful

Every Bool* word aborts on a bad domain — it stops the whole program — and nothing keyable may do that. Preventing it is the whole work of this file. bool is deliberately the strictest machine in the tree. It refuses a negative, a fraction, a value that does not fit its width, a width outside 1..53, and a base outside 2..36. Every one of those refusals is an Error, which would take the mill down with it. So each word here asks first the question bool would abort on, and answers a refusal instead. That is rule R4.1, applied to a machine that guards itself thoroughly — but in the one way a reckoner cannot use.

The guard helpers answer a reason string rather than a Result. The string is empty when nothing is wrong. The question is only ever "may this proceed, and if not why not", and there is no payload to carry.

Every guard tests its reason with If/Then/Else and never Ifte. Ifte is an ordinary call, so both of its arms run before it chooses between them. Handing it a Bool* word as one arm would run exactly the abort the guard was written to prevent. If/Then/Else compiles to two quotations — programs held as data, not yet run — and one Ifte over them, so only the chosen arm runs. This is the single most dangerous thing in the file to "tidy up".

User's Guide

Let st0 = RckSeedBool(RckNew())
RckEval(st0, "255 HEX")                  ' x: "FF"
RckEval(st0, "12 10 BITAND")             ' x: 8
RckEval(st0, "171 7 4 GETFIELD")         ' x: 10
RckEval(st0, "1 7 SHL")                  ' x: 128

Set the width once and the bit words work inside it. Every line below answers differently at the default 32. There the bit does not fall off (256), the pattern is not negative (255), and the card is thirty-two holes wide instead of eight. That difference is the whole argument for a mode:

Let st8 = RckEval(st0, "8 WIDTH")        ' a byte from here on
RckEval(st8, "1 8 SHL")                  ' x: 0      — the bit fell off the end
RckEval(st8, "255 SIGNED")               ' x: -1     — the same bits, read as signed
RckEval(st8, "202 CARD")                 ' x: "##..#.#."
RckEval(st8, "200 100 ADDW")             ' x: 44     — wrapped, as a byte does
RckEval(st8, "200 100 CARRIES")          ' x: True   — and it carried out

CARRIES and OVERFLOWS are the pair nobody expects and everybody needs. Unsigned overflow and signed overflow are different questions with different answers, and the same two operands routinely disagree. A machine that gave the wrapping arithmetic without the flags would be teaching half of it:

RckEval(st8, "100 100 CARRIES")          ' x: False  — 200 fits a byte
RckEval(st8, "100 100 OVERFLOWS")        ' x: True   — but not a SIGNED byte

Absence is an empty list. LOWBIT and HIGHBIT answer an Option in bool, and zero genuinely has no lowest set bit. That is an ordinary answer rather than an error, so the bridge answers { }. The answer is one cell either way, which is what keeps UNDO's accounting. And it composes with the words a keyboard already has:

RckEval(st0, "12 LOWBIT")                ' x: { 2 }
RckEval(st0, "0 LOWBIT")                 ' x: { }
RckEval(st0, "0 LOWBIT LENGTH")          ' x: 0      — "was there a bit?"
RckEval(st0, "12 LOWBIT FIRST")          ' x: 2      — "which one?"

Minimisation answers text. That is how a product term reaches a stack that has no cell for one. The minterms — the input rows where the answer is true — go in as a LIST, which is the input somebody actually has after filling in a table:

RckEval(st0, "{ 0 1 2 3 } 3 MINTERMS")   ' x: "A'"
RckEval(st0, "{ 1 2 } 2 TABLE")          ' x: "A B | OUT
                                         '     F F | F
                                         '     F T | T
                                         '     T F | T
                                         '     T T | F"

The field words are for decoding a register — a machine's own small storage slot — at the keyboard. 180 is 1011 0100. There are no hex literals in Shoddy, so a value is typed in decimal or read with FROMBASE:

RckEval(st0, "180 5 2 GETFIELD")         ' x: 13   — bits 5..2
RckEval(st0, "180 7 BITAT")              ' x: True — the top bit
RckEval(st0, "180 BIN")                  ' x: "10110100"

Refusals name the bound they are enforcing, not merely that something was out of range. "1 to 53" is the fact a caller needs; "bad width" is not:

RckEval(st8, "256 BITNOT")     ' BITNOT needs a value that fits 8 bits, and 256 does not
RckEval(st0, "54 WIDTH")       ' WIDTH needs a width from 1 to 53, got 54
RckEval(st0, "5 37 INBASE")    ' INBASE needs a base from 2 to 36, got 37
RckEval(st0, "TRUE 1 XOR")     ' XOR needs a BOOLEAN, got NUMBER

Note where the second of those now comes from. A width outside 1..53 is refused by WIDTH itself, once, where the mode is set. Every word that later reads the width reads a number already known good, so none of them carries that check.

FROMBASE is the one guard this seed translates rather than invents. Text that fails to read is not a bug, so bool answers a Result there. This seed turns that into a refusal carrying the 1-based position. The message names FROMBASE and not BOOLFROMBASE: the prefix up to the first colon is replaced rather than repeated, because naming two words for one refusal names neither of them clearly:

RckEval(st0, "\"1F\" 2 FROMBASE")  ' FROMBASE: '1F' HAS NO DIGIT F IN BASE 2 at position 2

Word Reference

Bases and rendering

WordDescription
HEX ( n -- s )n written in hexadecimal, uppercase.
BIN ( n -- s )n written in binary.
INBASE ( n base -- s )n written in the given base, 2 to 36, uppercase letters past 9; refuses a base outside that range.
FROMBASE ( s base -- n )The digit string s read in the given base, case-insensitively; refuses a digit the base does not have, naming its 1-based position, and refuses the empty string.

Bit operations

WordDescription
BITAND ( a b -- c )Bitwise AND. Needs no width.
BITOR ( a b -- c )Bitwise OR. Needs no width.
BITXOR ( a b -- c )Bitwise XOR. Needs no width. These three are the only width-free words, here as in bool itself.
BITNOT ( a -- c )Bitwise NOT within the session width; refuses a value that does not fit it. Every bit of the word flips, so the answer depends on how wide the word is — 255 BITNOT is 0 at width 8 and 4294967040 at 32.
SHL ( a n -- c )a shifted left n places within the session width, bits above it falling off; refuses a shift past 53 and a value that does not fit the width.
SHR ( a n -- c )a shifted right n places, zeros coming in at the top. Needs no width, because a logical right shift cannot produce a bit the input did not have.
SHRA ( a n -- b )Arithmetic right shift at the session width: the top bit is copied down rather than zeros coming in, so a negative pattern stays negative. This is the difference between SHRA and SHR and the only reason both exist.
ROTL ( a n -- b )a rotated left n places within the session width; bits leaving the top come back at the bottom.
ROTR ( a n -- b )a rotated right n places; bits leaving the bottom come back at the top.

SHL and BITNOT took their width as an argument until the session mode existed, and now read the session's. Both are changes to the arity — how many arguments a word takes — and not only to where the number comes from. 255 1 32 SHL is now 255 1 SHL, and 255 8 BITNOT is now 255 BITNOT. At the default width the answers are unchanged, which is what makes the mode opt-in. It is also what makes the change easy to under-rate, since the number is the same and the call is not. The change was made because the width-taking words are one family. One member demanding an argument while the rest read a mode is the surface nobody can remember. SHR is untouched: a word that needs no width gains nothing from one.

Masks, fields and inspection

WordDescription
MASK ( n -- m )A mask with the low n bits set, n from 0 to 53.
GETFIELD ( a hi lo -- v )Bits hi down to lo of a, inclusive and 0-based, shifted down to zero; refuses an inverted field and an index past 52.
BITAT ( a k -- flag )Whether bit k of a is set. Answers a BOOLEAN, never 1 or 0.
POPCOUNT ( a -- n )How many of a's bits are set.
LOWBIT ( a -- list )The index of a's lowest set bit as a one-item LIST, or an empty LIST when no bit is set. LENGTH asks whether there was one; FIRST takes it out.
HIGHBIT ( a -- list )The index of a's highest set bit, the same way.
CARD ( a -- s )a as a row of session-width holes, # for a set bit and . for a clear one. Plain ASCII, so it survives a tape or a saved session.
CARDWITH ( a on off -- s )CARD with your own two glyphs, for a terminal that can hold them. Anything outside ASCII will not survive being saved.

Zero's lowest set bit is an absence, not an error. LOWBIT and HIGHBIT answer { } there rather than refusing. A word that refused on zero would contradict the contract bool itself draws. The answer is one cell either way, because a word pushing a variable number of cells would break UNDO's accounting. seedregex's RXGROUPS keeps the same rule for the same shape. And bit zero is an index, not an absence — which is exactly the case a −1 sentinel (a special value standing for "none") confuses.

Two's complement and wrapping arithmetic

All at the session width, which is what makes them one-argument words instead of two-argument ones.

WordDescription
SIGNED ( a -- v )The bit pattern a read as a two's-complement signed value. At width 8, 255 SIGNED is −1; at 32 it is still 255.
UNSIGNED ( v -- a )The signed value v as the bit pattern that holds it. The one word here whose input may be negative, which is what it is for; refuses a value the width cannot hold as signed, naming the range it missed.
SIGNEXTEND ( a from -- b )The from-bit pattern a widened to the session width, its sign bit copied into the new bits; refuses a session width narrower than the source, which is not widening at all.
NEGW ( a -- b )Two's-complement negation. Zero negates to zero — the case a NOT-plus-one implementation gets wrong.
ADDW ( a b -- c )a plus b, wrapping at the session width the way a machine word does.
SUBW ( a b -- c )a minus b, wrapping; below zero it comes back round the top.
MULW ( a b -- c )a times b, keeping only the low bits that fit.
CARRIES ( a b -- flag )Whether a plus b carries out of the width — unsigned overflow.
OVERFLOWS ( a b -- flag )Whether a plus b overflows as signed values. A different question from CARRIES, with a different answer.

The codes

None of these wants a width: they are facts about a number's bits rather than about a machine word.

WordDescription
GRAY ( n -- g )n in Gray code, where consecutive values differ in exactly one bit.
FROMGRAY ( g -- n )The number a Gray code stands for.
BCD ( n -- b )n packed as binary-coded decimal, one digit to each four bits. Up to thirteen digits, which is what 53 bits hold.
FROMBCD ( b -- n )The number a BCD pattern stands for; refuses a nibble above 9, which is not a decimal digit. Translated from bool's Result, the same way FROMBASE is.
PARITY ( a -- flag )Whether an odd number of a's bits are set. Answers a BOOLEAN.
PARITYBIT ( a -- k )The even-parity bit to append to a: 1 when the set-bit count is odd.
HAMMING ( a b -- d )How many bit positions a and b differ in. Needs no width, and is a registered word because a user word takes exactly one cell.

Minimisation and truth tables, as text

A product term has no cuttle Cell, and inventing one would be a design rather than a bridge. It never reaches the stack. bool already renders both structures as strings, and a string is a Cell, so these three answer the rendering.

WordDescription
MINTERMS ( ms n -- s )The minimised sum-of-products covering those minterms over n variables, as text. Minimal in practice rather than proven minimal — the cover step is greedy, because exact cover is NP-hard.
IMPLICANTS ( ms n -- s )Every prime implicant of those minterms — the chart behind MINTERMS rather than one cover chosen from it.
TABLE ( ms n -- s )The truth table over n variables that is true on exactly those minterms. One string of 2n rows under a header line; four variables is sixteen rows, which is a screen and not a wall.

n is a variable count, not the session width. These are Boolean variables, not bits of a machine word. Letting WIDTH govern a truth table would be one mode quietly answering a question nobody pointed it at. Above twelve variables the words refuse by name, because 216 rows is not a slow answer but a hang. The variables are named A, B, C and on. bool's rendering words take the names rather than inventing them, because a program has its own. At a keyboard there is nobody to ask, and the letters are what the textbook prints.

The six gates the core does not own

All work on BOOLEAN, and each is one line over RckBool2, which names the kind it got instead when handed something else. They have to be here: a user word takes exactly one cell, so a keyboard user cannot define a two-argument XOR for themselves.

WordDescription
NAND ( p q -- r )NOT (p AND q).
NOR ( p q -- r )NOT (p OR q).
XOR ( p q -- r )True when exactly one of p and q is true.
XNOR ( p q -- r )True when p and q agree.
IMPLIES ( p q -- r )False on exactly one row: p true and q false.
IFF ( p q -- r )Material equivalence — the same answer as XNOR, under a logician's name.

What the page used to say, and why it no longer does

This seed stopped at twenty words for a long time. Four of the five families it left out were left out for reasons that have since dissolved. The load-bearing one was the width. The reasoning is kept here because it is worth following, not as a list of regrets.

The old objectionWhat answers it
The rotates, BoolShrA, two's complement and the codes each "want a width the keyboard must keep supplying, and a seed of forty words has stopped being a bridge and become a second dictionary."Right about the problem. The answer is not forty words carrying a width each — it is one mode word. WIDTH belongs to the reckoner core beside FIX and DEG, and is validated once where it is set. The moment it existed, the width stopped being something the keyboard supplies.
"a stack has nowhere to put None()"It has { }. LOWBIT and HIGHBIT answer a LIST that is empty when no bit is set — one cell always, so UNDO's accounting holds, and it composes with LENGTH and FIRST. seedregex's RXGROUPS already had this shape.
"a product term has no Cell"True, and it never reaches the stack. MINTERMS, IMPLICANTS and TABLE answer bool's own string renderings, and a string is a Cell.
"BoolCard's glyphs are UTF-8 and file I/O is Latin-1"So CARD is built on BoolCardWith, which takes its glyphs as arguments precisely so a caller can pick ones that survive being written down, and picks ASCII. CARDWITH is there for a session that knows its terminal.

Still not bridged

These are boundaries rather than a to-do list.

Left outWhy
AND, OR, NOTThe engine core owns all three as Boolean words already. Bridging bool's would give two spellings for one idea, which is the rule seedmath applied to Rad and Deg.
BoolBitList, BoolFromBitListPOPCOUNT, BITAT, LOWBIT, HIGHBIT and CARD answer everything a keyboard asks about which bits are set. A list of indices is a programming intermediate.
BoolIsTautology, BoolEquiv, BoolRowOf and the rest that take a [ List Of Boolean -- Boolean ]A reckoner quotation is a token list and not a typed function value, so bridging them means the engine calling back into a user program with a typed argument — genuinely a design, and genuinely not this one. TABLE sidesteps it by taking the minterms and building the predicate inside the seed, where writing a function value is an ordinary thing to do.
BoolMinimiseWithMINTERMS covers the common case; the with-don't-cares form needs a second minterm list, and three list arguments at a prompt is where a bridge stops helping.

Who Uses It

UserHow
halifaxThe calculator's bases, bitwise, two's-complement and boolean words: HEX, BIN, BITAND, SHL, ROTL, SIGNED, ADDW, LOWBIT, CARD, GRAY, MINTERMS, XOR and the rest of the table.
sparkySparky folds it too, so a model calling eval reaches the same words halifax puts at a prompt.

A mill claims this seed by folding RckSeedBool over its reckoner state, which is all halifax does. tst/seedbooltest.shoddy is this seed's own suite. It grades every word above at the default width and at a narrow one, since a bridge that ignored the mode entirely would pass the first and fail the second. tst/seedtest.shoddy walks one refusal from each family alongside the other twelve seeds. It is a guard walk, deliberately not a catalogue.

The Machines It Uses

MachineWhy
boolThe domain this seed bridges: BoolHex, BoolBinS, BoolInBase, BoolFromBase, the bit operations, BoolMask, BoolGetField, BoolBitAt, BoolPopCount and the six gates.
cuttleThe Cell type every bridged word reads its arguments from and answers into.
reckonerRckReg, RckSeeding, RckWholeArg — which already refuses a negative and a fraction, two of bool's three domain rules — and RckBool2, which is the whole of each gate.
seqAppend, under the multi-argument reader that pulls several whole numbers off the stack at once and stops at the first that is not one.