From b57ddaf8dc4300c0f0b0f8ddd4e9e8f5c31caf14 Mon Sep 17 00:00:00 2001 From: Oleksandr Kozachuk Date: Fri, 17 Apr 2026 11:22:14 +0200 Subject: [PATCH] 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`. --- Justfile | 6 + tools/editor-support/README.md | 47 ++++++ tools/editor-support/bat/WAFER.sublime-syntax | 155 ++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 tools/editor-support/README.md create mode 100644 tools/editor-support/bat/WAFER.sublime-syntax diff --git a/Justfile b/Justfile index b4658a2..b03339f 100644 --- a/Justfile +++ b/Justfile @@ -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 diff --git a/tools/editor-support/README.md b/tools/editor-support/README.md new file mode 100644 index 0000000..3a3c3c4 --- /dev/null +++ b/tools/editor-support/README.md @@ -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. diff --git a/tools/editor-support/bat/WAFER.sublime-syntax b/tools/editor-support/bat/WAFER.sublime-syntax new file mode 100644 index 0000000..5997c3a --- /dev/null +++ b/tools/editor-support/bat/WAFER.sublime-syntax @@ -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