:- discontiguous test/0, math/4, current/3, paren/3, prec/3, type/3, denoting/3, ml/3.
:- use_module(library(http/html_write)).

%
% R interface
%
r2mathml(A, X) :-
    r2mathml([], A, X).

r2mathml(Flags, A, S) :-
    mathml(Flags, A, M),
    html(M, X, []),
    maplist(atom_string, X, S).

mathml(Flags, A, X) :-
    ml(Flags, A, M),
    denoting(Flags, A, Denoting),
    ml(Flags, with(Denoting), With),
    !, X = [math(M), With].

mathml(Flags, A, X) :-
    ml(Flags, "Conversion failed", X),
    writeln(A).

%
% Macros
%
ml(Flags, A, X),
    math(Flags, A, New, M),
    dif(Flags-A, New-M)
 => ml(New, M, X).

math(Flags, A, New, X),
    member(replace(A, _), Flags)
 => select(replace(A, X), Flags, New).

%
% Greek letters
%
math(Flags, A, New, X),
    atom(A),
    memberchk(A, [alpha, beta, gamma, delta, epsilon, varepsilon, zeta,
        eta, theta, vartheta, iota, kappa, lambda, mu, nu, xi, pi, rho,
        sigma, tau, upsilon, phi, varphi, chi, psi, omega, 'Gamma',
        'Delta', 'Theta', 'Lambda', 'Xi', 'Pi', 'Sigma', 'Upsilon',
        'Phi', 'Psi', 'Omega'])
 => New = Flags,
    X = greek(A).

ml(_Flags, greek(A), X) =>
    X = mi(&(A)).

type(_Flags, greek(_), Type)
 => Type = atomic.

denoting(_Flags, greek(_), Den)
 => Den = [].

test :- test(epsilon).

%
% Sum over index
%
ml(Flags, sum(I, From, To, A), M)
 => ml(Flags, I = From, XFrom),
    ml(Flags, To, XTo),
    ml(Flags, A, X),
    M = mrow([munderover([mo(&(sum)), XFrom, XTo]), X]).

paren(Flags, sum(_, _, _, A), Paren)
 => paren(Flags, A, Paren).

prec(_Flags, sum(_, _, _, _), Prec)
 => current(Prec, yfx, +).

test :- test(sum(i, 1, 10, i)).

%
% Integrate over range
%
% extract value
math(Flags, $(integrate(Fn, Lower, Upper), value), New, M)
 => Flags = New,
    M = integrate(Fn, Lower, Upper).

% with named arguments
math(Flags, integrate(f=Fn, lower=Lower, upper=Upper), New, M)
 => Flags = New,
    M = integrate(Fn, Lower, Upper).

% No argument names
math(Flags, integrate(Fn, Lower, Upper), New, M)
 => Flags = New,
    r_eval('['(formalArgs(args(Fn)), 1), Arg1),
    atom_string(DX, Arg1),
    M = integrate(fn(Fn, [DX]), Lower, Upper, DX).

% Internal
ml(Flags, integrate(Fn, From, To, DX), M)
 => ml(Flags, Fn, XFn),
    ml(Flags, From, XFrom),
    ml(Flags, To, XTo),
    ml(Flags, DX, XDX),
    ml(Flags, space, Space),
    M = mrow([munderover([mo(&(int)), XFrom, XTo]), XFn, Space, mi(d), XDX]).

paren(Flags, integrate(_, _, _, A), Paren)
 => paren(Flags, A, Paren).

prec(_Flags, integrate(_, _, _, _), Prec)
 => current(Prec, yfx, *).

test :- test(integrate(sin, 0, pi)).

% hats
ml(Flags, hat(A), M)
 => ml(Flags, A, X),
	M = mover(accent(true), [X, mo(&('Hat'))]).

paren(Flags, hat(A), Paren)
 => paren(Flags, A, Paren).

prec(Flags, hat(A), Prec)
 => prec(Flags, A, Prec).

type(Flags, hat(A), Type)
 => type(Flags, A, Type).

test :- test(hat('K')).

test :- test(hat('K'^2)).
test :- test(hat('K')^2).
test :- test(hat('sigma')^2).

% tilde
ml(Flags, tilde(A), M)
 => ml(Flags, A, X),
	M = mover(accent(true), [X, mo(&(tilde))]).

paren(Flags, tilde(A), Paren)
 => paren(Flags, A, Paren).

prec(Flags, tilde(A), Prec)
 => prec(Flags, A, Prec).

type(Flags, tilde(A), Type)
 => type(Flags, A, Type).

test :- test(tilde('D')).

test :- test(tilde('X')^2).

%
% Booleans
%
math(Flags, A, New, X),
    atom(A),
    memberchk(A, ['TRUE', 'FALSE'])
 => New = Flags,
    X = boolean(A).

denoting(_Flags, boolean(_), Den)
 => Den = [].

%
% Space
%
math(Flags, space, New, X)
 => New = Flags,
    X = space(thinmathspace).

ml(_Flags, space(Width), M)
 => M = mspace(width(Width), []).

denoting(_Flags, space(_), Den)
 => Den = [].

test :- test(space).

%
% Symbols/Identifiers
%
math(Flags, A, New, X),
    atom(A)
 => New = Flags,
    X = ident(A).

ml(_Flags, ident(A), X)
 => X = mi(A).

type(_Flags, ident(_), Type)
 => Type = atomic.

denoting(_Flags, ident(_), Den)
 => Den = [].

test :- test(i).

%
% Upright text
%
math(Flags, A, New, X),
    string(A)
 => New = Flags,
    X = text(A).

ml(_Flags, text(A), X)
 => X = mtext(A).

type(_Flags, text(_), Type)
 => Type = atomic.

denoting(_Flags, text(_), Den)
 => Den = [].

test :- test("Text").

%
% Mathematical signs
%
ml(_Flags, sign(A), X)
 => X = mo(A).

prec(_Flags, sign(A), Prec),
    current(P, _Fix, A)
 => Prec = P.

current(Prec, yfx, &(sdot))
 => current_op(Prec, yfx, *).

denoting(_Flags, sign(_), Den)
 => Den = [].

test :- test(sign(&(sdot))).

%
% Indices like s_D
%
math(Flags, '['(A, Idx), New, X)
 => math(Flags, sub(A, Idx), New, X).

%
% Check for sub(sup(A, Power), Index)
%
math(Flags, sub(A, Idx), New, X),
    type(Flags, A, sup(Bas, Pwr))
 => New = [replace(sup(Bas, Pwr), subsup(Bas, Idx, Pwr)) | Flags],
    X = A.

%
%
% Render
%
math(Flags, sub(A, Idx), New, X),
    prec(Flags, sub(A, Idx), Outer),
    prec(Flags, A, Inner),
    Outer < Inner
 => New = Flags,
    X = sub(paren(A), Idx).

ml(Flags, sub(A, B), M)
 => ml(Flags, A, X),
    ml(Flags, B, Y),
    M = msub([X, Y]).

paren(Flags, sub(A, _), Paren)
 => paren(Flags, A, Paren).

prec(Flags, sub(A, _), Prec)
 => prec(Flags, A, Prec).

type(_Flags, sub(A, B), Type)
 => Type = sub(A, B).

test :- test(sub(s, 'D')).
test :- test(sub(s^r, 'D')).

%
% Powers like s^D
%
% Check for sup(sub(A, Index), Power)
%
math(Flags, sup(A, Pwr), New, X),
    type(Flags, A, sub(Bas, Idx))
 => New = [replace(sub(Bas, Idx), subsup(Bas, Idx, Pwr)) | Flags],
    X = A.

%
% Render
%
math(Flags, sup(A, Pwr), New, X),
    prec(Flags, sup(A, Pwr), Outer),
    prec(Flags, A, Inner),
    Outer < Inner
 => New = Flags,
    X = sup(paren(A), Pwr).

ml(Flags, sup(A, B), M)
 => ml(Flags, A, X),
    ml(Flags, B, Y),
    M = msup([X, Y]).

paren(Flags, sup(A, _), Paren)
 => paren(Flags, A, Paren).

prec(_Flags, sup(_, _), Prec)
 => current(Prec, xfy, ^).

type(_Flags, sup(A, B), Type)
 => Type = sup(A, B).

test :- test(sub(s, 'D')).
test :- test(sub(s^2, 'D')).

%
% Index and Exponent: s_D^2
%
math(Flags, subsup(A, Idx, Pwr), New, X),
    prec(Flags, subsup(A, Idx, Pwr), Outer),
    prec(Flags, A, Inner),
    Outer < Inner
 => New = Flags,
    X = subsup(paren(A), Idx, Pwr).

ml(Flags, subsup(A, B, C), M)
 => ml(Flags, A, X),
    ml(Flags, B, Y),
    ml(Flags, C, Z),
    M = msubsup([X, Y, Z]).

paren(Flags, subsup(A, _, _), Paren)
 => paren(Flags, A, Paren).

prec(Flags, subsup(A, _, C), Prec)
 => prec(Flags, sup(A, C), Prec).

type(_Flags, subsup(A, B, C), Type)
 => Type = subsup(A, B, C).

test :- test(subsup(s, 'D', r)).

%
% Numbers
%
math(Flags, A, New, X),
    integer(A),
    A >= 0
 => New = Flags,
    X = posint(A).

math(Flags, A, New, X),
    integer(A)
 => New = Flags,
    X = integer(A).

math(Flags, integer(A), New, X),
    A >= 0
 => New = Flags,
    X = posint(A).

math(Flags, integer(A), New, X),
    A < 0
 => New = Flags,
    Abs is abs(A),
    X = -posint(Abs).

math(Flags, A, New, X),
    number(A),
    A >= 0
 => New = Flags,
    X = pos(A).

math(Flags, A, New, X),
    number(A)
 => New = Flags,
    X = number(A).

denoting(_Flags, posint(_), Den)
 => Den = [].

denoting(_Flags, pos(_), Den)
 => Den = [].

denoting(_Flags, number(_), Den)
 => Den = [].

ml(_Flags, posint(A), M)
 => M = mn(A).

ml(_Flags, pos(1.0Inf), M)
 => M = mi(&('#x221E')).

ml(Flags, pos(A), M)
 => option(round(D), Flags, 2),
    D =< 99,
    format(codes(X), '~99f', [A]),
    nth1(Dot, X, 46),
    N is Dot + D,
    findall(E, (nth1(I, X, E), I =< N), Round),
    string_codes(S, Round),
    M = mn(S).

test :- test(3).
test :- test(-3).
test :- test(pi).

math(Flags, number(A), New, X),
    A < 0
 => New = Flags,
    Abs is abs(A),
    X = -pos(Abs).

math(Flags, number(A), New, X)
 => New = Flags,
    X = pos(A).

test :- test(-pi).

%
% Operators
%
math(Flags, A = B, New, X)
 => New = Flags,
    current_op(Prec, xfx, =),
    X = yfy(Prec, =, A, B).

math(Flags, A \= B, New, X)
 => New = Flags,
    current_op(Prec, xfx, \=),
    X = xfx(Prec, &(ne), A, B).

math(Flags, A < B, New, X)
 => New = Flags,
    current_op(Prec, xfx, <),
    X = yfy(Prec, <, A, B).

math(Flags, A =< B, New, X)
 => New = Flags,
    current_op(Prec, xfx, =<),
    X = yfy(Prec, &(le), A, B).

math(Flags, ~(A, B), New, X)
 => New = Flags,
    current_op(Prec, xfx, =),
    X = yfy(Prec, &('Tilde'), A, B).

math(Flags, A > B, New, X)
 => New = Flags,
    current_op(Prec, xfx, >),
    X = yfy(Prec, >, A, B).

math(Flags, A >= B, New, X)
 => New = Flags,
    current_op(Prec, xfx, >=),
    X = yfy(Prec, &(ge), A, B).

math(Flags, +A, New, X)
 => New = Flags,
    current_op(Prec, yfx, +),
    X = fy(Prec, +, A).

math(Flags, A + B, New, X)
 => New = Flags,
    current_op(Prec, yfx, +),
    X = yfy(Prec, +, A, B).

math(Flags, -A, New, X)
 => New = Flags,
    current_op(Prec, yfx, -),
    X = fy(Prec, -, A).

math(Flags, A - B, New, X)
 => New = Flags,
    current_op(Prec, yfx, -),
    X = yfy(Prec, -, A, B).

% Use dot or no dot instead of asterisk
math(Flags, A * B, New, X),
    type(Flags, B, Type),
    Type = atomic
 => New = Flags,
    X = nodot(A, B).

math(Flags, A * B, New, X)
 => New = Flags,
    X = dot(A, B).

test :- test(a * b).
test :- test(a * (b * c)).
test :- test((a * b) * c).
test :- test((2 * b) * c).
test :- test((-2 * b) * c).
test :- test((-2 * 2) * c).
test :- test((-2 * -2) * c).

math(Flags, dot(A, B), New, X)
 => New = Flags,
    current_op(Prec, yfx, *),
    X = yfy(Prec, &(sdot), A, B).

math(Flags, nodot(A, B), New, X)
 => New = Flags,
    current_op(Prec, yfx, *),
    X = yfy(Prec, &('#x2062'), A, B).

math(Flags, A / B, New, X)
 => New = Flags,
    current_op(Prec, yfx, /),
    X = yfx(Prec, /, A, B).

math(Flags, (A ; B), New, X)
 => New = Flags,
    current_op(Prec, xfy, ;),
    X = xfy(Prec, ;, A, B).

math(Flags, A^B, New, X)
 => New = Flags,
    X = sup(A, B).

%
% Render
%
ml(Flags, fy(Prec, Op, A), M)
 => ml(Flags, sign(Op), S),
    ml(Flags, right(Prec, A), X),
    M = mrow([S, X]).

ml(Flags, xfx(Prec, Op, A, B), M)
 => ml(Flags, left(Prec-1, A), X),
    ml(Flags, sign(Op), S),
    ml(Flags, right(Prec-1, B), Y),
    M = mrow([X, S, Y]).

ml(Flags, yfx(Prec, Op, A, B), M)
 => ml(Flags, left(Prec, A), X),
    ml(Flags, sign(Op), S),
    ml(Flags, right(Prec-1, B), Y),
    M = mrow([X, S, Y]).

ml(Flags, xfy(Prec, Op, A, B), M)
 => ml(Flags, left(Prec-1, A), X),
    ml(Flags, sign(Op), S),
    ml(Flags, right(Prec, B), Y),
    M = mrow([X, S, Y]).

ml(Flags, yfy(Prec, Op, A, B), M)
 => ml(Flags, left(Prec, A), X),
    ml(Flags, sign(Op), S),
    ml(Flags, right(Prec, B), Y),
    M = mrow([X, S, Y]).

denoting(Flags, fy(_, A, B), Den)
 => denoting(Flags, A, DenA),
    denoting(Flags, B, DenB),
    append(DenA, DenB, Den).

denoting(Flags, xfx(_, _, A, B), Den)
 => denoting(Flags, A, DenA),
    denoting(Flags, B, DenB),
    append(DenA, DenB, Den).

denoting(Flags, xfy(_, _, A, B), Den)
 => denoting(Flags, A, DenA),
    denoting(Flags, B, DenB),
    append(DenA, DenB, Den).

denoting(Flags, yfx(_, _, A, B), Den)
 => denoting(Flags, A, DenA),
    denoting(Flags, B, DenB),
    append(DenA, DenB, Den).

denoting(Flags, yfy(_, _, A, B), Den)
 => denoting(Flags, A, DenA),
    denoting(Flags, B, DenB),
    append(DenA, DenB, Den).

prec(_Flags, fy(P, _, _), Prec)
 => Prec = P.

prec(_Flags, xfx(P, _, _, _), Prec)
 => Prec = P.

prec(_Flags, yfx(P, _, _, _), Prec)
 => Prec = P.

prec(_Flags, xfy(P, _, _, _), Prec)
 => Prec = P.

prec(_Flags, yfy(P, _, _, _), Prec)
 => Prec = P.

math(Flags, left(Prec, A), New, X),
    prec(Flags, A, P),
    P > Prec
 => New = Flags,
    X = paren(A).

math(Flags, left(_, A), New, X)
 => New = Flags,
    X = A.

math(Flags, right(Prec, A), New, X)
 => New = Flags,
    X = left(Prec, A).

denoting(Flags, left(_, A), Den)
 => denoting(Flags, A, Den).

denoting(Flags, right(_, A), Den)
 => denoting(Flags, A, Den).

test :- test(a * b).
test :- test((a + b) * (c + d)).
test :- test(a * b + c * d).
test :- test(a + b + c + d).
test :- test((a - b) - (c + d)).
test :- test(dot((a - b), (c + d))).
test :- test(-2 * -2).

%
% Abbreviations
%
% with s^2_pool denoting the pooled variance
%
ml(Flags, with(A, _, _), X)
 => ml(Flags, A, X).

paren(Flags, with(A, _, _), Paren)
 => paren(Flags, A, Paren).

prec(Flags, with(A, _, _), Prec)
 => prec(Flags, A, Prec).

type(Flags, with(A, _, _), Type)
 => type(Flags, A, Type).

denoting(Flags, with(A, Expr, Info), Den)
 => denoting(Flags, Expr, T),
    Den = [denoting(A, Expr, Info) | T].

test :-
    S2P = with(sub(s, "pool")^2,
                   frac((sub('N', "A") - 1) * sub(s, "A")^2 +
                        (sub('N', "B") - 1) * sub(s, "B")^2,
                        sub('N', "A") + sub('N', "B") - 2),
                   "the pooled variance"),
    test(frac(sub(overline('X'), "A") - sub(overline('X'), "B"),
                  sqrt(nodot(S2P, 1/sub('N', "A") + 1/sub('N', "B"))))).

%
% Expand abbreviations
%
ml(Flags, denoting(A, Expr, Info), X)
 => ml(Flags, list(space, [A = Expr, "denoting", Info]), X).

type(Flags, denoting(A, _, _), Type)
 => type(Flags, A, Type).

denoting(_Flags, denoting(_, _, _), Den)
 => Den = [].

%
% Collect abbreviations
%
ml(Flags, with(Abbreviations), X)
 => sort(Abbreviations, Sorted), % remove duplicates
    ml(Flags, with_(Sorted), X).

ml(_Flags, with_([]), W)
 => W = " ".

ml(Flags, with_([A]), W)
 => ml(Flags, A, X),
    W = span([", with", &(nbsp), math(X)]).

ml(Flags, with_([A, B | T]), W)
 => ml(Flags, A, X),
    ml(Flags, and([B | T]), Y),
    W = span([", with", &(nbsp), math(X) | Y]).

ml(_Flags, and([]), W)
 => W = ".".

ml(Flags, and([A | T]), W)
 => ml(Flags, A, X),
    ml(Flags, and(T), Y),
    W = span([", and", &(nbsp), math(X) | Y]).

%
% Parentheses
%
ml(Flags, paren(A), X),
    paren(Flags, A, P),
    0 is P mod 3
 => ml(Flags, parentheses(A), X).

ml(Flags, paren(A), X),
    paren(Flags, A, P),
    1 is P mod 3
 => ml(Flags, brackets(A), X).

ml(Flags, paren(A), X),
    paren(Flags, A, P),
    2 is P mod 3
 => ml(Flags, braces(A), X).

paren(Flags, paren(A), Paren)
 => paren(Flags, A, P),
    Paren is P + 1.

ml(Flags, parentheses(A), M)
 => ml(Flags, A, X),
    M = mrow([mo('('), X, mo(')')]).

paren(_Flags, parentheses(_), Paren)
 => Paren is 1.

ml(Flags, brackets(A), M)
 => ml(Flags, A, X),
    M = mrow([mo('['), X, mo(']')]).

paren(_Flags, brackets(_), Paren)
 => Paren is 2.

ml(Flags, braces(A), M)
 => ml(Flags, A, X),
    M = mrow([mo('{'), X, mo('}')]).

paren(_Flags, braces(_), Paren)
 => Paren is 3.

test :- test(paren(paren(paren(paren(i))))).

%
% Lists of things
%
math(Flags, [H | T], New, M)
 => Flags = New, 
    M = list(space, [H | T]).

ml(Flags, list(_, [A]), M)
 => ml(Flags, A, M).

ml(Flags, list(Sep, [A, B | T]), M)
 => ml(Flags, A, X),
    ml(Flags, tail(Sep, [B | T]), Y),
    M = mrow([X | Y]).

ml(Flags, tail(Sep, [A]), M)
 => ml(Flags, Sep, S),
    ml(Flags, A, X),
    M = [S, X].

ml(Flags, tail(Sep, [A, B | T]), M)
 => ml(Flags, Sep, S),
    ml(Flags, A, X),
    ml(Flags, tail(Sep, [B | T]), Y),
    M = [S, X | Y].

paren(Flags, list(_, List), Paren)
 => maplist(paren(Flags), List, Parens),
    max_list(Parens, Paren).

prec(Flags, list(_, [A]), Prec)
 => prec(Flags, A, Prec).

prec(Flags, list(Sep, [_, _ | _]), Prec)
 => prec(Flags, Sep, Prec).

denoting(Flags, list(_, L), Den)
 => maplist(denoting(Flags), L, List),
    append(List, Den).

test :- test(list(space, [i, j, k])).

%
% Fractions
%
ml(Flags, frac(N, D), M)
 => ml(Flags, N, X),
    ml(Flags, D, Y),
    M = mfrac([X, Y]).

paren(_Flags, frac(_, _), Paren)
 => Paren = 0.

prec(_Flags, frac(_, _), Prec)
 => current(P, yfx, /),
    Prec is P - 1.

test :- test(frac(1, pi)).

%
% Large fraction
%
math(Flags, dfrac(N, D), New, X)
 => New = Flags,
    X = display(frac(N, D)).

test :- test(dfrac(1, pi)).

%
% Square root
%
ml(Flags, sqrt(A), M)
 => ml(Flags, A, X),
    M = msqrt(X).

prec(_Flags, sqrt(_), Prec)
 => current(P, xfy, ^),
    Prec is P + 1.

test :- test(sqrt(2)).
test :- test(sqrt(2)^2).

%
% Sum over index
%
ml(Flags, sum(I, From, To, A), M)
 => ml(Flags, I = From, XFrom),
    ml(Flags, To, XTo),
    ml(Flags, A, X),
    M = mrow([munderover([mo(&(sum)), XFrom, XTo]), X]).

paren(Flags, sum(_, _, _, A), Paren)
 => paren(Flags, A, Paren).

prec(_Flags, sum(_, _, _, _), Prec)
 => current(Prec, yfx, +).

test :- test(sum(i, 1, 10, i)).

%
% Intgrate over range
%
ml(Flags, integrate(Fn, From, To, DX), M)
 => ml(Flags, Fn, XFn),
    ml(Flags, From, XFrom),
    ml(Flags, To, XTo),
    ml(Flags, DX, XDX),
    ml(Flags, space, Space),
    M = mrow([munderover([mo(&(int)), XFrom, XTo]), XFn, Space, mi(d), XDX]).

paren(Flags, integrate(_, _, _, A), Paren)
 => paren(Flags, A, Paren).

prec(_Flags, integrate(_, _, _, _), Prec)
 => current(Prec, yfx, +).

test :- test(integrate(fn(f, [paren(x)]), 1, 10, x)).

%
% Large font ("displaystyle")
%
ml(Flags, display(A), M)
 => ml(Flags, A, X),
    M = mstyle(displaystyle(true), X).

type(Flags, display(A), Type)
 => type(Flags, A, Type).

%
% Decorations
%
ml(Flags, overline(A), M)
 => ml(Flags, A, X),
    M = mover(accent(true), [X, mo(&(macr))]).

paren(Flags, overline(A), Paren)
 => paren(Flags, A, Paren).

% Put overline(x)^2 in parentheses
prec(_Flags, overline(_), Prec)
 => current(Prec, yfx, *).

type(Flags, overline(A), Type)
 => type(Flags, A, Type).

test :- test(overline('D')).

%
% Cancel out
%
ml(Flags, cancel(A), M)
 => ml(Flags, A, X),
    M = menclose(notation(updiagonalstrike), X).

paren(Flags, cancel(A), Paren)
 => paren(Flags, A, Paren).

prec(Flags, cancel(A), Prec)
 => prec(Flags, A, Prec).

type(Flags, cancel(A), Type)
 => type(Flags, A, Type).

test :- test(cancel('D')).

%
% Box
%
ml(Flags, box(A), M)
 => ml(Flags, A, X),
    M = menclose(notation(roundedbox), X).

paren(Flags, box(A), Paren)
 => paren(Flags, A, Paren).

prec(Flags, box(A), Prec)
 => prec(Flags, A, Prec).

type(Flags, box(A), Type)
 => type(Flags, A, Type).

test :- test(box('D')).

%
% Underbrace
%
ml(Flags, underbrace(A, U), M)
 => ml(Flags, A, X),
    ml(Flags, U, Y),
    M = munder([munder(accentunder(true),
                  [Y, mo(stretchy(true), &('UnderBrace'))]), X]).

paren(Flags, underbrace(A, _), Paren)
 => paren(Flags, A, Paren).

prec(Flags, underbrace(A, _), Prec)
 => prec(Flags, A, Prec).

type(Flags, underbrace(A, _), Type)
 => type(Flags, A, Type).

test :- test(underbrace('D', u)).

%
% Mistakes
%
math(Flags, omit_left(Expr), New, M),
    option(error(ignore), Flags, highlight)
 => Flags = New,
    M = Expr.

math(Flags, omit_left(Expr), New, M),
    option(error(fix), Flags, highlight),
    Expr =.. [Op, L, R]
 => Flags = New,
    M = list(space, [box(list(space, [L, sign(Op)])), R]).

math(Flags, omit_left(Expr), New, M),
    option(error(highlight), Flags, highlight),
    Expr =.. [Op, L, R]
 => Flags = New,
    M = list(space, [cancel(list(space, [L, sign(Op)])), R]).

math(Flags, omit_right(Expr), New, M),
    option(error(ignore), Flags, highlight)
 => Flags = New,
    M = Expr.

math(Flags, omit_right(Expr), New, M),
    option(error(fix), Flags, highlight),
    Expr =.. [Op, L, R]
 => Flags = New,
    M = list(space, [L, box(list(space, [sign(Op), R]))]).

math(Flags, omit_right(Expr), New, M),
    option(error(highlight), Flags, highlight),
    Expr =.. [Op, L, R]
 => Flags = New,
    M = list(space, [L, cancel(list(space, [sign(Op), R]))]).

math(Flags, instead(_Wrong, Correct), New, M),
    option(error(ignore), Flags, highlight)
 => Flags = New,
    M = Correct.

math(Flags, instead(_Wrong, Correct), New, M),
    option(error(fix), Flags, highlight)
 => Flags = New,
    M = box(Correct).

math(Flags, instead(Wrong, Correct), New, M),
    option(error(highlight), Flags, highlight)
 => Flags = New,
    M = underbrace(list(space, ["instead of", Correct]), Wrong).

test :- test(dfrac(omit_right(overline('D') - mu),
                   sub(s, 'D') / sqrt('N'))).

test :- test(dfrac(overline('D') - mu,
                   sub(s, 'D') / instead('N', sqrt('N')))).

%
% Expert and buggy rules
%
math(Flags, expert(Flags, _, B), New, X)
 => New = Flags,
    X = B.

math(Flags, buggy(Flags, _, B), New, X)
 => New = Flags,
    X = B.

%
% Binomial coefficient and distribution
%
ml(Flags, choose(N, K), M)
 => ml(Flags, N, X),
    ml(Flags, K, Y),
    M = mrow([mo('('), mfrac([linethickness(0)], [X, Y]), mo(')')]).

paren(_Flags, choose(_, _), Paren)
 => Paren = 1.

prec(_Flags, choose(_, _), Prec)
 => Prec = 0.

type(_Flags, choose(_, _), Type)
 => Type = paren.

test :- test(choose('N', k)).

test :- test(choose('N', k) * pi^k * (1 - pi)^('N' - k)).

% Density, distribution etc.
math(Flags, dbinom(K, N, Pi), New, X)
 => New = Flags,
    X = fn(sub('P', "Bi"), (['X' = K] ; [N, Pi])).

math(Flags, pbinom(K, N, Pi), New, X)
 => New = Flags,
    X = fn(sub('P', "Bi"), (['X' =< K] ; [N, Pi])).

math(Flags, upbinom(K, N, Pi), New, X)
 => New = Flags,
    X = fn(sub('P', "Bi"), (['X' >= K] ; [N, Pi])).

math(Flags, cbinom(Alpha, N, Pi, Tail, Dist), New, X)
 => New = Flags,
    X = fn(Tail, [fn(sub('P', "Bi"), ([Dist] ; [N, Pi])) =< Alpha]).

math(Flags, tail("upper"), New, X)
 => New = Flags,
    X = sub("argmin", k).

math(Flags, tail("lower"), New, X)
 => New = Flags,
    X = sub("argmax", k).

math(Flags, tail("upperdens"), New, X)
 => New = Flags,
    X = sub("argmin", k > 'N' * pi).

math(Flags, tail("lowerdens"), New, X)
 => New = Flags,
    X = sub("argmax", k < 'N' * pi).

math(Flags, dist("upper"), New, X)
 => New = Flags,
    X = ('X' >= k).

math(Flags, dist("lower"), New, X)
 => New = Flags,
    X = ('X' =< k).

math(Flags, dist("density"), New, X)
 => New = Flags,
    X = ('X' = k).

test :- test(dbinom(k, 'N', pi)).
test :- test(pbinom(k, 'N', pi)).
test :- test(upbinom(k, 'N', pi)).
test :- test(cbinom(alpha, 'N', pi, tail("lower"), dist("lower"))).
test :- test(cbinom(alpha, 'N', pi, tail("upper"), dist("upper"))).
test :- test(cbinom(alpha, 'N', pi, tail("lowerdens"), dist("density"))).
test :- test(cbinom(alpha, 'N', pi, tail("upperdens"), dist("density"))).

%
% Trigonometry
%
math(Flags, sin(Alpha), New, X)
 => New = Flags,
    X = fn("sin", [Alpha]).

math(Flags, cos(Alpha), New, X)
 => New = Flags,
    X = fn("cos", [Alpha]).

math(Flags, tan(Alpha), New, X)
 => New = Flags,
    X = fn("tan", [Alpha]).

%
% Functions like f(x) and f(x; a, b)
%
ml(Flags, fn(Name, (Args ; Params)), M)
 => ml(Flags, Name, F),
    ml(Flags, paren(list(sign(';'), [list(sign(','), Args), list(sign(','), Params)])), X),
    M = mrow([F, mo(&(af)), X]).

paren(Flags, fn(_Name, (Args ; Params)), Paren)
 => paren(Flags, list(sign(','), Args), X),
    paren(Flags, list(sign(','), Params), Y),
    Paren is max(X, Y).

prec(Flags, fn(_Name, (_Args ; _Params)), Prec)
 => prec(Flags, a * b, Prec).

type(_Flags, fn(_Name, (_Args ; _Params)), Type)
 => Type = paren.

ml(Flags, fn(Name, [Arg]), M),
    member(Name, ["sin", "cos", "tan"]),
    prec(Flags, Arg, P),
    P = 0
 => ml(Flags, Name, F),
    ml(Flags, Arg, X),
    M = mrow([F, mo(&(af)), X]).

ml(Flags, fn(Name, [Arg]), M)
 => ml(Flags, Name, F),
    ml(Flags, paren([Arg]), X),
    M = mrow([F, mo(&(af)), X]).

ml(Flags, fn(Name, Args), M)
 => ml(Flags, Name, F),
    ml(Flags, paren(list(sign(','), Args)), X),
    M = mrow([F, mo(&(af)), X]).

paren(Flags, fn(_Name, Args), Paren)
 => paren(Flags, list(sign(','), Args), Paren).

prec(_Flags, fn(_Name, _Args), Prec)
 => current(Prec, yfx, *).

type(_Flags, fn(_Name, _Args), Type)
 => Type = paren.

%
% Defaults
%
math(Flags, A, New, X)
 => A = X,
    New = Flags.

paren(Flags, A, Den),
    math(Flags, A, New, M),
    dif(Flags-A, New-M)
 => paren(New, M, Den).

paren(_, _, P) =>
    P = 0.

prec(Flags, A, Den),
    math(Flags, A, New, M),
    dif(Flags-A, New-M)
 => prec(New, M, Den).

prec(_, _, P) =>
    P = 0.

type(Flags, A, Type),
    math(Flags, A, New, M),
    dif(Flags-A, New-M)
 => type(New, M, Type).

type(_Flags, A, Type),
    compound(A)
 => Type = compound.

denoting(Flags, A, Den),
    math(Flags, A, New, M),
    dif(Flags-A, New-M)
 => denoting(New, M, Den).

denoting(Flags, Expression, Den),
    compound(Expression)
 => compound_name_arguments(Expression, _, Arguments),
    maplist(denoting(Flags), Arguments, List),
    append(List, Den).

% If everything fails, there is no abbreviation
denoting(_Flags, _, Den)
 => Den = [].

% Precedence
current(Prec, Fix, Op),
    atom(Op)
 => current_op(Prec, Fix, Op).

%
% Tests
%
test(A) :-
    writeln(A),
    mathml([], A, M),
    html(math(M)).