Bounded vocabulary — the set of names the parser is allowed to intern as atoms.
More precisely: this is the set of source-text names the analyzer/evaluator currently pattern-matches as atom literals. Builtin function names + special forms + bounded namespaces + destructuring modifiers + qualified analyzer keys + short-fn param atoms. Everything else stays as a binary in the AST so the global atom table never grows from user input (issue #953).
Why an explicit allowlist instead of String.to_existing_atom/1:
the global VM atom table is non-deterministic — unrelated modules
loading later can change how the same source parses. Codex's
pushback on the bug thread covers this in detail.
What's in the table
- Every key of
PtcRunner.Lisp.Env.initial/0— all builtin functions (map,filter,+,str, etc.). - Analyzer special forms —
let,fn,def,if,case, etc. Only forms that the analyzer currently dispatches on. No aspirational Clojure entries. - Bounded keyword modifiers used by
for/doseq/destructuring —:else,:keys,:as,:or, etc. - Bounded namespaces —
data,tool,budget,json,mcp, plus Clojure aliases (clojure.string), and fully-qualified Java namespaces fromEnv.clojure_namespaces(java.time.LocalDate, etc.). - Qualified analyzer keys such as
serversand JSON member names matched as atom literals indispatch_list_formclauses. - Short-fn param atoms
:p1..:p20synthesized by the short-fn analyzer.
What's NOT in the table
User-defined names: var bindings from let, fn params, def
bindings, custom keywords like :my_kw, namespaced keys like
data/foo_42. These stay as binaries in the AST.
Cache
Table is built lazily on first call and cached in :persistent_term.
Read cost after first call is one :persistent_term.get/1 (no copy).
Summary
Functions
Returns the atom for name if it's in the bounded vocabulary,
otherwise returns the binary unchanged.
Returns the full lookup table — binary names → atoms.
Functions
Returns the atom for name if it's in the bounded vocabulary,
otherwise returns the binary unchanged.
This is the only function the parser should call to convert a source-text name into its AST representation.
Returns the full lookup table — binary names → atoms.
Cached in :persistent_term after first call.