#!/usr/bin/env nomsu -V4.8.10
#
    This file defines some common math literals and functions

use "core/metaprogramming.nom"
use "core/text.nom"
use "core/operators.nom"
use "core/control_flow.nom"
use "core/collections.nom"

# Literals:
test:
    assume (all of [inf, NaN, pi, tau, golden ratio, e]) or barf "\
        ..math constants failed"
    %nan = (NaN)
    assume (%nan != %nan) or barf "NaN failed"
[infinity, inf] all compile to (Lua value "math.huge")
[not a number, NaN, nan] all compile to (Lua value "(0/0)")
[pi, Pi, PI] all compile to (Lua value "math.pi")
[tau, Tau, TAU] all compile to (Lua value "(2*math.pi)")
(golden ratio) compiles to (Lua value "((1+math.sqrt(5))/2)")
(e) compiles to (Lua value "math.exp(1)")

# Functions:
test:
    assume (("5" as a number) == 5)
[% as a number, % as number] all compile to (..)
    Lua value "tonumber(\(% as lua expr))"

test:
    assume (..)
        all of [..]
            abs 5, | 5 |, sqrt 5, √ 5, sine 5, cosine 5, tangent 5, arc sine 5, arc cosine 5
            arc tangent 5, arc tangent 5 / 10, hyperbolic sine 5, hyperbolic cosine 5
            hyperbolic tangent 5, e^ 5, ln 5, log base 2 of 5, floor 5, ceiling 5, round 5
    ..or barf "math functions failed"
[absolute value %, | % |, abs %] all compile to (..)
    Lua value "math.abs(\(% as lua expr))"

[square root %, square root of %, √ %, sqrt %] all compile to (..)
    Lua value "math.sqrt(\(% as lua expr))"

[sine %, sin %] all compile to (Lua value "math.sin(\(% as lua expr))")
[cosine %, cos %] all compile to (Lua value "math.cos(\(% as lua expr))")
[tangent %, tan %] all compile to (Lua value "math.tan(\(% as lua expr))")
[arc sine %, asin %] all compile to (Lua value "math.asin(\(% as lua expr))")
[arc cosine %, acos %] all compile to (Lua value "math.acos(\(% as lua expr))")
[arc tangent %, atan %] all compile to (Lua value "math.atan(\(% as lua expr))")
[arc tangent %y / %x, atan2 %y %x] all compile to (..)
    Lua value "math.atan2(\(%y as lua expr), \(%x as lua expr))"

[hyperbolic sine %, sinh %] all compile to (..)
    Lua value "math.sinh(\(% as lua expr))"

[hyperbolic cosine %, cosh %] all compile to (..)
    Lua value "math.cosh(\(% as lua expr))"

[hyperbolic tangent %, tanh %] all compile to (..)
    Lua value "math.tanh(\(% as lua expr))"

[e^ %, exp %] all compile to (Lua value "math.exp(\(% as lua expr))")
[natural log %, ln %, log %] all compile to (..)
    Lua value "math.log(\(% as lua expr))"

[log % base %base, log base %base of %] all compile to (..)
    Lua value "math.log(\(% as lua expr), \(%base as lua expr))"

(floor %) compiles to (Lua value "math.floor(\(% as lua expr))")
[ceiling %, ceil %] all compile to (Lua value "math.ceil(\(% as lua expr))")
[round %, % rounded] all compile to (..)
    Lua value "math.floor(\(% as lua expr) + .5)"

test:
    assume ((463 to the nearest 100) == 500) or barf "rounding failed"
    assume ((2.6 to the nearest 0.25) == 2.5) or barf "rounding failed"
externally (%n to the nearest %rounder) means (..)
    =lua "(\%rounder)*math.floor((\%n / \%rounder) + .5)"

# Any/all
externally [all of %items, all %items] all mean:
    for % in %items:
        unless %: return (no)
    return (yes)
[all of %items, all %items] all compile to:
    unless (%items.type is "List"):
        return %tree
    %clauses = (((% as lua expr)::text) for % in %items)
    return (Lua value "(\(%clauses::joined with " and "))")
[not all of %items, not all %items] all parse as (not (all of %items))

externally [any of %items, any %items] all mean:
    for % in %items:
        if %: return (yes)
    return (no)
[any of %items, any %items] all compile to:
    unless (%items.type is "List"):
        return %tree
    %clauses = (((% as lua expr)::text) for % in %items)
    return (Lua value "(\(%clauses::joined with " or "))")
[none of %items, none %items] all parse as (not (any of %items))

# Sum/product
externally [sum of %items, sum %items] all mean:
    %total = 0
    for % in %items: %total += %
    return %total
[sum of %items, sum %items] all compile to:
    unless (%items.type is "List"):
        return %tree
    %clauses = (((% as lua expr)::text) for % in %items)
    return (Lua value "(\(%clauses::joined with " + "))")

externally [product of %items, product %items] all mean:
    %prod = 1
    for % in %items: %prod *= %
    return %prod
[product of %items, product %items] all compile to:
    unless (%items.type is "List"):
        return %tree
    %clauses = (((% as lua expr)::text) for % in %items)
    return (Lua value "(\(%clauses::joined with " * "))")

externally [avg of %items, average of %items] all mean (..)
    (sum of %items) / (size of %items)

# Shorthand for control flow
[if all of %items %body, if all of %items then %body] all parse as (..)
    if (all of %items) %body

[unless all of %items %body, unless all of %items then %body] all parse as (..)
    if (not (all of %items)) %body

[if any of %items %body, if any of %items then %body] all parse as (..)
    if (any of %items) %body

[unless any of %items %body, unless any of %items then %body] all parse as (..)
    if (not (any of %items)) %body

[if none of %items %body, if none of %items then %body] all parse as (..)
    if (not (any of %items)) %body

[unless none of %items %body, unless none of %items then %body] all parse as (..)
    if (any of %items) %body

[if all of %items %body else %else, if all of %items then %body else %else] all parse \
..as (if (all of %items) %body else %else)

[..]
    unless all of %items %body else %else, unless all of %items then %body else %else
..all parse as (if (not (all of %items)) %body else %else)

[if any of %items %body else %else, if any of %items then %body else %else] all parse \
..as (if (any of %items) %body else %else)

[..]
    unless any of %items %body else %else, unless any of %items then %body else %else
..all parse as (if (not (any of %items)) %body else %else)

[if none of %items %body else %else, if none of %items then %body else %else] all \
..parse as (if (not (any of %items)) %body else %else)

[..]
    unless none of %items %body else %else, unless none of %items then %body else %else
..all parse as (if (any of %items) %body else %else)

# Min/max
externally [min of %items, smallest of %items, lowest of %items] all mean:
    %best = (nil)
    for % in %items:
        if ((%best == (nil)) or (% < %best)):
            %best = %
    return %best

externally [max of %items, biggest of %items, largest of %items, highest of %items] all mean:
    %best = (nil)
    for % in %items:
        if ((%best == (nil)) or (% > %best)):
            %best = %
    return %best

test:
    assume ((min of [3, -4, 1, 2] by % = (% * %)) == 1)
    assume ((max of [3, -4, 1, 2] by % = (% * %)) == -4)
(min of %items by %item = %value_expr) parses as (..)
    result of:
        %best = (nil)
        %best_key = (nil)
        for %item in %items:
            %key = %value_expr
            if ((%best == (nil)) or (%key < %best_key)):
                %best = %item
                %best_key = %key
        return %best

(max of %items by %item = %value_expr) parses as (..)
    result of:
        %best = (nil)
        %best_key = (nil)
        for %item in %items:
            %key = %value_expr
            if ((%best == (nil)) or (%key > %best_key)):
                %best = %item
                %best_key = %key
        return %best

# Random functions
externally (seed random with %) means (..)
    lua> "\
        ..math.randomseed(\%);
        for i=1,20 do math.random(); end"

(seed random) parses as (seed random with (=lua "os.time()"))
[random number, random, rand] all compile to (Lua value "math.random()")
[random int %n, random integer %n, randint %n] all compile to (..)
    Lua value "math.random(\(%n as lua expr))"

[random from %low to %high, random number from %low to %high, rand %low %high] all \
..compile to (Lua value "math.random(\(%low as lua expr), \(%high as lua expr))")

externally [..]
    random choice from %elements, random choice %elements, random %elements
..all mean (=lua "\%elements[math.random(#\%elements)]")