infix

infix は、中置記法を定義するコマンドです。

-- 中置記法を定義。中身はただの掛け算
-- 特定の名前空間でだけ有効にするために `scoped` を付けている
scoped infix:60 " ⋄ " => Nat.mul

#guard 2 ⋄ 3 = 6

: の後に付けている数字はパース優先順位で、高いほど結合するタイミングが早くなります。等号 = のパース優先順位は 50 であることを覚えておくと良いかもしれません。

-- 等号より微妙にパース優先順位が高い
scoped infix:51 " strong " => Nat.add

-- きちんと 1 + (2 strong 3) = 6 と解釈される。
-- これは、 等号のパース優先順位が 51 未満であることを意味する
#check 1 + 2 strong 3 = 6

-- パース優先順位を 50 より低くすると等号より低くなる
-- したがってエラーになる
scoped infix:49 " weak " => Nat.add

#check_failure 1 + 2 weak 3 = 6

infix で定義される記法は左結合でも右結合でもなく、必ず括弧が必要です。

section

open Lean Parser

/-- `s : String` をパースして `Syntax` の項を得る。`cat` は構文カテゴリ。-/
def parse (cat : Name) (s : String) : MetaM Syntax := do
  ofExcept <| runParserCategory (← getEnv) cat s

-- 単に連結するとパース不可でエラーになる
/-- error: <input>:1:6: expected end of input -/
#guard_msgs in run_meta parse `term "1 ⋄ 2 ⋄ 3"

-- 括弧を付ければOK
run_meta parse `term "1 ⋄ (2 ⋄ 3)"

end

舞台裏

infixnotation コマンドに展開されるマクロとして実装されています。

def lxor (l r : Bool) : Bool := !l && r

/-- `#expand` の入力に渡すための構文カテゴリ -/
syntax macro_stx := command <|> tactic <|> term

open Lean in

/-- マクロを展開するコマンド -/
elab "#expand " t:macro_stx : command => do
  let t : Syntax := match t.raw with
  | .node _ _ #[t] => t
  | _ => t.raw
  match ← Elab.liftMacroM <| Macro.expandMacro? t with
  | none => logInfo m!"Not a macro"
  | some t => logInfo m!"{t}"

/-- info: notation:50 lhs✝:51 " LXOR " rhs✝:51 => lxor lhs✝ rhs✝ -/
#guard_msgs in
  #expand infix:50 " LXOR " => lxor