oobabooga/textgen · warning · RuntimeError

expecting ::= at {remaining_src}

Error message

expecting ::= at {remaining_src}

What it means

Raised by parse_rule (modules/grammar/grammar_utils.py:268): after reading a rule name, the very next token must be the GBNF definition operator '::=' (within whitespace). A single '=', '==', ':=', or a missing operator entirely triggers this error, echoing the offending remainder.

Source

Thrown at modules/grammar/grammar_utils.py:268

    outbuf = []
    remaining_src = parse_sequence(state, src, rule_name, outbuf, is_nested)
    while remaining_src and remaining_src[0] == "|":
        remaining_src = remove_leading_white_space(remaining_src[1:], True)
        remaining_src = parse_sequence(state, remaining_src, rule_name, outbuf, is_nested)

    state.grammar_encoding.append(rule_id)
    state.grammar_encoding.extend(outbuf)
    state.grammar_encoding.append(0)
    return remaining_src


def parse_rule(state, src):
    name, remaining_src = parse_name(src)
    remaining_src = remove_leading_white_space(remaining_src, False)
    rule_id = get_symbol_id(state, name)

    if remaining_src[:3] != "::=":
        raise RuntimeError("expecting ::= at " + remaining_src)
    remaining_src = remove_leading_white_space(remaining_src[3:], True)

    remaining_src = parse_alternates(state, remaining_src, name, rule_id, False)

    if remaining_src and remaining_src[0] == "\r":
        remaining_src = remaining_src[2:] if remaining_src[1] == "\n" else remaining_src[1:]
    elif remaining_src and remaining_src[0] == "\n":
        remaining_src = remaining_src[1:]
    elif remaining_src:
        raise RuntimeError("expecting newline or end at " + remaining_src)
    return remove_leading_white_space(remaining_src, True)


def parse_ebnf(src):
    try:
        state = ParseState()
        grammar_repr = remove_leading_white_space(src, True)
        last_grammar_repr = ""

View on GitHub (pinned to ed888c71f2)

Solutions

  1. Use exactly '::=' between every rule name and its body (two colons, equals).
  2. Look at the echoed remaining_src to see the actual operator bytes — it exposes invisible characters or typos.
  3. If converting from another grammar format (Lark, EBNF), script the conversion of '=' to '::='.

Example fix

// before
root = "a" | "b"

// after
root ::= "a" | "b"
Defensive patterns

Strategy: validation

Validate before calling

import re

DEF_OP = re.compile(r'^\s*[A-Za-z0-9_-]+\s*::=')

def all_rules_use_gbnf_op(grammar: str) -> list[str]:
    bad = []
    for line in grammar.splitlines():
        s = line.strip()
        if s and not s.startswith('#') and not DEF_OP.match(line):
            bad.append(s.split('::=')[0].split('=')[0].strip() or s)
    return bad

Type guard

def is_gbnf_rule(line: str) -> bool:
    return bool(DEF_OP.match(line)) or line.strip().startswith('#') or not line.strip()

Try / catch

try:
    parse_grammar(grammar_text)
except RuntimeError as e:
    if '::=' in str(e):
        raise ValueError("Rules must use exactly '::=' (two colons then equals)") from None
    raise

Prevention

When it happens

Trigger: Grammar lines like 'root = "a"' (single '='), 'root := "a"', 'root ::= missing' handled elsewhere but 'root "a"' (operator omitted), or '::=' split by a stray comment/character.

Common situations: Writing BNF (which uses '::=' or '::='-variants like ':=='); mixing EBNF '=' style from tools like lodash-style generators or Lark grammars; typos and autocorrect mangling '::='; extra colons (':=:', '::==').

Related errors


AI-assisted analysis of oobabooga/textgen@ed888c71f2 (2026-08-15). Data as JSON: /api/errors/f616966ff23fa9e9. Report an issue: GitHub.