Add bat syntax for WAFER / Forth 2012

Ship tools/editor-support/bat/WAFER.sublime-syntax so any bat user
(including oked, which probes bat first) renders .fth files with
proper keyword colouring, including the WAFER extras CONSOLIDATE,
RANDOM, RND-SEED, and UTIME.

Keyword list derives from register_primitive/register_host_primitive
calls in crates/core/src/outer.rs plus the boot.fth definitions.
Internal underscore-prefixed words are deliberately omitted.

Install with `just install-syntax`.
This commit is contained in:
2026-04-17 11:22:14 +02:00
parent be5dff243f
commit bcccdfb49d
3 changed files with 208 additions and 0 deletions
+6
View File
@@ -57,3 +57,9 @@ ci: fmt clippy deny test
# Check compilation without running
check:
cargo check --workspace
# Install bat syntax highlighting for WAFER / Forth
install-syntax:
mkdir -p ~/.config/bat/syntaxes
cp tools/editor-support/bat/WAFER.sublime-syntax ~/.config/bat/syntaxes/
bat cache --build
+47
View File
@@ -0,0 +1,47 @@
# Editor support for WAFER
Syntax highlighting assets for editors and pagers.
## bat (and other Sublime-Text-compatible tools)
`bat/WAFER.sublime-syntax` is a Sublime Text grammar covering Forth 2012 plus
WAFER-specific words (`CONSOLIDATE`, `RANDOM`, `RND-SEED`, `UTIME`).
### Install
```
just install-syntax
```
or manually:
```
mkdir -p ~/.config/bat/syntaxes
cp tools/editor-support/bat/WAFER.sublime-syntax ~/.config/bat/syntaxes/
bat cache --build
```
### Verify
```
bat --list-languages | grep -i forth # should list Forth
bat --language forth crates/core/boot.fth # should render with colour
```
### Use with `oked`
`oked` auto-detects `.fth` / `.4th` / `.forth` files and invokes `bat` with
`--language forth`. After the install step above, opening any WAFER source in
`oked` and toggling highlight (`H` command, or `oked -S forth`) will use this
syntax.
### Updating the keyword list
Primitives live in `crates/core/src/outer.rs` (`register_primitive` and
`register_host_primitive` calls). When a new **user-facing, non-standard** word
is added, append it to the `wafer_extras` context in
`bat/WAFER.sublime-syntax`. Standard Forth 2012 words are already covered by
the main contexts.
Internal symbols (names that start with `_`) should not be added — they are
implementation details that user code never types.
@@ -0,0 +1,155 @@
%YAML 1.2
---
# WAFER / Forth 2012 syntax for `bat` (and any Sublime Text compatible highlighter).
#
# Keyword list is derived from the primitives registered in
# crates/core/src/outer.rs plus the Forth 2012 core-ext wordset and the boot.fth
# definitions in crates/core/boot.fth. WAFER-specific additions are tagged below.
#
# Install: see tools/editor-support/README.md.
name: Forth
file_extensions:
- fth
- 4th
- forth
scope: source.forth
variables:
ident_break: '(?=\s|$)'
contexts:
main:
- include: comments
- include: strings
- include: numbers
- include: definitions
- include: control
- include: stack_ops
- include: return_stack
- include: arithmetic
- include: logic
- include: compare
- include: memory
- include: io
- include: float
- include: dictionary
- include: exception
- include: parsing
- include: literals
- include: wafer_extras
comments:
# Line comment: backslash to end of line, must be followed by whitespace or EOL.
- match: '(?i)(?:^|(?<=\s))\\(?=\s|$).*$'
scope: comment.line.backslash.forth
# Stack-effect / block comment: ( ... ) — the `(` must be followed by whitespace.
- match: '(?i)(?:^|(?<=\s))\((?=\s|$)'
scope: punctuation.definition.comment.forth
push:
- meta_scope: comment.block.paren.forth
- match: '\)'
scope: punctuation.definition.comment.forth
pop: true
# Immediate print comment: .( ... )
- match: '(?i)(?:^|(?<=\s))\.\((?=\s|$)'
scope: punctuation.definition.comment.forth
push:
- meta_scope: comment.block.dot-paren.forth
- match: '\)'
scope: punctuation.definition.comment.forth
pop: true
strings:
# Standard Forth strings: leading word followed by space then body, closed with ".
- match: '(?i)(?:^|(?<=\s))(S\\"|S"|C"|\."|ABORT")(\s)'
captures:
1: keyword.other.string-prefix.forth
push:
- meta_scope: string.quoted.double.forth
- match: '"'
pop: true
numbers:
# Hex / binary / decimal / char literals / negatives; all whitespace-delimited.
- match: '(?i)(?:^|(?<=\s))\$[0-9A-F]+{{ident_break}}'
scope: constant.numeric.hex.forth
- match: '(?i)(?:^|(?<=\s))#-?[0-9]+{{ident_break}}'
scope: constant.numeric.decimal.forth
- match: '(?i)(?:^|(?<=\s))%[01]+{{ident_break}}'
scope: constant.numeric.binary.forth
- match: "(?i)(?:^|(?<=\\s))'.'{{ident_break}}"
scope: constant.character.forth
- match: '(?i)(?:^|(?<=\s))-?[0-9]+(?:\.[0-9]*)?(?:[eE]-?[0-9]+)?{{ident_break}}'
scope: constant.numeric.forth
definitions:
- match: '(?i)(?:^|(?<=\s))(:|:NONAME)(\s+)(\S+)?'
captures:
1: keyword.other.definition.forth
3: entity.name.function.forth
- match: '(?i)(?:^|(?<=\s));{{ident_break}}'
scope: keyword.other.definition.forth
- match: '(?i)(?:^|(?<=\s))(VARIABLE|2VARIABLE|CONSTANT|2CONSTANT|VALUE|CREATE|DEFER|MARKER|BUFFER:|FCONSTANT|FVARIABLE)(\s+)(\S+)?'
captures:
1: keyword.other.defining.forth
3: entity.name.constant.forth
- match: '(?i)(?:^|(?<=\s))(DOES>|IMMEDIATE|RECURSE|POSTPONE|COMPILE,|LITERAL|2LITERAL|FLITERAL|SLITERAL){{ident_break}}'
scope: keyword.other.defining.forth
control:
- match: '(?i)(?:^|(?<=\s))(IF|THEN|ELSE|BEGIN|UNTIL|WHILE|REPEAT|AGAIN|DO|\?DO|LOOP|\+LOOP|LEAVE|UNLOOP|EXIT|CASE|OF|ENDOF|ENDCASE|QUIT){{ident_break}}'
scope: keyword.control.forth
stack_ops:
- match: '(?i)(?:^|(?<=\s))(DUP|\?DUP|DROP|SWAP|OVER|ROT|-ROT|NIP|TUCK|PICK|ROLL|2DUP|2DROP|2SWAP|2OVER|2ROT|DEPTH|SP@){{ident_break}}'
scope: support.function.stack.forth
return_stack:
- match: '(?i)(?:^|(?<=\s))(>R|R>|R@|2>R|2R>|2R@|N>R|NR>|I|J|CS-PICK|CS-ROLL){{ident_break}}'
scope: support.function.return-stack.forth
arithmetic:
- match: '(?i)(?:^|(?<=\s))(\+|-|\*|/|MOD|/MOD|\*/|\*/MOD|NEGATE|ABS|MIN|MAX|1\+|1-|2\*|2/|M\*|M\+|M\*/|UM\*|UM/MOD|FM/MOD|SM/REM|S>D|D>S){{ident_break}}'
scope: keyword.operator.arithmetic.forth
logic:
- match: '(?i)(?:^|(?<=\s))(AND|OR|XOR|INVERT|LSHIFT|RSHIFT){{ident_break}}'
scope: keyword.operator.logical.forth
compare:
- match: '(?i)(?:^|(?<=\s))(=|<>|<|>|<=|>=|U<|U>|0=|0<>|0<|0>){{ident_break}}'
scope: keyword.operator.comparison.forth
memory:
- match: '(?i)(?:^|(?<=\s))(@|!|C@|C!|\+!|2@|2!|ALLOT|HERE|ALIGN|ALIGNED|CELL\+|CELLS|CHAR\+|CHARS|UNUSED|MOVE|CMOVE|CMOVE>|FILL|ERASE|BLANK|ALLOCATE|FREE|RESIZE|PAD){{ident_break}}'
scope: support.function.memory.forth
io:
- match: '(?i)(?:^|(?<=\s))(EMIT|CR|SPACE|SPACES|TYPE|\.|U\.|\.R|U\.R|D\.|D\.R|\?|KEY|KEY\?|PAGE|AT-XY|ACCEPT|EXPECT|\.S){{ident_break}}'
scope: support.function.io.forth
float:
- match: '(?i)(?:^|(?<=\s))(F\+|F-|F\*|F/|FNEGATE|FABS|FMAX|FMIN|FSQRT|FFLOOR|FROUND|FSINCOS|F=|F<|F0=|F0<|F~|FDUP|FDROP|FSWAP|FOVER|FROT|FNIP|FTUCK|FDEPTH|F@|F!|FE\.|FS\.|F\.|F>D|D>F|F>S|S>F|>FLOAT|REPRESENT|PRECISION|SET-PRECISION|FALIGNED|DFALIGNED|SFALIGNED|DF@|DF!|SF@|SF!){{ident_break}}'
scope: support.function.float.forth
dictionary:
- match: "(?i)(?:^|(?<=\\s))('|\\[']|,|>BODY|FIND|WORDS|ONLY|ALSO|PREVIOUS|DEFINITIONS|FORTH|GET-ORDER|SET-ORDER|GET-CURRENT|SET-CURRENT|WORDLIST|SEARCH-WORDLIST|FORTH-WORDLIST|ENVIRONMENT\\?|EXECUTE){{ident_break}}"
scope: support.function.dictionary.forth
exception:
- match: '(?i)(?:^|(?<=\s))(CATCH|THROW|ABORT){{ident_break}}'
scope: keyword.control.exception.forth
parsing:
- match: '(?i)(?:^|(?<=\s))(PARSE|PARSE-NAME|WORD|REFILL|EVALUATE|SOURCE|SOURCE-ID|>IN|BASE|STATE|>NUMBER|SEARCH|SUBSTITUTE|UNESCAPE|REPLACES){{ident_break}}'
scope: support.function.parsing.forth
literals:
- match: '(?i)(?:^|(?<=\s))(TRUE|FALSE|BL|CHAR|\[CHAR\]|\[COMPILE\]){{ident_break}}'
scope: constant.language.forth
wafer_extras:
# WAFER-specific extensions beyond the Forth 2012 standard.
# When the language grows new user-facing non-standard words, add them here.
- match: '(?i)(?:^|(?<=\s))(CONSOLIDATE|RANDOM|RND-SEED|UTIME){{ident_break}}'
scope: support.function.wafer-extra.forth