Zulip Chat Archive

Stream: lean4

Topic: Unexpander can't match expression with nested parentheses


Chris Wong (Feb 16 2025 at 02:24):

Hi all, I ran into a problem that I've minimized here:

import Mathlib

open Lean
open Lean.PrettyPrinter

def Board := Array (Option (Option Bool))

def Board.mk (array : Array (Option (Option Bool))) : Board := array

declare_syntax_cat cell
syntax "- " : cell
syntax "# " : cell
syntax "I " : cell

syntax "board " cell* : term

open Lean.Macro in
macro_rules
  | `(term| board $cells:cell*) => do
    let cells :=  cells.mapM fun
      | `(cell| -) => ``(none)
      | `(cell| #) => ``(some none)
      | `(cell| I) => ``(some (some true))
      | _ => throwUnsupported
    ``(Board.mk #[ $[$cells],* ])

@[app_unexpander Board.mk]
def unexpandBoard : Unexpander
  | `($_boardMk #[$cells,*]) => do
    let cells :=  (cells : TSyntaxArray `term).mapM fun
      | `(none) => `(cell| -)
      | `(some none) => `(cell| #)
      | `(some (some true)) => `(cell| I)
      | _ => throw ()
    `(board $cells*)
  | _ => throw ()

def board1 : Board := board - #

def board2 : Board := board - # I

#print board1
#print board2

board1 pretty-prints successfully, but board2 doesn't, and it seems like the difference is that the latter has a nested constructor.

Any ideas why this happens?

Context: I'm formalizing Tetris, and as part of that, I'm making a DSL for constructing game boards. Here's the unminimized code.

Kyle Miller (Feb 16 2025 at 02:27):

Unexpanders get run before parenthesization, so you can't have parentheses in your match patterns.

A way to work around this limitation is to nest matches

Kyle Miller (Feb 16 2025 at 02:28):

@[app_unexpander Board.mk]
def unexpandBoard : Unexpander
  | `($_boardMk #[$cells,*]) => do
    let cells :=  (cells : TSyntaxArray `term).mapM fun
      | `(none) => `(cell| -)
      | `(some none) => `(cell| #)
      | `(some $v) =>
        match v with
        | `(some true) => `(cell| I)
        | _ => throw ()
      | _ => throw ()
    `(board $cells*)
  | _ => throw ()

Chris Wong (Feb 16 2025 at 02:29):

Thanks Kyle, that makes sense!

Chris Wong (Feb 16 2025 at 03:53):

I wonder if it's worth calling this out in the Metaprogramming in Lean book.

Kyle Miller (Feb 16 2025 at 04:48):

Maybe match could also have some widget to make this more convenient, like

    | stripParens `(some (some true)) => `(cell| I)

Chris Wong (Feb 16 2025 at 05:43):

Chris Wong said:

I wonder if it's worth calling this out in the Metaprogramming in Lean book.

https://github.com/leanprover-community/lean4-metaprogramming-book/pull/162


Last updated: May 02 2025 at 03:31 UTC