Zulip Chat Archive

Stream: Is there code for X?

Topic: term representation of labeled binary tree


Asei Inoue (Sep 28 2025 at 09:25):

/-- Binary tree -/
inductive BinTree (α : Type) where
  | empty
  | node (val : α) (left right : BinTree α)
deriving DecidableEq, BEq

notation:max "∅" => BinTree.empty

variable {α : Type}

/-- Leaf of a binary tree -/
def BinTree.leaf (a : α) : BinTree α := .node a  

def BinTree.isEmpty (t : BinTree α) : Bool :=
  match t with
  | empty => true
  | node _ _ _ => false

def BinTree.isLeaf (t : BinTree α) : Bool :=
  match t with
  | empty => false
  | node _ left right =>
    left.isEmpty && right.isEmpty

@[inherit_doc]
notation:max "⟦" x "⟧" => BinTree.leaf x

/- ## Displaying a binary tree -/

protected def BinTree.toString [ToString α] (t : BinTree α) : String :=
  match t with
  | empty => "∅"
  | node val left right =>
    if t.isLeaf then
      toString val
    else
      toString val ++ " * " ++ "(" ++ BinTree.toString left ++ " + " ++ BinTree.toString right ++ ")"

instance [ToString α] : ToString (BinTree α) where
  toString := BinTree.toString

#guard toString 1 = "1"
#guard toString (BinTree.node 1 2 .empty) = "1 * (2 + ∅)"
#guard toString (BinTree.node 1 2 3) = "1 * (2 + 3)"
#guard
  let tree := BinTree.node 1
    (left := BinTree.node 2 3 4)
    (right := BinTree.node 5 6 7)
  toString tree = "1 * (2 * (3 + 4) + 5 * (6 + 7))"

/- ## Syntax to represent a binary tree -/

/-- Syntax category for constructing a binary tree -/
declare_syntax_cat bintree
syntax "[tree| " bintree "]" : term

/-- Base case: Something like `[tree| 42]` corresponds to a leaf, so it is valid syntax -/
syntax num : bintree

/-- Base case: `[tree| ∅]` corresponds to an empty tree, so it is valid syntax -/
syntax "∅" : bintree

/-- Recursive step -/
syntax num " * " "(" bintree " + " bintree ")" : bintree

#check_failure [tree| 1 * (2 + 3)]
#check_failure [tree| 1 * (2 + )]
#check_failure [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]

macro_rules
  | `([tree| ]) => `(BinTree.empty)
  | `([tree| $num:num]) => `(BinTree.leaf $num)
  | `([tree| $v:num * ($l + $r)]) => `(BinTree.node $v [tree| $l] [tree| $r])

#guard
  let actual := [tree| 1 * (2 + 3)]
  let expected := BinTree.node 1 2 3
  actual = expected

#guard
  let actual := [tree| 12 * (2 + )]
  let expected := BinTree.node 12 2 
  actual = expected

#guard
  let actual := [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
  let expected := BinTree.node 1
    (left := BinTree.node 2 3 4)
    (right := BinTree.node 5 6 7)
  actual = expected

/- ## Definition of Repr -/

def BinTree.reprPrec [ToString α] (tree : BinTree α) : String :=
  "[tree| " ++ toString tree ++ "]"

instance [ToString α] : Repr (BinTree α) where
  reprPrec := fun tree _ => BinTree.reprPrec tree

#eval [tree| 1 * (2 + 3)]
#eval [tree| 12 * (2 + )]
#eval [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]

Asei Inoue (Sep 28 2025 at 09:30):

I'm exploring better ways to represent terms of (binary) trees.
Writing tree terms with constructors quickly becomes deeply nested, which hurts readability and ergonomics.
SVGs are pleasant to view but awkward to author.
So I'd like to implement a “term syntax for labeled trees” in Lean.

Asei Inoue (Sep 28 2025 at 09:40):

The [tree| ... ] syntax defined in the code above works well, but it currently only supports numeric literals.
How can I write a more general macro that also works with other kinds of literals, such as string literals?

Asei Inoue (Sep 28 2025 at 09:40):

Thank you for advance!

Aaron Liu (Sep 28 2025 at 12:53):

Asei Inoue said:

The [tree| ... ] syntax defined in the code above works well, but it currently only supports numeric literals.
How can I write a more general macro that also works with other kinds of literals, such as string literals?

Just add in string literals?

Asei Inoue (Sep 29 2025 at 13:08):

This does not work...

declare_syntax_cat bintree
syntax "[tree| " bintree "]" : term

syntax num : bintree
syntax str : bintree
syntax "∅" : bintree

/-- Recursive step -/
syntax num " * " "(" bintree " + " bintree ")" : bintree
syntax str " * " "(" bintree " + " bintree ")" : bintree

#check_failure [tree| 1 * (2 + 3)]
#check_failure [tree| 1 * (2 + )]
#check_failure [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
#check_failure [tree| 'x'] -- unexpected token error

Asei Inoue (Sep 29 2025 at 13:08):

@Aaron Liu How to fix this error?

Henrik Böving (Sep 29 2025 at 13:09):

Your syntax declarations only allow for

#check_failure [tree| "x"]

you'll need to add another syntax for the character literals

Asei Inoue (Sep 29 2025 at 13:18):

what is the suitable category for character literals?

self resolved

Asei Inoue (Sep 29 2025 at 13:29):

How to rewrite this macro_rules more simply?

/- ## Syntax to represent a binary tree -/

/-- Syntax category for constructing a binary tree -/
declare_syntax_cat bintree
syntax "[tree| " bintree "]" : term

declare_syntax_cat bintree_label
syntax bintree_label : bintree
syntax num : bintree_label
syntax str : bintree_label
syntax char : bintree_label

/-- Base case: `[tree| ∅]` corresponds to an empty tree, so it is valid syntax -/
syntax "∅" : bintree

/-- Recursive step -/
syntax bintree_label " * " "(" bintree " + " bintree ")" : bintree

#check_failure [tree| 1 * (2 + 3)]
#check_failure [tree| 1 * (2 + )]
#check_failure [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
#check_failure [tree| "x"]
#check_failure [tree| 'x']

macro_rules
  | `([tree| ]) => `(BinTree.empty)
  | `([tree| $num:num]) => `(BinTree.leaf $num)
  | `([tree| $str:str]) => `(BinTree.leaf $str)
  | `([tree| $char:char]) => `(BinTree.leaf $char)
  | `([tree| $v:num * ($l + $r)]) => `(BinTree.node $v [tree| $l] [tree| $r])
  | `([tree| $v:str * ($l + $r)]) => `(BinTree.node $v [tree| $l] [tree| $r])
  | `([tree| $v:char * ($l + $r)]) => `(BinTree.node $v [tree| $l] [tree| $r])

Henrik Böving (Sep 29 2025 at 13:32):

This looks just right really

Asei Inoue (Sep 29 2025 at 13:33):

I cannot write simple concise rule for bintree_label?

Asei Inoue (Sep 29 2025 at 13:40):

This is my best attempt:

/- ## Syntax to represent a binary tree -/

/-- Syntax category for constructing a binary tree -/
declare_syntax_cat bintree
syntax "[tree| " bintree "]" : term

declare_syntax_cat bintree_label
syntax bintree_label : bintree
syntax num : bintree_label
syntax str : bintree_label
syntax char : bintree_label

/-- Base case: `[tree| ∅]` corresponds to an empty tree, so it is valid syntax -/
syntax "∅" : bintree

/-- Recursive step -/
syntax bintree_label " * " "(" bintree " + " bintree ")" : bintree

#check_failure [tree| 1 * (2 + 3)]
#check_failure [tree| 1 * (2 + )]
#check_failure [tree| 1 * (2 * (3 + 4) + 5 * (6 + 7))]
#check_failure [tree| "x"]
#check_failure [tree| 'x']

open Lean Macro

def expandLabel (stx : TSyntax `bintree_label) : MacroM (TSyntax `term) := do
  match stx with
  | `(bintree_label| $num:num) => `(term| $num)
  | `(bintree_label| $str:str) => `(term| $str)
  | `(bintree_label| $char:char) => `(term| $char)
  | _ => throwUnsupported

macro_rules
  | `([tree| ]) => `(BinTree.empty)
  | `([tree| $label:bintree_label]) => do
    let label  expandLabel label
    `(BinTree.leaf $label)
  | `([tree| $label:bintree_label * ($l + $r)]) => do
    let label  expandLabel label
    `(BinTree.node $label [tree| $l] [tree| $r])

Asei Inoue (Sep 29 2025 at 15:25):

By the way, I want to write term representation syntax for general labeled-trees.

How to fix this error?

/-- Labeled tree -/
inductive Tree (α : Type) where
  /-- Empty tree -/
  | empty
  | node (v : α) (children : List (Tree α))

variable {α : Type}

/-- Leaf (a node with no children) -/
def Tree.leaf (v : α) : Tree α := Tree.node v []

@[inherit_doc]
local notation:max "⟦" v "⟧" => Tree.leaf v

section
-- ## Define syntax for constructing Tree terms

open Lean Macro

/-- Syntax category for labeled trees -/
declare_syntax_cat tree

/-- If `φ` is a `tree` syntax, then `[tree| φ]` represents a labeled tree -/
syntax "[tree| " tree "]" : term

/-- Labels of the tree. We allow numeric literals, string literals, and character literals as labels -/
syntax tree_label := num <|> str <|> char

/-- Base case: expressions like `[tree| 42]` are valid syntax -/
syntax tree_label : tree

/-- Recursive step -/
syntax tree_label " * " "(" sepBy(tree, " + ") ")" : tree

/-- Convert a `tree_label` syntax into a `term` -/
def expandLabel (stx : TSyntax `tree_label) : MacroM (TSyntax `term) :=
  match stx with
  | `(tree_label| $num:num) => `(term| $num)
  | `(tree_label| $str:str) => `(term| $str)
  | `(tree_label| $char:char) => `(term| $char)
  | _ => throwUnsupported

macro_rules
  | `(tree| $label:tree_label) => do
    let v  expandLabel label
    `(term| Tree.leaf $v)
  | `(tree| $label:tree_label * ($children:tree+*)) => do
    let v  expandLabel label
    let trees := children.getElems.map (fun x => `(term| [tree| $x]))
    `(term| Tree.node $v $trees)

end

Asei Inoue (Sep 29 2025 at 16:27):

I think let trees := children.getElems.map (fun x => (term| [tree| $x]))` is not good, but how to fix this?

Asei Inoue (Oct 05 2025 at 01:00):

Self resolved


Last updated: Dec 20 2025 at 21:32 UTC