Zulip Chat Archive

Stream: lean4

Topic: Defining syntax


Dan Velleman (Jun 17 2022 at 18:42):

I decided to try to implement the assume syntax in Lean 4. The following seems to work:

syntax "assume" ident " : " term ", " term : term
syntax "assume" ident ", " term : term
macro_rules
  | `(assume $v:ident : $t:term, $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident, $b:term) => `(fun $v => $b)

But it has one unfortunate feature: it requires a comma after the assumption. This seems inconsistent with the syntax used in other situations--for example, have and suffices don't require commas. So is there a way to get rid of the comma? Simply leaving it out of the syntax and macro_rules doesn't seem to work, but I'm not sure why. (Does Lean have trouble parsing two terms in a row with no delimiter between them?)

Wojciech Nawrocki (Jun 17 2022 at 18:44):

Does Lean have trouble parsing two terms in a row with no delimiter between them?

Yes, foo bar is parsed as a function application. If you know something specific about the first term, e.g. that it's just one identifier you could write ident term and that should parse as two things (I think), but in general term term is not going to work.

Dan Velleman (Jun 17 2022 at 18:47):

That makes sense. But then how do have and suffices get parsed with no delimiter at the end?

Mario Carneiro (Jun 17 2022 at 18:49):

it's not that they have no delimiter, the delimiter is allowed to be newline

Dan Velleman (Jun 17 2022 at 18:50):

So can I require newline as a delimiter in my syntax? How do I do that?

Mario Carneiro (Jun 17 2022 at 18:52):

actually, the way have does it is using withPosition

Dan Velleman (Jun 17 2022 at 18:53):

I'm not familiar with withPosition. Where do I learn about that?

Mario Carneiro (Jun 17 2022 at 18:58):

This almost works, except that the last one probably shouldn't be accepted:

import Lean

syntax withPosition("assume" ident " : " term ", "?) term : term
syntax withPosition("assume" ident ", "?) term : term
macro_rules
  | `(assume $v:ident : $t:term $[,]? $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident $[,]? $b:term) => `(fun $v => $b)

#check assume x : List Nat
       x
#check assume x : List Nat,
       x
#check assume x : List Nat, x
#check assume x
       x
#check assume x,
       x
#check assume x, x
#check assume x x

Arthur Paulino (Jun 17 2022 at 18:58):

The API is still missing documentation, but this thread helped me with withPosition

Mario Carneiro (Jun 17 2022 at 19:08):

Here's a version using the new checkLinebreakBefore combinator:

import Lean

def Lean.Parser.commaOrLinebreak : Parser := ", " <|> checkLinebreakBefore >> pushNone
syntax withPosition("assume" ident " : " term Lean.Parser.commaOrLinebreak) term : term
syntax withPosition("assume" ident Lean.Parser.commaOrLinebreak) term : term
macro_rules
  | `(assume $v:ident : $t:term, $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident : $t:term
      $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident, $b:term) => `(fun $v => $b)
  | `(assume $v:ident
      $b:term) => `(fun $v => $b)

#check assume x : List Nat
       x
#check assume x : List Nat,
       x
#check assume x : List Nat, x
#check assume x
       x
#check assume x,
       x
#check assume x, x
#check assume x x -- fails

Leonardo de Moura (Jun 17 2022 at 19:13):

@Mario Carneiro You can also use

syntax withPosition("assume" ident " : " term (", " <|> linebreak)) term : term
syntax withPosition("assume" ident (", " <|> linebreak)) term : term

Mario Carneiro (Jun 17 2022 at 19:13):

ah, I was just about to suggest the addition of such a linebreak

Leonardo de Moura (Jun 17 2022 at 19:14):

You also don't need the extra rules. The following is sufficient.

macro_rules
  | `(assume $v:ident : $t:term , $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident , $b:term) => `(fun $v => $b)

Leonardo de Moura (Jun 17 2022 at 19:16):

We don't have good documentation for this yet, but Sebastian's PR https://github.com/leanprover/lean4/pull/1226 has several relevant remarks.

Mario Carneiro (Jun 17 2022 at 19:17):

When using linebreak instead of my hand-rolled version, I'm getting some weird parse results on the ones that use the line break

import Lean

syntax withPosition("assume" ident " : " term (", " <|> linebreak)) term : term
syntax withPosition("assume" ident (", " <|> linebreak)) term : term
macro_rules
  | `(assume $v:ident : $t:term, $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident, $b:term) => `(fun $v => $b)

#check assume x : Nat
       x -- x is colored like text
-- fun x => sorryAx (?m.1038 x) true : (x : Nat) → ?m.1038 x

#check assume x : Nat,
       x  -- x is colored like a variable
-- fun x => x : Nat → Nat

Leonardo de Moura (Jun 17 2022 at 19:19):

@Mario Carneiro Thanks for reporting. I will investigate it.

Mario Carneiro (Jun 17 2022 at 19:20):

Leonardo de Moura said:

You also don't need the extra rules. The following is sufficient.

macro_rules
  | `(assume $v:ident : $t:term , $b:term) => `(fun ($v : $t) => $b)
  | `(assume $v:ident , $b:term) => `(fun $v => $b)

How does this work? Is this something in linebreak that causes it to produce a comma, or some magic in the pattern matcher?

Leonardo de Moura (Jun 17 2022 at 19:22):

Sebastian wrote about this here: https://github.com/leanprover/lean4/pull/1226#discussion_r899029212

Leonardo de Moura (Jun 17 2022 at 19:23):

The remark also makes it clear what is missing in the example above. The linebreak parser is not adding the pushNone

Sebastian Ullrich (Jun 17 2022 at 19:34):

group(linebreak) should work

Leonardo de Moura (Jun 17 2022 at 19:35):

Sebastian Ullrich said:

group(linebreak) should work

Yes, it should, but it looks too bizarre. I was writing a PR that changes the semantics of linebreak to include pushNone.

Leonardo de Moura (Jun 17 2022 at 19:36):

It feels like parsers such as (", " <|> linebreak) will be heavily used from now on when defining syntax such as assume.

Sebastian Ullrich (Jun 17 2022 at 19:38):

And that's not the only caveat. linebreak linebreak does not check for two linebreaks.

Sebastian Ullrich (Jun 17 2022 at 19:40):

Not sure if there are any macros out there that depend on ws being nullary

Leonardo de Moura (Jun 17 2022 at 19:41):

Sebastian Ullrich said:

And that's not the only caveat. linebreak linebreak does not check for two linebreaks.

Yes, this is not nice :( Another item for https://github.com/leanprover/lean4/issues/638?

Mario Carneiro (Jun 17 2022 at 19:52):

For the (", " <|> linebreak) case, perhaps orLinebreak(", ") is better to combine the pushNone behavior with the delimiter

Sebastian Ullrich (Jun 17 2022 at 19:54):

Sebastian Ullrich said:

And that's not the only caveat. linebreak linebreak does not check for two linebreaks.

If we wanted to fix this, we may want to generate an empty token after all

Mario Carneiro (Jun 17 2022 at 19:54):

Sebastian Ullrich said:

And that's not the only caveat. linebreak linebreak does not check for two linebreaks.

I'm kind of okay with the current behavior, perhaps it just needs a clearer name like checkLinebreakBefore

Mario Carneiro (Jun 17 2022 at 19:56):

alternatively, it could take an optional parameter like linebreak(2) to check for 2 linebreaks in the previous whitespace

Sebastian Ullrich (Jun 18 2022 at 12:32):

Leonardo de Moura said:

Sebastian Ullrich said:

group(linebreak) should work

Yes, it should, but it looks too bizarre

Let's fix this properly, once and for all https://github.com/leanprover/lean4/pull/1229


Last updated: Dec 20 2023 at 11:08 UTC