words

tape
import words from "words";

@tape { words.gen_lookup("Kw", ["break", "case", "const", "let", "if"]); }

fn statement(name: []const u8) {
    match (kw_of(name)) {
        Kw.Let => { declaration(); }
        Kw.Const => { declaration(); }
        Kw.If => { if_statement(); }
        Kw.None => { expression(); }
        _ => { other(); }
    }
}

words turns a fixed list of words, such as a lexer’s keywords, CSS property names or HTML tag names, into an enum and a function that finds a word’s variant. The table is built at compile time, so a lookup is a hash and a check of the word’s bytes, with no chain of comparisons and no allocation. It is pure and freestanding: every profile can use it.

gen_lookup

tape
@tape { words.gen_lookup("CssProp", ["color", "background-color", "z-index"]); }

This emits, in your module:

tape
pub enum CssProp : u16 { None; Color; BackgroundColor; ZIndex; }
pub fn css_prop_of(s: []const u8) -> CssProp;    // CssProp.None: not in the list
  • The enum. None comes first (0), then one variant per word, in list order.
  • Variant names. The word’s letters and digits, each run capitalized: break → Break, background-color → BackgroundColor, h1 → H1.
  • The function. The enum’s name in snake case followed by _of. Words compare byte for byte, so css_prop_of("Color") is None.

A list holds up to 4096 words of 1 to 64 bytes. An empty word, a longer one, or a word listed twice is a compile error.

gen_lookup_named

Words that make no name of their own, such as operators, take their names from a second list:

tape
@tape { words.gen_lookup_named("Op", ["==", "===", "!="], ["Eq", "StrictEq", "Ne"]); }

let op = op_of("===");          // Op.StrictEq

Speed

For 600 property names a lookup takes about 17 ns, about 33 times faster than testing them one by one with bytes.eq. The list compiles in under a tenth of a second.

Last modified: