Zulip Chat Archive
Stream: lean4
Topic: Implementing a tactic modifier
François G. Dorais (May 03 2022 at 18:23):
I was thinking about implementing a "tactic modifier" like clean tac
that would run the tactic tac
with some additional cleanup afterwards to get rid of common crud (e.g, superfluous lambdas, Nat.add
instead of +
, etc.). I got a partial implementation working using macros but I'm struggling with some desirable features. Here is the most desirable feature that I don't have a clue how to do.
Here's the base case macro I'm using
syntax (name := clean) "clean " tactic (location)? : tactic
macro_rules
| `(tactic| clean $tac:tactic $[$loc:location]?) => `(tactic| $tac; simp only [clean] $[$loc]?)
where clean
is a simp attribute for this task. (It doesn't matter what clean
does, so replace with simp only []
if you want to experiment on your own machine.)
The first issue that I'm running into is that I need to write clean rw [blah] at h at h
instead of just clean rw [blah] at h
where clean
would somehow detect that the rewrite has a location parameter and only clean at that location. Is there a way to do that?
Alex Keizer (May 03 2022 at 20:05):
What's happening is that the location parameter is "consumed" by clean
with a higher precedence, so clean rw[x] at h
is parsed as clean (rw[x]) at h
, i.e., $tac = rw[x]
. You want to somehow pass that location parameter onto the tactic you're executing.
Henrik Böving (May 03 2022 at 20:09):
I think they are aware of what is happening, the question is how to do this. I guess one could have parenthesis around the tactic and if a location modifier is passed to the clean
one also pass it to the parenthesized tactic.
Alex Keizer (May 03 2022 at 20:18):
One way is by having an explicit case for each tactic with a location you want to support, with the downside of it being a bit brittle, you have to duplicate the syntax of every such location-bearing tactic
Something like this?
open Lean.Parser.Tactic (location)
syntax (name := clean) "clean " tactic (location)? : tactic
macro_rules
| `(tactic| clean rw $[$cfg:config]? $rw:rwRuleSeq $[$loc:location]?) => `(tactic| rw $[$cfg]? $rw $[$loc]?; simp only [clean] $[$loc]?)
| `(tactic| clean $tac:tactic $[$loc:location]?) => `(tactic| $tac; simp only [clean] $[$loc]?)
François G. Dorais (May 04 2022 at 02:42):
It's actually the opposite: clean rw [blah] at h
parses as clean (rw [blah] at h)
Anyway, I got something working! (It's funny how often merely asking for help leads to a sudden breakthrough!)
syntax (name := clean) "clean " tactic (location)? : tactic
macro_rules
| `(tactic| clean $tac:tactic) => do
match tac.find? (λ s => s.getKind == ``location) with
| some loc => `(tactic| $tac; simp only [] $loc)
| none => `(tactic| $tac; simp only [])
| `(tactic| clean $tac:tactic $loc) => `(tactic| $tac; simp only [] $loc)
Right now this doesn't do exactly the right thing for clean (rw [foo] at h1; rw [bar] at h2)
because it only finds the location at h1
.
Is there an analogue of Syntax.find?
that returns a list of all occurrences? (I'll roll my own if necessary but I generally try to avoid yet another implementation of the wheel.)
The next issue is how to correctly unify locations. Maybe there's a neat trick for that?
François G. Dorais (May 04 2022 at 05:38):
I've solved it! Improvements are welcome!
-- could be a useful addition to Prelude.lean
partial def Lean.Syntax.filter (stx : Syntax) (p : Syntax → Bool) : Array Syntax :=
match stx with
| stx@(Syntax.node _ _ args) =>
let as := args.concatMap (λ stx => filter stx p)
if p stx then as.push stx else as
| stx => if p stx then #[stx] else #[]
-- could be a useful addition to Lean/Elab/Tactic/Location.lean
def Lean.Elab.Tactic.joinLocation : Location → Location → Location
| .wildcard, _ => .wildcard
| _, .wildcard => .wildcard
| .targets hs₁ g₁, .targets hs₂ g₂ => .targets (hs₁ ++ hs₂) (g₁ || g₂)
open Lean.Parser.Tactic (location)
open Lean.Elab.Tactic (Location expandLocation joinLocation)
syntax (name := clean) "clean " tactic (location)? : tactic
macro_rules
| `(tactic| clean $tac $[$loc]?) => do
let mut loc : Location := match loc with
| some loc => expandLocation loc
| none => .targets #[] false
for stx in tac.filter fun stx => stx.getKind == ``location do
loc := joinLocation loc (expandLocation stx)
match loc with
| .wildcard => `(tactic| $tac; simp only [clean] at *)
| .targets hs _ => `(tactic| $tac; simp only [clean] at $[$hs]* ⊢)
Last updated: Dec 20 2023 at 11:08 UTC