$NAME = arg; binds; $NAME substitutes. Single-pass: define before
use, no reassignment. Substitution is structural — refs share the
parsed action object, but actions are evaluated per occurrence (so
$r = rand(3); $r $r re-rolls each use).
Implementation: assign stores into PromptTransformer.vars and
returns None (filtered by tokenizer); ref returns the stored arg
wrapped in a transparent WeightedGroup(items, 1.0), so existing
_flatten and embedding_tensor code paths handle it without a new
container type.
NAME and WORD terminals overlap on bare identifiers; the earley
parser's dynamic lexer disambiguates by grammar context (the $
prefix forces NAME). Noted in grammar.py since this would break
under a basic/contextual lexer.
# comments run to end-of-line and are lexer-ignored.
Nine new tests cover top-level/arg-level/weighted substitution,
chaining, define-before-use error, reassignment error, comments,
and assign-only prompts.