Skip to content

Commit 8e20b60

Browse files
committed
feat: run_macro & run_cmd_macro
1 parent c1d145e commit 8e20b60

File tree

4 files changed

+91
-32
lines changed

4 files changed

+91
-32
lines changed

src/Init/Notation.lean

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,21 @@ This is the same as `#eval show MetaM Unit from do discard doSeq`.
619619
-/
620620
syntax (name := runMeta) "run_meta " doSeq : command
621621

622+
/--
623+
The `run_macro doSeq` command executes code in `MacroM Unit`.
624+
This is the same as `#eval show MacroM Unit from do discard doSeq`.
625+
-/
626+
syntax (name := runMacro) "run_macro " doSeq : command
627+
628+
/--
629+
The `run_cmd_macro doSeq` command executes code in `MacroM Command`.
630+
The produced command is then immediately elaborated.
631+
632+
This effectively the same as `run_cmd elabCommand (← do doSeq)`,
633+
but this works without any imports.
634+
-/
635+
syntax (name := runCmdMacro) "run_cmd_macro " doSeq : command
636+
622637
set_option linter.missingDocs false in
623638
syntax guardMsgsFilterSeverity := &"info" <|> &"warning" <|> &"error" <|> &"all"
624639

src/Lean/Elab/BuiltinEvalCommand.lean

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ builtin_initialize
3737
/--
3838
Elaborates the term, ensuring the result has no expression metavariables.
3939
If there would be unsolved-for metavariables, tries hinting that the resulting type
40-
is a monadic value with the `CommandElabM`, `TermElabM`, or `IO` monads.
40+
is a monadic value with the `CommandElabM`, `TermElabM`, `MarcoM`, or `IO` monads.
4141
Throws errors if the term is a proof or a type, but lifts props to `Bool` using `mkDecide`.
4242
-/
4343
private def elabTermForEval (term : Syntax) (expectedType? : Option Expr) : TermElabM Expr := do
@@ -63,7 +63,7 @@ where
6363
catch ex =>
6464
let exS ← saveState
6565
-- Try hinting that `ty` is a monad application.
66-
for m in #[``CommandElabM, ``TermElabM, ``IO] do
66+
for m in #[``CommandElabM, ``TermElabM, ``MacroM, ``IO] do
6767
s.restore true
6868
try
6969
if ← isDefEq ty (← mkFreshMonadApp m) then
@@ -159,8 +159,34 @@ private structure EvalAction where
159159
If `some`, the expression is what type to use for the type ascription when `pp.type` is true. -/
160160
printVal : Option Expr
161161

162-
unsafe def elabEvalCoreUnsafe (bang : Bool) (tk term : Syntax) (expectedType? : Option Expr) : CommandElabM Unit := withRef tk do
162+
def elabEvalCoreAux (tk term : Syntax) (expectedType? : Option Expr)
163+
(mkAct : Name → Expr → TermElabM EvalAction) : CommandElabM Unit := withRef tk do
163164
let declName := `_eval
165+
let (output, exOrRes) ← IO.FS.withIsolatedStreams do
166+
try
167+
-- Generate an action without executing it. We use `withoutModifyingEnv` to ensure
168+
-- we don't pollute the environment with auxiliary declarations.
169+
let act : EvalAction ← liftTermElabM do Term.withDeclName declName do withoutModifyingEnv do
170+
let e ← elabTermForEval term expectedType?
171+
-- If there is an elaboration error, don't evaluate!
172+
if e.hasSyntheticSorry then throwAbortTerm
173+
mkAct declName e
174+
let res ← act.eval
175+
return Sum.inr (res, act.printVal)
176+
catch ex =>
177+
return Sum.inl ex
178+
if !output.isEmpty then logInfoAt tk output
179+
match exOrRes with
180+
| .inl ex => logException ex
181+
| .inr (_, none) => pure ()
182+
| .inr (res, some type) =>
183+
if eval.type.get (← getOptions) then
184+
logInfo m!"{res} : {type}"
185+
else
186+
logInfo res
187+
188+
unsafe def elabEvalCoreUnsafe (bang : Bool) (tk term : Syntax) (expectedType? : Option Expr) : CommandElabM Unit := do
189+
elabEvalCoreAux tk term expectedType? fun declName e => do
164190
-- `t` is either `MessageData` or `Format`, and `mkT` is for synthesizing an expression that yields a `t`.
165191
-- The `toMessageData` function adapts `t` to `MessageData`.
166192
let mkAct {t : Type} [Inhabited t] (toMessageData : t → MessageData) (mkT : Expr → MetaM Expr) (e : Expr) : TermElabM EvalAction := do
@@ -185,8 +211,8 @@ unsafe def elabEvalCoreUnsafe (bang : Bool) (tk term : Syntax) (expectedType? :
185211
if let some act ← mkMAct? ``CommandElabM CommandElabM e
186212
-- Fallbacks in case we are in the Lean package but don't have `CommandElabM` yet
187213
<||> mkMAct? ``TermElabM TermElabM e <||> mkMAct? ``MetaM MetaM e <||> mkMAct? ``CoreM CoreM e
188-
-- Fallback in case nothing is imported
189-
<||> mkMAct? ``IO IO e then
214+
-- Fallbacks in case nothing is imported
215+
<||> mkMAct? ``MacroM MacroM e <||> mkMAct? ``IO IO e then
190216
return act
191217
else
192218
-- Otherwise, assume this is a pure value.
@@ -208,33 +234,12 @@ unsafe def elabEvalCoreUnsafe (bang : Bool) (tk term : Syntax) (expectedType? :
208234
-- `evalConst` may emit IO, but this is collected by `withIsolatedStreams` below.
209235
let r ← toMessageData <$> evalConst t declName
210236
return { eval := pure r, printVal := some (← inferType e) }
211-
let (output, exOrRes) ← IO.FS.withIsolatedStreams do
212-
try
213-
-- Generate an action without executing it. We use `withoutModifyingEnv` to ensure
214-
-- we don't pollute the environment with auxiliary declarations.
215-
let act : EvalAction ← liftTermElabM do Term.withDeclName declName do withoutModifyingEnv do
216-
let e ← elabTermForEval term expectedType?
217-
-- If there is an elaboration error, don't evaluate!
218-
if e.hasSyntheticSorry then throwAbortTerm
219-
-- We want `#eval` to work even in the core library, so if `ofFormat` isn't available,
220-
-- we fall back on a `Format`-based approach.
221-
if (← getEnv).contains ``Lean.MessageData.ofFormat then
222-
mkAct id (mkMessageData ·) e
223-
else
224-
mkAct Lean.MessageData.ofFormat (mkFormat ·) e
225-
let res ← act.eval
226-
return Sum.inr (res, act.printVal)
227-
catch ex =>
228-
return Sum.inl ex
229-
if !output.isEmpty then logInfoAt tk output
230-
match exOrRes with
231-
| .inl ex => logException ex
232-
| .inr (_, none) => pure ()
233-
| .inr (res, some type) =>
234-
if eval.type.get (← getOptions) then
235-
logInfo m!"{res} : {type}"
236-
else
237-
logInfo res
237+
-- We want `#eval` to work even in the core library, so if `ofFormat` isn't available,
238+
-- we fall back on a `Format`-based approach.
239+
if (← getEnv).contains ``Lean.MessageData.ofFormat then
240+
mkAct id (mkMessageData ·) e
241+
else
242+
mkAct Lean.MessageData.ofFormat (mkFormat ·) e
238243

239244
@[implemented_by elabEvalCoreUnsafe]
240245
opaque elabEvalCore (bang : Bool) (tk term : Syntax) (expectedType? : Option Expr) : CommandElabM Unit
@@ -274,4 +279,23 @@ def elabRunMeta : CommandElab := fun stx =>
274279
elabEvalCore false tk (← `(discard do $elems)) (mkApp (mkConst ``MetaM) (mkConst ``Unit))
275280
| _ => throwUnsupportedSyntax
276281

282+
@[builtin_command_elab runMacro]
283+
def elabRunMacro : CommandElab := fun stx =>
284+
match stx with
285+
| `(run_macro%$tk $elems:doSeq) => do
286+
elabEvalCore false tk (← `(discard do $elems)) (mkApp (mkConst ``MacroM) (mkConst ``Unit))
287+
| _ => throwUnsupportedSyntax
288+
289+
@[builtin_command_elab runCmdMacro]
290+
def elabRunCmdMacro : CommandElab
291+
| `(run_cmd_macro%$tk $elems:doSeq) => do
292+
let mkAct declName e : TermElabM EvalAction := do
293+
addAndCompileExprForEval declName e
294+
-- Safety: `e` has the (expected) type `MacroM Command`,
295+
-- which we are assuming is defined appropriately in the environment.
296+
let mf : MacroM Command ← unsafe evalConst (MacroM Command) declName
297+
return { eval := do elabCommand =<< MonadEvalT.monadEval mf; pure "", printVal := none }
298+
elabEvalCoreAux tk (← `(do $elems)) (mkApp (mkConst ``MacroM) (mkConst ``Command)) mkAct
299+
| _ => throwUnsupportedSyntax
300+
277301
end Lean.Elab.Command

src/Lean/Elab/Command.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ instance : MonadRecDepth CommandElabM where
436436
getRecDepth := return (← read).currRecDepth
437437
getMaxRecDepth := return (← get).maxRecDepth
438438

439+
instance : MonadEval MacroM CommandElabM where
440+
monadEval := liftMacroM
441+
439442
builtin_initialize registerTraceClass `Elab.command
440443

441444
open Language in

tests/lean/run/run_macro.lean

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Test
2+
3+
open Lean
4+
5+
/-- info: `Test -/
6+
#guard_msgs in
7+
#eval Macro.getCurrNamespace
8+
9+
/-- error: thrown -/
10+
#guard_msgs in
11+
run_macro Macro.throwError (α := Unit) "thrown"
12+
13+
run_cmd_macro (TSyntax.mk <| mkNullNode ·) <$> #[`foo, `bar].mapM fun name =>
14+
`(def $(mkIdent name) := $(quote name.toString))
15+
16+
/-- info: "foobar" -/
17+
#guard_msgs in #eval foo ++ bar

0 commit comments

Comments
 (0)