Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: Expressing parsing


Patrick Massot (Jan 15 2022 at 14:51):

Do we have a nice way to parse an expression analogous to how we parse tactic arguments? Or are we meant to use nested matches on the expr constructors?

Patrick Massot (Jan 15 2022 at 14:52):

I have about a dozen shapes of expressions I'd like to recognize and be able to act depending on which shape was recognized and what were the components.

Patrick Massot (Jan 15 2022 at 14:53):

For instance if I have ∀ k, ∃ n, P n k as input then I would like to get : this is forall_exists_prop with components k, n, P.

Mario Carneiro (Jan 15 2022 at 18:34):

You can use expr match for a lot of these use cases, but binders can be tricky

Patrick Massot (Jan 15 2022 at 18:43):

Thanks. I managed to do something. I won't claim there wasn't times where I simply tried all matched arguments in order until I found the one that worked...

Patrick Massot (Jan 15 2022 at 18:45):

If you don't feel like doing more important or entertaining things then you can have a look at:

import tactic.basic

meta def rel_symb : expr  tactic (expr × expr × string)
| `(%%x < %%y) := pure (x, y, " < ")
| `(%%x  %%y) := pure (x, y, " ≤ ")
| `(%%x > %%y) := pure (x, y, " > ")
| `(%%x  %%y) := pure (x, y, " ≥ ")
| `(%%x  %%y) := pure (x, y, " ∈ ")
| e := do pe  tactic.pp e, tactic.fail $ "Not a relation: " ++ pe.to_string


meta inductive my_expr
| forall_rel (var_name : name) (typ : expr) (rel : string) (rel_rhs : expr) (propo : my_expr) : my_expr
| forall_simple (var_name : name) (typ : expr) (propo : my_expr) : my_expr
| exist_rel (var_name : name) (typ : expr) (rel : string) (rel_rhs : expr) (propo : my_expr) : my_expr
| exist_simple (var_name : name) (typ : expr) (propo : my_expr) : my_expr
| impl (lhs : expr) (rhs : expr) : my_expr
| prop (e : expr) : my_expr
| data (e : expr) : my_expr

namespace my_expr

meta def tofmt : my_expr   tactic format
| (forall_rel var_name typ rel rel_rhs propo) := do
    rhs  tactic.pp rel_rhs,
    p  tofmt propo,
    pure $ "forall_rel " ++ var_name.to_string ++ rel ++ rhs.to_string ++ ", " ++ p
| (forall_simple var_name typ propo) := do
    p  tofmt propo,
    pure $ "forall_simple " ++ var_name.to_string ++ ", " ++ p
| (exist_rel var_name typ rel rel_rhs propo) := do
    rhs  tactic.pp rel_rhs,
    p  tofmt propo,
    pure $ "exist_rel " ++ var_name.to_string ++ rel ++ rhs.to_string ++ ", " ++ p
| (exist_simple var_name typ propo) := do
    p  tofmt propo,
    pure $ "exist_simple " ++ var_name.to_string ++ ", " ++ p
| (impl lhs rhs) := do
  l  tactic.pp lhs,
  r  tactic.pp rhs,
  pure $ "impl " ++ l ++ " ⇒ " ++ r
| (prop e) := do p  tactic.pp e, pure $ "prop: " ++ p
| (data e) := do p  tactic.pp e, pure $ "data: " ++ p


meta instance : has_to_tactic_format my_expr :=
my_expr.tofmt

meta def parse : expr  tactic my_expr
| e@(expr.pi n _ t b) := match b with
    | e'@(expr.pi n' _ t' b') :=
      do { (x, y, symbole)  rel_symb t',
           my_expr.forall_rel n t symbole y <$> parse b' } <|>
      do { body  parse e',
           pure $ my_expr.forall_simple n t body }
    | e' := if e.binding_name = "ᾰ" then
              pure (impl t e')
            else
              forall_simple n t <$> parse b
    end
|`(@Exists %%α %%p) := do
  `(@Exists %%α' %%p')  pure p.binding_body |
      exist_simple p.binding_name α <$> parse p.binding_body ,
  (x, y, symbole)  rel_symb α'.binding_body,
  exist_rel p.binding_name α symbole y <$> parse p'.binding_body
| e := do { t  tactic.infer_type e, pure $ if t = `(Prop) then prop e else data e } <|> pure (prop e)

end my_expr

Patrick Massot (Jan 15 2022 at 18:46):

This defines a specialized grammar for expression where bounded quantifiers become first class citizens.

Patrick Massot (Jan 15 2022 at 18:47):

Note that I'm especially not proud of my detection of implication using if e.binding_name = "ᾰ". A better solution would be welcome.

Mario Carneiro (Jan 15 2022 at 19:02):

I think docs#expr.is_arrow exists

Patrick Massot (Jan 15 2022 at 19:08):

This is what I used in the beginning, but it didn't work.

Patrick Massot (Jan 15 2022 at 19:08):

I think it doesn't work for open terms.

Patrick Massot (Jan 15 2022 at 19:08):

I hope I'm not inventing the meaning of "open term".

Patrick Massot (Jan 15 2022 at 19:09):

I mean I had issue where I was traversing an expr and the implication referred to variables introduced earlier in the expression.

Mario Carneiro (Jan 15 2022 at 19:11):

You should not be directly manipulating open terms if possible

Mario Carneiro (Jan 15 2022 at 19:11):

(where open means containing free occurrences of var constructor)

Mario Carneiro (Jan 15 2022 at 19:12):

Instead you should instantiate as you descend into binders

Alex J. Best (Jan 15 2022 at 19:14):

How come Mario? Certainly I can see why it is nicer to have closed terms a lot of the time, but are there specific reasons we should try and avoid it? I've written a lot of code that manipulates open terms :sweat:

Mario Carneiro (Jan 15 2022 at 19:14):

An example of a simple tactic that has to descend into binders is generalize_proofs, see https://github.com/leanprover-community/mathlib/blob/master/src/tactic/generalize_proofs.lean#L50-L53

Mario Carneiro (Jan 15 2022 at 19:14):

Mostly because all of lean's API assumes closed terms

Mario Carneiro (Jan 15 2022 at 19:15):

If you can do the whole job yourself, constructing / manipulating exprs without consulting lean, then it's fine to work on open terms

Mario Carneiro (Jan 15 2022 at 19:16):

but for example if you ever want to call infer_type then you are stuck

Mario Carneiro (Jan 15 2022 at 19:16):

(and for some things you have to consult the local context, for example if you encounter a local constant or metavariable in the expr)

Mario Carneiro (Jan 15 2022 at 19:18):

an example of a function working directly on open terms nontrivially is src#tactic.alias.mk_iff_mp_app

Patrick Massot (Jan 15 2022 at 19:21):

I'm clearly having issues with this. Below is a slightly updated version of my code including parsing tests. I'm almost happy with the result except precisely for the appearing of induces where I would like to see the original names. Do you see what I mean?

import tactic.basic

meta def rel_symb : expr  tactic (expr × expr × string)
| `(%%x < %%y) := pure (x, y, " < ")
| `(%%x  %%y) := pure (x, y, " ≤ ")
| `(%%x > %%y) := pure (x, y, " > ")
| `(%%x  %%y) := pure (x, y, " ≥ ")
| `(%%x  %%y) := pure (x, y, " ∈ ")
| e := do pe  tactic.pp e, tactic.fail $ "Not a relation: " ++ pe.to_string

meta inductive my_expr
| forall_rel (var_name : name) (typ : expr) (rel : string) (rel_rhs : expr) (propo : my_expr) : my_expr
| forall_simple (var_name : name) (typ : expr) (propo : my_expr) : my_expr
| exist_rel (var_name : name) (typ : expr) (rel : string) (rel_rhs : expr) (propo : my_expr) : my_expr
| exist_simple (var_name : name) (typ : expr) (propo : my_expr) : my_expr
| impl (lhs : expr) (rhs : expr) : my_expr
| prop (e : expr) : my_expr
| data (e : expr) : my_expr

namespace my_expr

meta def tofmt : my_expr   tactic format
| (forall_rel var_name typ rel rel_rhs propo) := do
    rhs  tactic.pp rel_rhs,
    p  tofmt propo,
    pure $ "∀ " ++ var_name.to_string ++ rel ++ rhs.to_string ++ ", " ++ p
| (forall_simple var_name typ propo) := do
    p  tofmt propo,
    pure $ "∀ " ++ var_name.to_string ++ ", " ++ p
| (exist_rel var_name typ rel rel_rhs propo) := do
    rhs  tactic.pp rel_rhs,
    p  tofmt propo,
    pure $ "∃ " ++ var_name.to_string ++ rel ++ rhs.to_string ++ ", " ++ p
| (exist_simple var_name typ propo) := do
    p  tofmt propo,
    pure $ "∃ " ++ var_name.to_string ++ ", " ++ p
| (impl lhs rhs) := do
  l  tactic.pp lhs,
  r  tactic.pp rhs,
  pure $ l ++ " ⇒ " ++ r
| (prop e) := tactic.pp e
| (data e) := tactic.pp e

meta instance : has_to_tactic_format my_expr :=
my_expr.tofmt

meta def parse : expr  tactic my_expr
| e@(expr.pi n _ t b) := match b with
    | e'@(expr.pi n' _ t' b') :=
      do { (x, y, symbole)  rel_symb t',
           my_expr.forall_rel n t symbole y <$> parse b' } <|>
      do { body  parse e',
           pure $ my_expr.forall_simple n t body }
    | e' := if e.binding_name = "ᾰ" then
              pure (impl t e')
            else
              forall_simple n t <$> parse b
    end
|`(@Exists %%α %%p) := do
  `(@Exists %%α' %%p')  pure p.binding_body |
      exist_simple p.binding_name α <$> parse p.binding_body ,
  (x, y, symbole)  rel_symb α'.binding_body,
  exist_rel p.binding_name α symbole y <$> parse p'.binding_body
| e := do { t  tactic.infer_type e, pure $ if t = `(Prop) then prop e else data e } <|> pure (prop e)

end my_expr

namespace tactic.interactive
setup_tactic_parser
open tactic my_expr

meta def test (h : parse ident) : tactic unit :=
get_local h >>= infer_type >>= parse >>= trace

end tactic.interactive

example (Q R :   Prop) (P :     Prop)
  (h₁ : R 1  Q 2)
  (h₂ :  k  2,  n  3,  l, l - n = 0  P l k)
  (h₃ :  n  5, Q n)
  (h₄ :  k  2,  n  3, P n k)
  (h₅ :  n, Q n)
  (h₆ :  k,  n, P n k)
  (h₇ :  k  2,  n, P n k) : true :=
begin
  test h₁,
  test h₂,
  test h₃,
  test h₄,
  test h₅,
  test h₆,
  test h₇,
  trivial
end

Mario Carneiro (Jan 15 2022 at 19:21):

induces?

Patrick Massot (Jan 15 2022 at 19:22):

So really I don't want those open terms, I simply lack the knowledge of how to close them back

Patrick Massot (Jan 15 2022 at 19:22):

Mario Carneiro said:

induces?

Sorry, I don't understand that message at all.

Mario Carneiro (Jan 15 2022 at 19:22):

you said induces and it didn't make sense

Patrick Massot (Jan 15 2022 at 19:23):

oh sorry! I wanted to write indices

Patrick Massot (Jan 15 2022 at 19:23):

de Bruijn indices

Patrick Massot (Jan 15 2022 at 19:23):

modulo spelling of that name

Mario Carneiro (Jan 15 2022 at 19:24):

So that's what the first link was about:

| (expr.pi n b d e) := do
  nh  collect_proofs_in d ctx (ns, hs),
  var  mk_local' n b d,
  collect_proofs_in (expr.instantiate_var e var) (var::ctx) nh

You use var <- mk_local' n b d to get a new local constant and then use expr.instantiate_var e var to instantiate the body before recursing

Mario Carneiro (Jan 15 2022 at 19:24):

and hold on to var as the "variable being bound" in your inductive representation of the binder

Patrick Massot (Jan 15 2022 at 19:27):

Thanks, I'll try that.


Last updated: Dec 20 2023 at 11:08 UTC