Zulip Chat Archive

Stream: lean4

Topic: Multi commands macro


Patrick Massot (Jun 21 2023 at 07:54):

How can I define a macro that expands to a sequence of commands? Say I want #foo to be read as

#check Nat
#eval 1 + 1
def x := 0

Patrick Massot (Jun 21 2023 at 07:54):

Note my actual commands don't generate output.

Mario Carneiro (Jun 21 2023 at 08:55):

stop_at_first_error

Mario Carneiro (Jun 21 2023 at 08:56):

you can also use `(...) which is overloaded to work on commands

Mario Carneiro (Jun 21 2023 at 08:56):

macro "#foo" : command => `(
  #check Nat
  #eval 1 + 1
  def x := 0
)

#foo

Patrick Massot (Jun 21 2023 at 08:58):

Thanks! I was trying to be too specific and used `(command|...) instead of `(...)

Patrick Massot (Jun 21 2023 at 09:00):

While you're here, do you remember whether there was any discussion of having the ProofWidgets4 lib depend on Std?

Mario Carneiro (Jun 21 2023 at 09:00):

do you mean the other way around?

Patrick Massot (Jun 21 2023 at 09:01):

I mean I want to contribute something to ProofWidgets4 but I need something that is in Std.

Patrick Massot (Jun 21 2023 at 09:02):

And currently ProofWidgets4 does not require Std in its lakefile

Patrick Massot (Jun 21 2023 at 09:02):

which seems weird since I thought every Lean lib except for core and Std is meant to require Std.

Patrick Massot (Jun 21 2023 at 09:04):

The thing I need is

/-- Gets the LSP range from a `String.Range`. -/
def Lean.FileMap.utf8RangeToLspRange (text : FileMap) (range : String.Range) : Lsp.Range :=
  { start := text.utf8PosToLspPos range.start, «end» := text.utf8PosToLspPos range.stop }

/-- Gets the LSP range of syntax `stx`. -/
def Lean.FileMap.rangeOfStx? (text : FileMap) (stx : Syntax) : Option Lsp.Range :=
  text.utf8RangeToLspRange <$> stx.getRange?

which I think depend on nothing, so I could copy-paste, but then I would need to avoid the name clash for libs importing both Std and ProofWidgets4.

Mario Carneiro (Jun 21 2023 at 09:14):

Probably you should ask @Wojciech Nawrocki about that, I am not maintaining ProofWidgets

Mario Carneiro (Jun 21 2023 at 09:15):

It's definitely not the case that all Lean projects depend on Std; doc-gen, Qq, repl, and oleanparser are some lean projects off the top of my head that don't depend on std

Patrick Massot (Jun 21 2023 at 09:16):

I know you are not maintaining it, that's why I wrote "do you remember whether there was any discussion".

Patrick Massot (Jun 21 2023 at 09:16):

Do you think those lib try to be independent of Std or is this a historical accident?

Mario Carneiro (Jun 21 2023 at 09:16):

No I don't believe there has been any discussion

Mario Carneiro (Jun 21 2023 at 09:17):

Part of it might be that ProofWidgets might be aiming to be a dependency of Std

Mario Carneiro (Jun 21 2023 at 09:18):

although I don't think Std will be taking any dependencies, for user convenience, meaning that in that case Std would just absorb the whole library

Mario Carneiro (Jun 21 2023 at 09:18):

with the current state of things I think it would be fine to just have ProofWidgets depend on Std

Patrick Massot (Jun 21 2023 at 09:20):

Ok, we'll see what Wojciech say.

Patrick Massot (Jun 21 2023 at 09:21):

Actually the list of command thing doesn't work. Could you remind me how to ask Lean to show what macro expand to? Also stop_at_first_error is in Mathlib so I definitely cannot use it here.

Mario Carneiro (Jun 21 2023 at 09:23):

I don't know any good way to see macro expansions, other than finding them in the info tree (set_option trace.Elab.info true). @Sebastian Ullrich ?

Mario Carneiro (Jun 21 2023 at 09:25):

stop_at_first_error is very simple, it could be relocated to std. I don't know anything else that has no added behaviors; mutual ... end may work but it also has some extra stuff

Sebastian Ullrich (Jun 21 2023 at 09:44):

trace.Elab.step is the most direct way to debug macro expansion, though I don't know if the output is more helpful than Elab.info

Patrick Massot (Jun 21 2023 at 10:14):

I think I'm lost in nested macros. Is there a way to write something like

macro "mkMacro" name:str : command =>
`(macro $name : command => `(#check Nat))

do that mkMacro foo will define a macro foo expanding to #check Nat?

Patrick Massot (Jun 21 2023 at 10:15):

ie mkMacro foo would expand to macro "foo" : command => `(#check Nat)

Patrick Massot (Jun 21 2023 at 10:22):

To be clear, the part I don't know is what to put instead of $name on the second line.

Alex Keizer (Jun 21 2023 at 10:29):

Almost right, you just have to specify what category $name is.

macro "mkMacro" name:str : command =>
`(macro $name:str : command => `(#check Nat))

Patrick Massot (Jun 21 2023 at 10:33):

Thanks!

Patrick Massot (Jun 21 2023 at 10:34):

Next level of complexity is: what if instead of having name as an argument to my outside macro I compute a Lean.Name from other arguments. How can I turn that into a TSyntax `name?

Patrick Massot (Jun 21 2023 at 10:58):

Actually I have a hard time asking the right question. I have somewhere a function which looks like

open Lean Elab Tactic in
def foo (myName: Name) (myStr : String) (myOtherName : Name) (stx : Syntax) : TacticM Unit := sorry

and I want a macro like

macro "mkTactic" myStr:str myStr':str myFun:ident : command => do
  let newName : Name := .str `foo (myStr.getString ++ "_bar")
  sorry

such that mkTactic "baz" "Hello" myfun expands to elab stx:"baz" : tactic => foo `foo.baz_bar "Hello" `myfun stx

Alex Keizer (Jun 21 2023 at 11:01):

Names are usually turned into identifiers (ident) with the mkIdent function. But, you don't want an identifier, you want a string literal (that is what the str category means), so for that you need mkStrLit, which takes as argument a String object

Patrick Massot (Jun 21 2023 at 11:11):

I've tried endless random combinations of those functions without getting anywhere.

Patrick Massot (Jun 21 2023 at 11:11):

Could you try to do the example described above?

Patrick Massot (Jun 21 2023 at 11:12):

You can replace the body of foo with done for instance.

Mario Carneiro (Jun 21 2023 at 13:27):

import Lean

open Lean Elab Tactic
def foo (myName: Name) (myStr : String) (myOtherName : Name) (stx : Syntax) : TacticM Unit :=
  println! "called foo {myName} {myStr} {myOtherName} {stx}"

macro "mkTactic" myStr:str myStr':str myFun:ident : command => do
  let newName : Name := .str `foo (myStr.getString ++ "_bar")
  `(elab stx:$myStr:str : $(mkIdent `tactic) =>
      foo $(quote newName) $myStr':str $(quote myFun.getId) stx)

mkTactic "baz" "Hello" myfun

example : true := by
  baz -- called foo foo.baz_bar Hello myfun "baz"

Patrick Massot (Jun 21 2023 at 13:56):

Thanks a lot Mario! I was missing quote.


Last updated: Dec 20 2023 at 11:08 UTC