Zulip Chat Archive

Stream: lean4

Topic: json elaborator


Patrick Massot (Sep 19 2023 at 17:08):

ProofWidgets has a nice json% elaborator but it requires a open scoped Json that will break things, especially the words "true" and "false". Is there a way to get this elaborator without opening those scoped notations? Or is there a way to open it for one line of code in a function. @Wojciech Nawrocki

Wojciech Nawrocki (Sep 19 2023 at 17:09):

You can open it in the scope of a single command (e.g. a single def) using open scoped Json in def .., but afaik there is no way to open within just a single term. The issue with true/false is due to the token table being global, and I recall Sebastian mention he wanted to fix that at some point. Note also that json% isn't an elaborator, it is a macro.

Patrick Massot (Sep 19 2023 at 17:10):

Example:

import ProofWidgets.Data.Json

open Lean

open scoped ProofWidgets.Json in
def foo : Json := Id.run do
  -- let x := true -- cannot uncomment this line
  return json% {"x" : 1}

Patrick Massot (Sep 19 2023 at 17:11):

Yes, I know I can open it before def. But here I want to open it before return.

Mario Carneiro (Sep 19 2023 at 17:14):

open ... in works within terms, but it is possible that it doesn't work here because you can't change the parser mid-command

Mario Carneiro (Sep 19 2023 at 17:15):

does the json parser work if these are declared as &"true" and &"false"? Are other identifiers legal in that position?

Patrick Massot (Sep 19 2023 at 17:16):

Oh,

def foo : Json := Id.run do
  -- let x := true
  open scoped ProofWidgets.Json in
  return  json% {"x" : 1}

fails but

def foo' : Json := Id.run do
  let x := true
  return open scoped ProofWidgets.Json in json% {"x" : 1}

works!

Patrick Massot (Sep 19 2023 at 17:16):

I'll be able to re-re-rewrite my code!

Patrick Massot (Sep 19 2023 at 17:20):

I need to find out how to write a macro that expands to open scoped ProofWidgets.Json in json%

Patrick Massot (Sep 19 2023 at 17:20):

but this doesn't really feel right.

Mario Carneiro (Sep 19 2023 at 17:22):

this seems like leakage of macro internals, you shouldn't need to open anything for json% to do the right thing

Mario Carneiro (Sep 19 2023 at 17:23):

except possibly the json% macro itself, if it is scoped

Wojciech Nawrocki (Sep 19 2023 at 17:24):

Mario Carneiro said:

except possibly the json% macro itself, if it is scoped

Yeah, it's a scoped syntax. The issue is precisely the true/false thing: we don't want importing the macro definition to mess up parsing everywhere.

Patrick Massot (Sep 19 2023 at 17:24):

Mario, you can take a look at https://github.com/EdAyers/ProofWidgets4/blob/main/ProofWidgets/Data/Json.lean, it doesn't depend on anything else.

Wojciech Nawrocki (Sep 19 2023 at 17:25):

Mario Carneiro said:

does the json parser work if these are declared as &"true" and &"false"? Are other identifiers legal in that position?

Are there any resources for how to use the & parser? Declaring them as non-keywords breaks matching in macro_rules:

declare_syntax_cat cat
syntax &"dogcow" : cat

macro_rules
  | `(cat| dogcow) => `(37) -- expected cat

Patrick Massot (Sep 19 2023 at 17:26):

import ProofWidgets.Data.Json

macro "Json%" x:jso : term => `(open scoped ProofWidgets.Json in json% $x)

def foo' : Json := Id.run do
  let x := true
  return Json% {"x" : 1}

doesn't work :sad: I'm out of hack ideas.

Wojciech Nawrocki (Sep 19 2023 at 17:27):

@Patrick Massot I think the issue is that to parse your Json% blah, the scoped syntaxes that make up blah need to already be open.

Patrick Massot (Sep 19 2023 at 17:27):

Yes, I understand, but I'm still out of ideas.

Mario Carneiro (Sep 19 2023 at 17:29):

this seems to work @Wojciech Nawrocki:

declare_syntax_cat cat (behavior := symbol)
syntax "dogcow" : cat

macro_rules
  | `(cat| dogcow) => `(37)

#check fun dogcow => dogcow

Patrick Massot (Sep 19 2023 at 17:31):

Maybe a stop gap would be to drop support of true and false and use $(true) and $(false).

Mario Carneiro (Sep 19 2023 at 17:31):

I'm not sure if this suffices for the json parser, but I would guess it does since true can only appear at a json object node

Mario Carneiro (Sep 19 2023 at 17:31):

I'm not sure why I'm getting strange reactions on that post

Wojciech Nawrocki (Sep 19 2023 at 17:32):

Mario Carneiro said:

I'm not sure why I'm getting strange reactions on that post

Mine was because the option is not documented on declare_syntax_cat and I had not seen it before. But one can get to its documentation by following the parser trail.

James Gallicchio (Sep 19 2023 at 17:33):

it's giving dark metamagic

James Gallicchio (Sep 19 2023 at 17:33):

(not opposed)

Wojciech Nawrocki (Sep 19 2023 at 17:44):

Okay, released new PW4 in which

open scoped ProofWidgets.Json in
def foo : Json := Id.run do
  let x := true
  return json% {"x" : 1}

should work thanks to Mario's metaprogramming investigations.

Patrick Massot (Sep 19 2023 at 17:45):

Great! Next question is: do we dare trying to bump PW4 in mathlib or will this break the cache?

Mario Carneiro (Sep 19 2023 at 17:46):

all bumps break the cache

Patrick Massot (Sep 19 2023 at 17:50):

By the way Mario, don't you want https://github.com/EdAyers/ProofWidgets4/blob/main/ProofWidgets/Data/Json.lean in Std anyway?

Patrick Massot (Sep 19 2023 at 17:50):

Mario Carneiro said:

all bumps break the cache

What should we do then?

Mario Carneiro (Sep 19 2023 at 17:51):

bump anyway

Ruben Van de Velde (Sep 19 2023 at 17:52):

Do you mean "invalidate" rather than "break"?

Mario Carneiro (Sep 19 2023 at 17:53):

I mean invalidate, not sure whether patrick does

Mario Carneiro (Sep 19 2023 at 17:53):

Patrick Massot said:

By the way Mario, don't you want https://github.com/EdAyers/ProofWidgets4/blob/main/ProofWidgets/Data/Json.lean in Std anyway?

PRs welcome

Patrick Massot (Sep 19 2023 at 18:06):

I'm referring the the week of debugging that followed my previous PW bump attempt. I opened #7258 anyway.

Patrick Massot (Sep 19 2023 at 18:29):

I opened std4#267. I hope @Wojciech Nawrocki and @Edward Ayers are ok. The only differences with the version at ProofWidgets4 are docstrings and tests.

Mac Malone (Sep 22 2023 at 00:49):

@Wojciech Nawrocki I am curious why jso_ident and jso_field are syntax categories rather than just syntax abbreviations. That is, the following should work

syntax jsoSubst := "$(" term ")"
syntax jsoIdent := ident <|> str <|> jsoSubst
syntax jsoField := jsoIdent ": " jso

Mac Malone (Sep 22 2023 at 00:50):

syntax categories are generally only used for recursive or extensible syntax kinds.

Wojciech Nawrocki (Sep 22 2023 at 02:04):

I'm afraid the answer is that I was sadly never able to understand the intricacies of Lean syntax extensions! I can confirm that after removing the two categories it still works.

However this does not fix the real issue. To remove the need for scoped, the task is to come up with a syntax extension which makes macro_rules work while also not breaking quotation patterns. Here is a minimal example:

import Lean
open Lean

-- Challenge: devise the syntax so that at least one of the `macro_rules`,
-- as well as the `#check` below succeed;
-- or explain why this is not possible.
declare_syntax_cat dog (behavior := symbol)
syntax "$(" term ")" : dog
syntax "dog% " dog : term

macro_rules
  | `(term| dog% $( $t:term )) => `(1337)

macro_rules
  | `(dog| $( $t:term )) => `(1337)

#check
  let a : Syntax := sorry
  if let `(calcStep| $(_) := $stx) := a then sorry else sorry

Mac Malone (Sep 22 2023 at 02:48):

@Wojciech Nawrocki I have two alternative suggestions:

  • Just use parentheses without the $ (as they are otherwise not valid JSON).
  • Use {{ ... }} like Moustache/Handlebars/GitHub actions (via atomic("{" noWs "{") / atomic("}" noWs "}") to avoid a new token).

The former is simpler that later is clearer for the user.

Mac Malone (Sep 22 2023 at 02:51):

I wonder if it would be a good idea to host some online (or even offline) workshops on some of the fine non-mathematical components of Lean 4 (and, in particular, the various aspects of metaprogramming).

Wojciech Nawrocki (Sep 22 2023 at 03:06):

I totally agree that a solution which does not clash with antiquotations is the pragmatic way to go. That said, I am interested in this puzzle because it seems intuitively wrong that adding parsers localized to a freshly defined category breaks the global syntax. This intuition might be incorrect, and in that case I would also be interested in learning why.

Mac Malone (Sep 22 2023 at 04:03):

@Wojciech Nawrocki The token trie is global. Every token reachable from command/term is in there. As json% makes jso reachable from term, its $( token breaks antiquotes.

Wojciech Nawrocki (Sep 22 2023 at 14:07):

Yep, you are right about that. Except that with antiquotations, because the token already exists, we can invent some hacks to make it work:

declare_syntax_cat dog
syntax "dog% " dog : term

macro_rules
  | `(term| dog% $stx) => do
    if stx.raw.getKind == `dog.pseudo.antiquot then
      return stx.raw[2][1]
    else
      Macro.throwUnsupported

/- 10 : Nat -/
#check dog% $(10)

#check
  let a : Syntax := sorry
  if let `(calcStep| $(_) := $stx) := a then sorry else sorry -- works

Sebastian Ullrich (Sep 22 2023 at 14:12):

Yes, this is in fact the reason I lifted the restriction of antiquotations only being allowed in quotations at some point. Ideally you should make use of functions isEscapedAntiquot etc. such that even `(dog% $$x) works (it should be a dog-level antiquotation, not a Syntax-level one)

Sebastian Ullrich (Sep 22 2023 at 14:16):

Actually that should work out of the box since it's the syntax quotation elaborator's job to strip off one $ here! You should only have to worry about that in your own quotation-like elaborator if other quotation-likes can be nested in it

Wojciech Nawrocki (Sep 22 2023 at 14:35):

Nice! The following seems to work as a fallback case handling antiquotations:

macro_rules
  | `(json% $stx)           =>
    if stx.raw.isAntiquot then
      let stx := stx.raw.getAntiquotTerm
      `(Lean.toJson $stx)
    else
      Macro.throwUnsupported

...

def foo : Lean.Json := sorry
/- Lean.Json.mkObj [("abc", Lean.toJson foo), ("_def", Lean.toJson 10)] : Lean.Json -/
#check json% { abc: $foo, _def: $(10) }

Mario Carneiro (Sep 22 2023 at 20:00):

Mac Malone said:

I wonder if it would be a good idea to host some online (or even offline) workshops on some of the fine non-mathematical components of Lean 4 (and, in particular, the various aspects of metaprogramming).

This was actually the point of the ICERM 2022 after-party, it was a metaprogramming workshop to get people trained up to write missing tactics for mathlib4


Last updated: Dec 20 2023 at 11:08 UTC