abbrev Sequence (α : Type) := List α

def bigop (init : β) (seq : Sequence α) (op : β → β → β) (f : α → Bool × β) : β := Id.run do
  let mut result := init
  for a in seq do
    let (ok, b) := f a
    if ok then
      result := op result b
  return result

#eval bigop 0 [1, 2, 4] Add.add fun elem => (elem % 2 == 0, elem * 2)

def iota : Nat → Nat → List Nat
  | _, 0   => []
  | m, n+1 => m :: iota (m+1) n

def index_iota (m n : Nat) := iota m (n - m)

class Enumerable (α : Type) where
  elems : List α

instance : Enumerable Bool where
  elems := [false, true]

instance {α β} [Enumerable α] [Enumerable β] : Enumerable (α × β) where
  elems := Enumerable.elems.flatMap fun (a : α) => Enumerable.elems.flatMap fun (b : β) => [(a, b)]

def finElems (n : Nat) : List (Fin n) :=
  match n with
  | 0   => []
  | n+1 => go (n+1) n (by simp +arith)
where
  go (n : Nat) (i : Nat) (h : i < n) : List (Fin n) :=
   match i with
   | 0 => [⟨0, h⟩]
   | i+1 => ⟨i+1, h⟩ :: go n i (Nat.lt_of_succ_lt h)

instance : Enumerable (Fin n) where
  elems := (finElems n).reverse

instance : OfNat (Fin (Nat.succ n)) m :=
  ⟨Fin.ofNat' _ m⟩

-- Declare a new syntax category for "indexing" big operators
declare_syntax_cat index
syntax term:51 "≤" ident "<" term : index
syntax term:51 "≤" ident "<" term "|" term : index
syntax ident "<-" term : index
syntax ident "<-" term "|" term : index
-- Primitive notation for big operators
syntax "_big" "[" term "," term "]" "(" index ")" term : term

-- We define how to expand `_big` with the different kinds of index
macro_rules
| `(_big [$op, $idx] ($i:ident <- $r | $p) $F) => `(bigop $idx $r $op (fun $i:ident => ($p, $F)))
| `(_big [$op, $idx] ($i:ident <- $r) $F) => `(bigop $idx $r $op (fun $i:ident => (true, $F)))
| `(_big [$op, $idx] ($lower:term ≤ $i:ident < $upper) $F) => `(bigop $idx (index_iota $lower $upper) $op (fun $i:ident => (true, $F)))
| `(_big [$op, $idx] ($lower:term ≤ $i:ident < $upper | $p) $F) => `(bigop $idx (index_iota $lower $upper) $op (fun $i:ident => ($p, $F)))

-- Define `∑ `
syntax "∑ " "(" index ")" term : term
macro_rules | `(∑ ($idx) $F) => `(_big [Add.add, 0] ($idx) $F)

-- We can already use `Sum` with the different kinds of index.
#check ∑ (i <- [0, 2, 4] | i != 2) i
#check ∑ (10 ≤ i < 20 | i != 5) i+1
#check ∑ (10 ≤ i < 20) i+1

-- Define `∏`
syntax "∏" "(" index ")" term : term
macro_rules | `(∏ ($idx) $F) => `(_big [Mul.mul, 1] ($idx) $F)

-- The examples above now also work for `Prod`
#check ∏ (i <- [0, 2, 4] | i != 2) i
#check ∏ (10 ≤ i < 20 | i != 5) i+1
#check ∏ (10 ≤ i < 20) i+1

-- We can extend our grammar for the syntax category `index`.
syntax ident "|" term : index
syntax ident ":" term : index
syntax ident ":" term "|" term : index
-- And new rules
macro_rules
| `(_big [$op, $idx] ($i:ident : $type) $F) => `(bigop $idx (Enumerable.elems (α := $type)) $op (fun $i:ident => (true, $F)))
| `(_big [$op, $idx] ($i:ident : $type | $p) $F) => `(bigop $idx (Enumerable.elems (α := $type)) $op (fun $i:ident => ($p, $F)))
| `(_big [$op, $idx] ($i:ident | $p) $F) => `(bigop $idx (Enumerable.elems) $op (fun $i:ident => ($p, $F)))

-- The new syntax is immediately available for all big operators that we have defined
def myPred (x : Fin 10) : Bool := true
#check ∑ (i : Fin 10) i+1
#check ∑ (i : Fin 10 | i != 2) i+1
#check ∑ (i | myPred i) i+i
#check ∏ (i : Fin 10) i+1
#check ∏ (i : Fin 10 | i != 2) i+1
#check ∏ (i | myPred i) i+i

-- We can easily create alternative syntax for any big operator.
syntax "Summation" index "=>" term : term
macro_rules | `(Summation $idx => $F) => `(∑ ($idx) $F)

#check Summation 10 ≤ i < 20 => i+1

-- Now, we create command for automating the generation of big operators.
syntax "def_bigop" str term:max term:max : command
macro_rules
| `(def_bigop $head:str $op $unit) =>
   `(macro $head:str "(" idx:index ")" F:term : term => `(_big [$op, $unit] ($$idx) $$F))

def_bigop "SUM" Nat.add 0
#check SUM (i <- [0, 1, 2]) i+1
