Zulip Chat Archive
Stream: new members
Topic: syntactic sugar for simple enumerated type definitions
Mark Aagaard (Feb 01 2025 at 20:12):
I'm teaching a first-year course on logic and discrete math to computer engineers. I'd like to enable the students to write simple enumerated type definitions such as:
Typedef Ty1 := A1 | A2
and have this elaborate to:
inductive Ty1 where | A1 | A2
open Ty1
The issues I'm facing are:
- Writing a macro for the
| A1 | A2
expansion. - Writing a macro that expands to 2 commands (
inductive
andopen
)
For the | A1 | A2
expansion, I started with the simple case of just one constructor, (e.g., Typedef Ty1 := A1
)
syntax "Typedef" ident " := " ident : command
macro_rules
| `( Typedef $id:ident := $val1:ident ) => `( inductive $id where | $val1 )
This gives a syntax error just after $val1
of expected token
. I get a similar message with multiple val
s.
Any suggestions on how to write this macro?
For the second issue, is it possible to write a macro that expands to 2 commands?
-mark
Aaron Liu (Feb 01 2025 at 20:23):
It looks like the parser is expecting some docs#Lean.Parser.Command.declModifiers. It works if you annotate it.
syntax "Typedef" ident " := " ident : command
macro_rules
| `( Typedef $id:ident := $val1:ident ) => `( inductive $id where | $val1:ident )
Kyle Miller (Feb 01 2025 at 20:27):
Mark Aagaard said:
For the second issue, is it possible to write a macro that expands to 2 commands?
Yes, you can put multiple commands into the `(...)
syntax.
Or you can do something like
macro_rules
| `( Typedef $id:ident := $val1:ident ) => do
let cmd1 ← `(inductive $id where | $val1:ident )
let cmd2 ← `(other command)
return mkNullNode #[cmd1, cmd2]
This has the same effect. Null nodes can be processed by the command elaborator.
Mark Aagaard (Feb 01 2025 at 20:35):
Thanks, I didn't know that it was possible to put a declModifier on the rhs of a macro expansion.
Aaron- How did you identify that the parser was looking for a declModifier
? Debugging macros is still more art than science for me.
-mark
Aaron Liu (Feb 01 2025 at 20:45):
Step 1
syntax "Typedef" ident " := " ident : command
macro_rules
| `( Typedef $id:ident := $val1:ident ) => `( inductive $id where | $val1 /- expected token -/)
Step 2
syntax "Typedef" ident " := " ident : command
macro_rules
| `( Typedef $id:ident := $val1:ident ) => `( inductive $id where | $val1 a /- i put a token -/)
Step 3
application type mismatch
val1.raw
argument
val1
has type
Lean.TSyntax `ident : Type
but is expected to have type
Lean.TSyntax `Lean.Parser.Command.declModifiers : Type
Mark Aagaard (Feb 01 2025 at 20:54):
Got it. Thanks!
Kyle Miller (Feb 01 2025 at 21:01):
Key tidbit: the lhs and rhs are both the same kind of syntax, known as syntax quotations. The quotations get interpreted in different ways depending on the context (e.g. as a pattern or as a value), but syntax quotations all have the same parsing rules.
Aaron Liu (Feb 01 2025 at 22:23):
Here's my attempt at making this.
import Lean
syntax "Typedef" ident ":=" sepBy(ident, "|") : command
macro_rules
| `(Typedef $id := $[$vals]|*) => do
let ctors ← vals.mapM fun val => `(Lean.Parser.Command.ctor| | $val:ident)
`(inductive $id where $[$ctors]* open $id:ident)
Kyle Miller (Feb 01 2025 at 22:39):
Here's how you do it with a single syntax quotation:
macro_rules
| `(Typedef $id:ident := $[$vals]|*) => do
`(inductive $id where
$[ | $vals:ident]*
open $id:ident)
Kyle Miller (Feb 01 2025 at 22:40):
The syntax
and macro_rules
can also be done together using the macro
command:
macro "Typedef" id:ident ":=" vals:sepBy(ident, "|") : command =>
`(inductive $id where
$[ | $vals:ident]*
open $id:ident)
Last updated: May 02 2025 at 03:31 UTC