Zulip Chat Archive
Stream: metaprogramming / tactics
Topic: conflicting with Lean keywords
Arthur Paulino (Feb 19 2026 at 22:58):
I have a DSL implemented in Lean, with a custom elaborator.
But Lean's syntax is interfering with it. For example, I can't have a variable called Type because Lean complains "unexpected token 'Type'" ("Type" glows blue).
Is there a way to disable Lean's syntax within my own syntactical scope?
Robin Arnez (Feb 19 2026 at 23:41):
You can write a docs#Lean.Parser.Parser but this is a bit hacky:
import Lean
declare_syntax_cat myCat
open Lean Parser
def myCatParser : Parser := withFn (p := categoryParser `myCat 0) <|
adaptUncacheableContextFn fun ctx =>
-- this makes "bad", "good" and "test" the only tokens
let table : TokenTable := ["bad", "good", "test"].foldl (fun acc tk => acc.insert tk tk) {}
{ ctx with tokens := table }
elab "#test_me " stx:myCatParser : command => do
Lean.logInfo stx
-- you can simply use Lean keywords in identifiers
syntax "test " ident : myCat
#test_me test Type
#test_me test let
#test_me test def
-- but oh no: it doesn't update the token table so
-- `term` is left with only the three tokens (see below)
syntax "bad " term : myCat
-- so if you want to transition, make sure to restore the token table
def restoreTermParser : Parser := withFn (p := termParser) <|
adaptUncacheableContextFn fun ctx =>
{ ctx with tokens := getTokenTable ctx.env }
syntax "good " restoreTermParser : myCat
#test_me bad let a 5 Type oh no _ stop sorry do by -- interpreted as an application
#test_me good let a := 5; 5 -- correctly interpreted as let
Robin Arnez (Feb 19 2026 at 23:45):
You only need to use these special parsers on transition; and if you don't want to hardcode the token table, you can retrieve it from the environment:
import Lean
declare_syntax_cat myCat
open Lean Parser
def collectTokens (env : Environment) (cat : Name) (table : TokenTable) : TokenTable :=
let state := parserExtension.getState env
let cat := state.categories.find! cat
let table := cat.tables.leadingParsers.foldl (init := table) fun acc (p, _) =>
(p.info.collectTokens []).foldl (fun acc tk => acc.insert tk tk) acc
let table := cat.tables.trailingParsers.foldl (init := table) fun acc (p, _) =>
(p.info.collectTokens []).foldl (fun acc tk => acc.insert tk tk) acc
let table := cat.tables.leadingTable.foldl (init := table) fun acc _ p =>
p.foldl (init := acc) fun acc (p, _) =>
(p.info.collectTokens []).foldl (fun acc tk => acc.insert tk tk) acc
let table := cat.tables.trailingTable.foldl (init := table) fun acc _ p =>
p.foldl (init := acc) fun acc (p, _) =>
(p.info.collectTokens []).foldl (fun acc tk => acc.insert tk tk) acc
table
def myCatParser : Parser := withFn (p := categoryParser `myCat 0) <|
adaptUncacheableContextFn fun ctx =>
let table : TokenTable := {}
let table : TokenTable := collectTokens ctx.env `myCat table
--let table : TokenTable := collectTokens ctx.env `myOtherCat table -- for any nested categories
{ ctx with tokens := table }
-- perfectly fine, since the token table is already updated
syntax myCat " + " myCat : myCat
...
Arthur Paulino (Feb 20 2026 at 01:22):
Thanks a lot for your answer!
So there's no principled solution to this issue
Robin Arnez (Feb 20 2026 at 21:27):
Yeah, parsing is currently not build to support multiple token tables
Last updated: Feb 28 2026 at 14:05 UTC