# BROADSWORD LANGUAGE SPECIFICATION (DRAFT) Status: working document. Sections below are drafted as of this 07/2026. Verification key used throughout: - **[CONFIRMED]** — Working at compile time as exists in the emitter. - **[DECLARED]** — Emitter backend exists but have not had time (or reason) to implement. - **[RESERVED]** — the word exists in the tokenizer's verb table; no emitter is implemented. Using it is a compile error until I have time to fix. --- ## 1. Overview Broadsword is inteded as a systems language built around one governing rule: Logic moves down. There is no nesting, no braces, no inline expressions. Every line is one instruction, every block is a named `STEP`. Reading a program top to bottom tells you exactly the order things will happen. This is not a stylistic preference layered on top of a conventional language, it is enforced by the compiler. `SEEK` may only target a `STEP` later in the source file. `REPEAT` may only target a `STEP` at, or before the current position. There is no `GOTO`- equivalent that can jump anywhere — the two motions available are "forward, once" and "backward, to repeat," - and nothing else. This mirrors the discipline informally practiced in well-written COBOL (where the convention was social, not enforced). A `STEP` is the unit of logic, one station on a belt. It looks at what's in front of it, does one action, and hands it off downward. A `STEP` needing many branches to decide what to do next is usually two things wearing one name, not evidence that more branching is needed. The compiler enforces a soft version of this: a `STEP` with more than three `WHEN` actions produces a warning (not a compile error) suggesting it may be doing more than one job. This is the social contract. Don't make me make it hard compiled. Broadsword has no recursion. Period. Ever. Now, or in the conventional sense, and no concurrency at all. `CALL`/`RETURN` nest properly (real stack frames), but there is no scheduler, no threads, and on constrained targets (embedded, kernel-shared-stack) call depth is a real, finite resource, not an abstraction. This could change based on systems testing, but I haven't seen any issues in my personal use so far. Please do let me know. Naming is deliberately human-scale. Verbs and variable names are meant to be read, not decoded — `DECODE_FIELD_PERCENT` over `df_p`. Long names are the expected style, not an exception. (Note: the current 20-character name-length ceiling was inherited from an embedded target [where broadsword started] and has already caused one silently-truncating bug. This limit as under active reconsideration.) Broadsword compiles to more than one target today. Linux/x86-64 is the primary, most complete target. But because its difficult to write two operating systems, and a language at the same time, I chose to focus on getting the compiler working for a well documented system first. See the per-verb target notes in Section 6. --- ## 2. Lexical Structure ### 2.1 Sigils Classification is driven entirely by the first one or two characters of a token. No lookahead beyond that is needed. | Sigil | Meaning | Example | |---|---|---| | `###` | Bulkhead — division marker, consumes the rest of the line as a single label | `### DATA DIVISION` | | `--` | Comment — rest of line discarded | `-- this explains something` | | `#` | Numeric literal — bare numbers are disallowed, and will result in compile errors | `#42`, `#-7`, `#0` | | `[...]` | Container — a variable, array reference, or step name | `[NAME]`, `[NUMS AT #3]` | | `"..."` | String literal, here is where bare numbers can be allowed | `"Hello"` | | bare word | Verb, keyword, condition, type, or structural word, resolved by lookup | `SUM`, `TO`, `EQUALS` | Bare words are case-insensitive on input (`sum`, `Sum`, `SUM` all classify identically) but are conventionally written uppercase. In the compiler backend, all verbs are forced UPPER CASE by the tokinizer byte by byte in the first place. So it doens't matter, its just visual to the human. This is under review. ### 2.2 The bare-number rule A bare word consisting entirely of digits is a **hard compile error**, not a warning: `BSC ERROR: bare number 'N' must use # sigil. write #N. This is deliberate. A naked number is far more likely to be a dropped sigil than an intentional bare word, so the compiler refuses rather than guesses. Every numeric literal, everywhere, must carry `#`. ### 2.3 String escapes Recognized inside `"..."` string literals: | Escape | Byte | Notes | |---|---|---| | `\n` | 0x0A | newline | | `\r` | 0x0D | carriage return | | `\t` | 0x09 | tab | | `\e` | 0x1B | ESC — used constantly for ANSI cursor/color control | | `\b` | 0x08 | backspace — for raw-mode line editing (`SAY "\b \b"` to erase one character) | | `\\` | 0x5C | literal backslash | | `\"` | 0x22 | literal double quote — **required** any time literal HTML/text containing `"` is embedded in a string; otherwise the string terminates early and the remainder tokenizes as unrelated bare words/numbers | A string literal is bounded by an unescaped `"` **or** by end of line — it cannot span multiple physical lines. In the case of HTML, being that html doens't care if '' is used or "" is used. It is necessary to use single quote '' because of the compilers string literal using "double quotes". ### 2.4 Fixed limits These are current compiler constants, not language-level guarantees. All under revision. | Limit | Value | Note | |---|---|---| | Max variable/step name length | 19 usable characters (`BS_NAME_LEN` = 20, includes null terminator) | Known too tight; caused a real silent-truncation bug when exceeded. Under reconsideration. | | Max tokens per source line | 16 (`BS_MAX_TOKENS`) | | | Max content per token | 127 usable bytes (`BS_TOK_LEN` = 128) | Applies to string literals too — a literal longer than this truncates silently at the tokenizer, independent of the destination variable's declared width. | | Max source line length | 255 usable bytes (`BS_LINE_LEN` = 256) | | | Max named `STEP`s per program | 32 (`BS_MAX_STEPS`) default | Every `STEP` costs one slot regardless of whether it's a true subroutine or just a branch target — write branchless arithmetic and shared `CALL`ed routines rather than one-off branch steps to conserve these. | | Max declared variables per program | 64 (`BS_MAX_VARS`) | | | Reserved memory (slabs) | 1024 slabs (16,384 bytes) by default; raise with `### MEMORY #N` | See §7 (not yet drafted) for the directive itself. | ### 2.5 Container addressing — a critical distinction '[Case Sensitive]' Variable names must be consistent throught. Variable names in the ### DATA DIVISION are case sensitive inside the tokenizer. `[NAME AT expr]` means two *different* things depending on what kind of variable `NAME` is, and confusing them causes silent, wrong addresses rather than a compile error: - For a **`NUMBER ARRAY`**, `AT` strides by 16 bytes per element (one full slab per array slot). This is the only correct use of bracketed `AT` indexing. - For **byte-level access into a `TEXT` variable**, bracketed `AT` is *wrong* — it will silently compute an address 16× too far. Byte-level positions (from `FIND`, from a running count, from percent-decoding) must use the dedicated `BYTE-READ`/`BYTE-WRITE` verbs (§6.4), which step one byte at a time by design. --- ## 6. Verb Reference Verbs are grouped by category. Hyphenated compound verbs (`FILE-OPEN`, `NET-SEND`, `BITS-AND`) are one atomic word each, never a namespace word plus a bare sub-command. This is a hard rule adopted after several real bugs: a bare sub-command word can silently collide with an unrelated verb of the same name elsewhere in the grammar (`WRITE` the verb vs. `WRITE` as a `FILE` sub-command; `SEEK` the flow-control verb vs. a hypothetical `FILE SEEK`). ### 6.1 Data movement | Verb | Syntax | Status | Notes | |---|---|---|---| | `COPY` | `COPY [SRC] TO [DEST]` or `COPY #lit TO [DEST]` | **[CONFIRMED]** | Moves exactly one slab (up to 8 usable bytes). **Does not copy whole `TEXT` buffers** — for a multi-byte `TEXT` copy, use `JOIN [EMPTYTEXT] AND [SRC] TO [DEST]` (concatenate onto a declared-empty string) instead. | | `WRITE` | `WRITE "literal"` \| `#lit` TO `[DEST]` | **[CONFIRMED]** | Writes a literal into a variable. Silently truncates if the literal exceeds `[DEST]`'s declared width, *and* separately, a literal longer than 127 bytes truncates at the tokenizer first regardless of destination size (§2.4). Neither truncation currently errors. | ### 6.2 Arithmetic | Verb | Syntax | Status | Notes | |---|---|---|---| | `SUM` | `SUM [A] AND [B]/#lit TO [DEST]` | **[CONFIRMED]** | `DEST = A + B` | | `DIFF` | `DIFF [A] AND [B]/#lit TO [DEST]` | **[CONFIRMED]** | `DEST = A - B` | | `TIMES` | `TIMES [A] BY [B]/#lit TO [DEST]` | **[CONFIRMED]** | `DEST = A * B` | | `DIVIDE` | `DIVIDE [A] BY [B]/#lit TO [DEST]` | **[CONFIRMED]** | Integer division, truncates toward zero for the ranges exercised so far. | | `BITS-AND` | `BITS-AND [A] WITH [B]/#lit TO [DEST]` | **[CONFIRMED]** | | | `BITS-OR` | `BITS-OR [A] WITH [B]/#lit TO [DEST]` | **[CONFIRMED]** | | | `BITS-XOR` | `BITS-XOR [A] WITH [B]/#lit TO [DEST]` | **[CONFIRMED]** | | | `BITS-NOT` | `BITS-NOT [A] TO [DEST]` | **[CONFIRMED]** | Unary — no `WITH` clause. | Branchless idioms worth knowing rather than reaching for `WHEN`: hex nibble from an ASCII digit is `(byte AND 15) + (byte DIVIDE 64) * 9`; `a MOD n` (non-negative operands) is `a - (a DIVIDE n) * n`. ### 6.3 Text | Verb | Syntax | Status | Notes | |---|---|---|---| | `JOIN` | `JOIN [A] AND [B] TO [DEST]` | **[CONFIRMED]** | Concatenation. Both operands must be `[VAR]` — a bare string literal is rejected (`JOIN "" AND ...` is a compile error). To introduce literal empty text, `WRITE "" TO [EMPTYTEXT]` once, then `JOIN [EMPTYTEXT] AND ...`. Safe to use the destination as one of the source operands (`JOIN [X] AND [Y] TO [X]`) — proven, in-place accumulation. | | `TRIM` | `TRIM [VAR]` | **[DECLARED]** | Exact trimming behavior (leading/trailing/both) not re-confirmed this session. | | `FIND` | `FIND "pat"/[VAR] IN [TEXT] TO [DEST]` | **[CONFIRMED]** | Returns a position. Exact not-found sentinel value not confirmed this session — prefer a direct `BYTE-READ` comparison over relying on `FIND`'s not-found behavior until this is verified. | | `SPLIT` | `SPLIT [SRC] AT [START] TO [DEST]` or `SPLIT [SRC] FROM [START] UNTIL [STOP] TO [DEST]` | **[CONFIRMED]** | Plain form copies from `START` to `SRC`'s own terminator. Bounded form copies only the span between `START` and `STOP`. (The bounded form previously used `AT ... END ...` — renamed to `FROM ... UNTIL ...` to stop overloading the structural keyword `END`.) | | `LENGTH` | `LENGTH [VAR] TO [DEST]` | **[DECLARED]** | Exact syntax pattern inferred from usage elsewhere, not independently re-confirmed. | | `CONVERT` | `CONVERT [NUMBER] TO [DEST]` | **[CONFIRMED]** | Converts a number to its decimal text representation. No zero-padding control — for fixed-width digit formatting (e.g. `MM`/`DD` in a date), build digits manually via `DIVIDE`/`TIMES`/`DIFF` and `BYTE-WRITE`, as done in the guestbook's date routine. | ### 6.4 Byte-level access | Verb | Syntax | Status | Notes | |---|---|---|---| | `BYTE-READ` | `BYTE-READ [TEXT] AT [I]/#lit TO [DEST]` | **[CONFIRMED]** | Single-byte read, stride of 1. Not the same code path as array `AT` indexing (§2.5) — do not confuse the two. | | `BYTE-WRITE` | `BYTE-WRITE [VAL]/#lit TO [TEXT] AT [I]/#lit` | **[CONFIRMED]** | Single-byte write, stride of 1. | ### 6.5 Control flow | Verb | Syntax | Status | Notes | |---|---|---|---| | `STEP ... DONE` | `STEP [NAME] ... DONE` | **[CONFIRMED]** | The unit of logic. First `STEP` in the file is the program's entry point. | | `WHEN` | `WHEN [VAR] cond [operand] ACTION target` | **[CONFIRMED]** | `cond` is one of `EQUALS`, `OVER`, `UNDER` (two-operand — take an `[VAR]`/`#lit` right after) or `ZERO`, `BLANK` (one-operand — take *no* trailing operand at all). **Known grammar inconsistency, flagged but not yet resolved:** these two shapes coexist; a form like `WHEN [X] EQUALS #0` and `WHEN [X] ZERO` are functionally equivalent but grammatically dissimilar. `ACTION` is `SEEK`, `REPEAT`, or `CALL`. | | `SEEK` | `SEEK [STEP]` | **[CONFIRMED]** | Unconditional jump. **Target must be later in the source file than the current `STEP`** — enforced at compile time. | | `REPEAT` | `REPEAT [STEP]` | **[CONFIRMED]** | Unconditional jump, the mirror of `SEEK`. **Target must be at or before the current `STEP`'s position.** The only sanctioned way to move backward — there is no general-purpose backward `SEEK`. | | `CALL` | `CALL [STEP]` | **[CONFIRMED]** | Real subroutine call (hardware `call`), no directional restriction — a `CALL` target may be anywhere in the file, since it always returns. | | `RETURN` | `RETURN` | **[CONFIRMED]** | Exits the current `STEP` back to its caller. Only meaningful in a `STEP` reached via `CALL`. | A `STEP` with more than three `WHEN`-driven branches (`SEEK`/`REPEAT` actions) produces a compiler warning, not an error, suggesting the `STEP` may be doing more than one job. ### 6.6 Input/output | Verb | Syntax | Status | Notes | |---|---|---|---| | `SAY` | `SAY "literal"` \| `[VAR]` | **[CONFIRMED]** | Prints text or a variable's current content. Multiple arguments on one line are supported (concatenated in output). | | `HEAR` | `HEAR [DEST]` | **[CONFIRMED]** | In cooked terminal mode, reads up to `[DEST]`'s declared width in one call — behaves like a line read only insofar as the OS buffers a line before making it available. In raw mode (after `TERM-ENABLE`), returns exactly one byte per call regardless of `[DEST]`'s declared width — **`[DEST]` should be declared `TEXT #1` for raw-mode reads** to avoid a multi-byte declared width silently swallowing more than one keystroke into a single `HEAR` call. | ### 6.7 File All require Linux; target availability on Neptune varies per verb, see notes. | Verb | Syntax | Status | Notes | |---|---|---|---| | `FILE-OPEN` | `FILE-OPEN [PATH] TO [HANDLE]` \| `FILE-OPEN [PATH] WRITE TO [HANDLE]` \| `FILE-OPEN [PATH] APPEND TO [HANDLE]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune — needs async request/poll bridging not yet built. | | `FILE-READ` | `FILE-READ [HANDLE] TO [DEST]` | **[CONFIRMED]** on Linux. Byte count lands in `SLAB_ERROR`, readable via `STATUS`. **[RESERVED]** on Neptune. | | `FILE-WRITE` | `FILE-WRITE [SRC] TO [HANDLE]` | **[CONFIRMED]** on Linux. Writes up to `[SRC]`'s first NUL byte (`strlen`-based) — **not** a fixed-width write. **[RESERVED]** on Neptune. | | `FILE-CLOSE` | `FILE-CLOSE [HANDLE]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | | `FILE-EXISTS` | `FILE-EXISTS [PATH] TO [DEST]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | | `FILE-STAT` | `FILE-STAT [PATH] TO [DEST]` | **[CONFIRMED]** on Linux — returns size. **[CONFIRMED]** on Neptune — Neptune's file index is memory-resident from boot, so this is synchronous with no polling needed, unlike every other `FILE-*` verb on that target. | | `FILE-SIZE` | `FILE-SIZE [HANDLE] TO [DEST]` | **[CONFIRMED]** on Linux. **[CONFIRMED]** on Neptune (same underlying call as `FILE-STAT`, different field). | | `FILE-MODE` | `FILE-MODE [PATH] TO [DEST]` | **[CONFIRMED]** on Linux — raw `st_mode` bits; test specific bits with `BITS-AND`. **[RESERVED]** on Neptune. | | `FILE-DEL` | `FILE-DEL [PATH]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | ### 6.8 Directory **Not yet hyphenated — see the note at the top of §6.** | Verb | Syntax | Status | Notes | |---|---|---|---| | `DIR MAKE` | `DIR MAKE [PATH]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | | `DIR DEL` | `DIR DEL [PATH]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | | `DIR LIST` | `DIR LIST [PATH] TO [DEST]` | **[CONFIRMED]** on Linux. **[RESERVED]** on Neptune. | ### 6.9 Network All **[RESERVED]** on Neptune currently — the underlying network stack is running and DHCP-connected at boot, but no syscall bridges a running Broadsword program to it yet. | Verb | Syntax | Status | Notes | |---|---|---|---| | `NET-LISTEN` | `NET-LISTEN #port TO [HANDLE]` | **[CONFIRMED]** on Linux. Issues `socket`+`setsockopt`(`SO_REUSEADDR`)+`bind`+`listen` internally. | | `NET-ACCEPT` | `NET-ACCEPT [HANDLE] TO [CONN]` | **[CONFIRMED]** on Linux. | | `NET-CONN` | `NET-CONN [IP] #port TO [HANDLE]` | **[CONFIRMED]** on Linux. `[IP]` is a packed `NUMBER`, typically produced by `NET-RESOLVE`. | | `NET-SEND` | `NET-SEND [SRC] TO [CONN]` | **[CONFIRMED]** on Linux. | | `NET-RECV` | `NET-RECV [CONN] TO [DEST]` | **[CONFIRMED]** on Linux. Byte count via `STATUS`. | | `NET-CLOSE` | `NET-CLOSE [CONN]` | **[CONFIRMED]** on Linux. | | `NET-RESOLVE` | `NET-RESOLVE [HOSTNAME] TO [IP]` | **[CONFIRMED]** on Linux. Hand-rolled UDP DNS query/response, not a libc resolver call. | ### 6.10 Screen (Neptune only) No Linux equivalent — these verbs are permanently `[RESERVED]` on the Linux target, not a temporary gap. | Verb | Syntax | Status | Notes | |---|---|---|---| | `SCREEN-FILL` | `SCREEN-FILL [X] [Y] [W] [H] [COLOR]` | **[CONFIRMED]** on Neptune. | | `SCREEN-STRING` | `SCREEN-STRING [X] [Y] "text" [COLOR]` | **[CONFIRMED]** on Neptune. | ### 6.11 Terminal Linux only — raw/cooked mode switching has no meaning on Neptune's input model. | Verb | Syntax | Status | Notes | |---|---|---|---| | `TERM-ENABLE` | `TERM-ENABLE TO [DEST]` | **[CONFIRMED]** on Linux. Saves current terminal settings into `[DEST]` (declare `TEXT #64` or larger), switches to raw mode (no line buffering, no echo). Every program-exit path must call `TERM-RESTORE` before ending, or the launching terminal is left broken. | | `TERM-RESTORE` | `TERM-RESTORE FROM [SAVED]` | **[CONFIRMED]** on Linux. Restores exactly the settings `TERM-ENABLE` saved. | ### 6.12 Time | Verb | Syntax | Status | Notes | |---|---|---|---| | `TIMESTAMP` | `TIMESTAMP TO [DEST]` | **[CONFIRMED]** on Linux — raw Unix epoch seconds, no calendar decomposition built into the verb itself. **[CONFIRMED]** on Neptune (`SYS_TIME_NOW`). | | `TIME` / `DATE` | `SAY TIME` / `SAY DATE` | **[DECLARED]**, known incomplete — prints the raw epoch value, not a decomposed clock/calendar string. A full Unix-timestamp-to-calendar-date routine exists as ordinary application code (see the guestbook's `CONVERT_DATE` station) rather than as compiler-level support. | ### 6.13 System / miscellaneous | Verb | Syntax | Status | Notes | |---|---|---|---| | `STATUS` | `STATUS [DEST]` | **[CONFIRMED]** | Copies the reserved error/result slab into `[DEST]` — the standard way to read a byte count or result code after `FILE-READ`, `NET-RECV`, `NET-CONN`, etc. | | `POLL` | `POLL [HANDLE] TO [DEST]` | **[CONFIRMED]** on Linux — blocks until either `[HANDLE]` or stdin has data, returns a bitmask (`1`=handle ready, `2`=stdin ready, `3`=both). **[RESERVED]** on Neptune (no scheduler; single foreground program). | | `WAIT` | `WAIT #lit` / `WAIT [VAR]` (with `MS`/`SECONDS` etc.) | **[DECLARED]** — real emitter exists (`nanosleep`), not re-confirmed this session. **[RESERVED]** on Neptune. | | `RND` | (syntax not re-confirmed this session) | **[DECLARED]** — real emitter exists (`getrandom`). **[RESERVED]** on Neptune. | | `STOP` | `STOP` | **[CONFIRMED]** on Linux (`exit`). **[RESERVED]** on Neptune — deliberate: a Neptune program ends via a plain `ret`, not an exit syscall. | | `CONFIG` | — | **[RESERVED]** — verb exists in the token table, no emitter confirmed. | | `LOG` | — | **[RESERVED]** | | `LAUNCH` | — | **[RESERVED]** — no `fork`/`exec`/`wait` support exists; spawning a second process is not currently possible in Broadsword. | | `SENSE`, `TOGGLE`, `SIGNAL`, `APPLY` | — | **[RESERVED]** — hardware-control verbs, present in the token table from the language's embedded-target origins, no emitter confirmed on any current target. | | `GLYPH`, `PIXEL`, `SCROLL`, `ERASE`, `DRAW`, `RENDER` | — | **[RESERVED]**, status genuinely unclear — these may have real emitters from earlier embedded work not exercised this session. Verify before relying on documented syntax for any of these; do not assume the shape based on naming similarity to `SCREEN-FILL`/`SCREEN-STRING`. | | `FILL`, `SWAP`, `MOVE`, `REST` | — | **[RESERVED]**, same caveat as above. | --- *Sections 3 (Program Structure), 4 (Data Types), 5 (already partially covered under Control Flow above, needs its own full pass), 7 (Compiler Directives), 8 (Limits — partially covered in §2.4, needs consolidation), and 9 (Portability Notes) remain to be drafted.*