# MIT License, Copyright (c) 2022 Marvin Borner
# with implicit help from Justine Tunney and John Tromp
# classic Church style numerals

:import std/Logic .
:import std/Combinator .
:import std/Pair .

# id for church numerals
# generic base for dec/fib/fac/etc.
uid [[[extract (2 inc init)]]] ⧗ Unary → Unary
	extract &i
	inc [&(0 2)]
	init &0

:test (uid (+0u)) ((+0u))
:test (uid (+1u)) ((+1u))
:test (uid (+5u)) ((+5u))

# returns true if a unary number is zero
zero? [0 [(+0u)] true] ⧗ Unary → Boolean

=?‣ zero?

:test (=?(+0u)) (true)
:test (=?(+42u)) (false)

# adds 1 to a unary number
inc [[[1 (2 1 0)]]] ⧗ Unary → Unary

++‣ inc

:test (++(+0u)) ((+1u))
:test (++(+1u)) ((+2u))
:test (++(+42u)) ((+43u))

# subs 1 from a unary number
dec [[[extract (2 inc const)]]] ⧗ Unary → Unary
	extract &i
	inc [&(0 2)]
	const [1]

--‣ dec

:test (--(+0u)) ((+0u))
:test (--(+1u)) ((+0u))
:test (--(+42u)) ((+41u))

# adds two unary numbers
add [[[[3 1 (2 1 0)]]]] ⧗ Unary → Unary → Unary

…+… add

:test ((+0u) + (+2u)) ((+2u))
:test ((+5u) + (+3u)) ((+8u))

# subs two unary numbers
sub [[0 dec 1]] ⧗ Unary → Unary → Unary

…-… sub

:test ((+2u) - (+2u)) ((+0u))
:test ((+5u) - (+3u)) ((+2u))

# returns true if number is less than or equal to other number
leq? zero? ∘∘ sub ⧗ Unary → Unary → Boolean

…≤?… leq?

:test ((+1u) ≤? (+2u)) (true)
:test ((+2u) ≤? (+2u)) (true)
:test ((+3u) ≤? (+2u)) (false)

# returns true if number is greater than or equal to other number
geq? \leq? ⧗ Unary → Unary → Boolean

…≥?… geq?

:test ((+1u) ≥? (+2u)) (false)
:test ((+2u) ≥? (+2u)) (true)
:test ((+3u) ≥? (+2u)) (true)

# returns true if number is greater than other number
# larger numbers should be second argument (performance)
gre? not! ∘∘ leq? ⧗ Unary → Unary → Boolean

…>?… gre?

:test ((+1u) >? (+2u)) (false)
:test ((+2u) >? (+2u)) (false)
:test ((+3u) >? (+2u)) (true)

# returns true if number is less than other number
# smaller numbers should be second argument (performance)
les? \gre? ⧗ Unary → Unary → Boolean

…<?… les?

:test ((+1u) <? (+2u)) (true)
:test ((+2u) <? (+2u)) (false)
:test ((+3u) <? (+2u)) (false)

# returns true if two unary numbers are equal
eq? [[=?(1 - 0) ⋀? =?(0 - 1)]] ⧗ Unary → Unary → Boolean

…=?… eq?

:test ((+1u) =? (+0u)) (false)
:test ((+1u) =? (+1u)) (true)
:test ((+1u) =? (+2u)) (false)
:test ((+42u) =? (+42u)) (true)

# returns true if two unary numbers are not equal
not-eq? not! ∘∘ eq? ⧗ Unary → Unary → Boolean

…≠?… not-eq?

:test ((+1u) ≠? (+0u)) (true)
:test ((+1u) ≠? (+1u)) (false)
:test ((+42u) ≠? (+42u)) (false)

# returns eq, lt, gt depending on comparison of two numbers
compare-case [[[[[go (1 - 0) (0 - 1)]]]]] ⧗ a → b → c → Unary → Unary → d
	go [[=?0 (=?1 6 5) 4]]

# returns ternary 1 if a>b, -1 if a<b and 0 if a=b
# also: spaceship operator
compare compare-case (+0) (+1) (-1) ⧗ Unary → Unary → Number

:test (compare (+2u) (+2u)) ((+0))
:test (compare (+2u) (+1u)) ((+1))
:test (compare (+1u) (+2u)) ((-1))

…<=>… compare

# muls two unary numbers
mul …∘… ⧗ Unary → Unary → Unary

…⋅… mul

:test ((+0u) ⋅ (+2u)) ((+0u))
:test ((+2u) ⋅ (+3u)) ((+6u))

# divs two unary numbers
div [[[[3 t [1] (3 [3 t [3 (0 1)] i] 0)]]]] ⧗ Unary → Unary → Unary

…/… div

:test ((+8u) / (+4u)) ((+2u))
:test ((+2u) / (+1u)) ((+2u))
:test ((+2u) / (+2u)) ((+1u))
:test ((+2u) / (+3u)) ((+0u))

# slower div (more obvious impl)
div* [z rec ++0] ⧗ Unary → Unary → Unary
	rec [[[[[[=?0 ((+0u) 2 1) (2 (5 0 3 2 1))] (3 - 2)]]]]]

# returns remainder of integer division
mod [[[[3 &k (3 [3 [[[0 (2 (5 1)) 1]]] [1] 1] [1]) ki]]]] ⧗ Unary → Unary → Unary

…%… mod

:test ((+10u) % (+3u)) ((+1u))
:test ((+3u) % (+5u)) ((+3u))

# slower mod (more obvious impl)
mod* [[1 &[[(0 ⋀? (2 ≤? 1)) case-rec case-end]] (1 : true) k]] ⧗ Unary → Unary → Unary
	case-rec (1 - 3) : true
	case-end 1 : false

# exponentiates two unary number
# doesn't give correct results for x^0
pow* [[1 0]] ⧗ Unary → Unary → Unary

# exponentiates two unary numbers
pow [[0 [[3 (1 0)]] pow*]] ⧗ Unary → Unary → Unary

…^… pow

:test ((+2u) ^ (+3u)) ((+8u))
:test ((+3u) ^ (+2u)) ((+9u))

# fibonacci sequence
# index +1 vs std/Math fib
fib [0 [[[2 0 [2 (1 0)]]]] [[1]] [0]] ⧗ Unary → Unary

:test (fib (+6u)) ((+8u))

# factorial function
fac [[1 [[0 (1 [[2 1 (1 0)]])]] [1] i]] ⧗ Unary → Unary

:test (fac (+3u)) ((+6u))

# hyperfactorial function
hyperfac [[1 [[(0 0) (1 [[2 1 (1 0)]])]] [1] i]] ⧗ Unary → Unary

:test (hyperfac (+3u)) ((+108u))