/* TeXForm: convert Yacas objects to TeX math mode strings */

/* version 0.4 */

/* Changelog
	0.1	basic functionality
	0.2 fixed bracketing of Exp, added all infix ops and math functions
	0.3 fixed bracketing of lists, changed bracketing of math functions, modified TeX representation of user-defined functions (up to two-letter functions are in italics), added TeX Greek letters
	0.4 added nth roots, Sum, Limit, Integrate, hyperbolics, set operations, Abs, Max, Min, "==", ":=", Infinity; support indexed expressions A[i] and matrices.
	0.4.1 bugfixes for [] operator, support for multiple indices a[1][2][3]
	0.4.2 fix for variable names ending on digits "a2" represented as $a_2$
	0.4.3 bugfixes: complex I, indeterminate integration; relaxed bracketing of Sin()-like functions; implemented $TeX$ and $LaTeX$ correctly now (using \textrm{})
	0.4.4 use ordinary instead of partial derivative if expression has only one variable
	0.4.5 fixes for bracketing of Sum(); added <> to render as \sim and <=> to render as \approx; added Bin()
	0.4.6 moved the <> and <=> operators to stdopers.ys
	0.4.7 added Factorize() i.e. Product()
	0.4.8 added D(x,n), Deriv(x,n), =>, and fixed errors with ArcSinh, ArcCosh, ArcTanh
	0.4.9 fixed omission: (fraction)^n was not put in brackets
	0.4.10 cosmetic change: insert \cdot between numbers in cases like 2*10^n
	0.4.11 added DumpErrors() to TexForm for the benefit of TeXmacs notebooks
	0.4.12 implement the % operation as Mod
	0.4.13 added Bessel{I,J,K,Y}, Ortho{H,P,T,U}, with a general framework for usual two-argument functions of the form $A_n(x)$; fix for Max, Min
	0.4.14 added mathematical notation for Floor(), Ceil()
	0.4.15 added Prog() represented by ( )
	0.4.16 added Zeta()
*/

/* To do:
	0. Find and fix bugs.
	1. The current bracketing approach has limitations: can't omit extra brackets sometimes. " sin a b" is ambiguous, so need to do either "sin a sin b" or "(sin a) b" Hold((a*b)*Sqrt(x)). The current approach is *not* to bracket functions unless the enveloping operation is more binding than multiplication. This produces "sin a b" for both Sin(a*b) and Sin(a)*b but this is the current mathematical practice.
	2. Need to figure out how to deal with variable names such as "alpha3"
*/

/// TeXmacs prettyprinter
TexForm(_expr) <-- [DumpErrors();WriteString(TeXForm(expr));NewLine();];

RuleBase("TeXForm",{expression});
RuleBase("TeXForm",{expression, precedence});

/* Boolean predicate */


/* this function will put TeX brackets around the string if predicate holds */

Function ("TeXFormBracketIf", {predicate, string})
[
	Check(IsBoolean(predicate) And IsString(string), "TeXForm internal error: non-boolean and/or non-string argument of TeXFormBracketIf");
	If(predicate, ConcatStrings("\\left( ", string, "\\right) "), string);
];

/* First, we convert TeXForm(x) to TeXForm(x, precedence). The enveloping precedence will determine whether we need to bracket the results. So TeXForm(x, TeXFormMaxPrec()) will always print "x", while TeXForm(x,-TeXFormMaxPrec()) will always print "(x)".
*/

TeXFormMaxPrec() := 60000;	 /* This precedence will never be bracketed. It is equal to KMaxPrec */

If (Not IsNumber(TeXForm'FuncPrec), TeXForm'FuncPrec := OpPrecedence("*"));

/// main front-end
100 # TeXForm(_x) <-- ConcatStrings("$", TeXForm(x, TeXFormMaxPrec()), "$");

/* Replace numbers and variables -- never bracketed except explicitly */

110 # TeXForm(x_IsNumber, _p) <-- String(x);
/* Variables */
200 # TeXForm(x_IsAtom, _p) <-- TeXFormTeXify(String(x));

/* Strings must be quoted but not bracketed */
100 # TeXForm(x_IsString, _p) <-- ConcatStrings("\\mathrm{", x, "}");

/* Listify(...) can generate lists with atoms that would otherwise result in unparsable expressions. */
100 # TeXForm(x_IsAtom, _p)_(IsInfix(String(x))) <-- ConcatStrings("\\mathrm{", String(x), "}");


/* Lists: make sure to have matrices processed before them. Enveloping precedence is irrelevant because lists are always bracketed. List items are never bracketed. Note that TeXFormFinishList({a,b}) generates ",a,b" */

100 # TeXForm(x_IsList, _p)_(Length(x)=0) <-- TeXFormBracketIf(True, "");
110 # TeXForm(x_IsList, _p) <-- TeXFormBracketIf(True, ConcatStrings(TeXForm(Head(x), TeXFormMaxPrec()), TeXFormFinishList(Tail(x)) ) );
100 # TeXFormFinishList(x_IsList)_(Length(x)=0) <-- "";
110 # TeXFormFinishList(x_IsList) <-- ConcatStrings(", ", TeXForm(Head(x), TeXFormMaxPrec()), TeXFormFinishList(Tail(x)));

/* Replace operations */


	/* Template for "regular" binary infix operators:
100 # TeXForm(_x + _y, _p) <-- TeXFormBracketIf(p<OpPrecedence("+"), ConcatStrings(TeXForm(x, OpLeftPrecedence("+")), " + ", TeXForm(y, OpRightPrecedence("+")) ) );
	*/
// special cases: things like x*2 and 2*10^x look ugly without a period
// cases like x*2
115 # TeXForm(_expr * n_IsNumber, _p) <-- TeXFormBracketIf(p<OpPrecedence("*"), ConcatStrings(TeXForm(expr, OpLeftPrecedence("*")), "\\cdot ", TeXForm(n, OpRightPrecedence("*")) ) );
// cases like a*20! and a*10^x
116 # TeXForm(_n * _expr, _p) _ (IsFunction(expr) And Contains({"^", "!", "!!"}, Type(expr)) And IsNumber(Listify(expr)[2])) <-- TeXFormBracketIf(p<OpPrecedence("*"), ConcatStrings(TeXForm(n, OpLeftPrecedence("*")), "\\cdot ", TeXForm(expr, OpRightPrecedence("*")) ) );

	/* generic binary ops here */
120 # TeXForm(expr_IsFunction, _p)_(NrArgs(expr)=2 And IsInfix(Type(expr)) ) <-- TeXFormBracketIf(p<OpPrecedence(Type(expr)), ConcatStrings(TeXForm(Listify(expr)[2], OpLeftPrecedence(Type(expr))), TeXFormTeXify(Type(expr)), TeXForm(Listify(expr)[3], OpRightPrecedence(Type(expr))) ) );

	/* Template for "regular" unary prefix operators:
100 # TeXForm(+ _y, _p) <-- TeXFormBracketIf(p<OpPrecedence("+"), ConcatStrings(" + ", TeXForm(y, OpRightPrecedence("+")) ) );
	Note that OpRightPrecedence needs to be defined for prefix ops or else we won't be able to tell combined prefix/infix ops like "-" from strictly prefix ops.
	*/

	/* generic unary ops here */
	/* prefix */
120 # TeXForm(expr_IsFunction, _p)_(NrArgs(expr)=1 And IsPrefix(Type(expr))) <-- TeXFormBracketIf(p<OpPrecedence(Type(expr)), ConcatStrings(
	TeXFormTeXify(Type(expr)),
	TeXForm(Listify(expr)[2], OpRightPrecedence(Type(expr)))
) );
	/* postfix */
120 # TeXForm(expr_IsFunction, _p)_(NrArgs(expr)=1 And IsPostfix(Type(expr))) <-- TeXFormBracketIf(p<OpLeftPrecedence(Type(expr)), ConcatStrings(
	TeXForm(Listify(expr)[2], OpLeftPrecedence(Type(expr))),
	TeXFormTeXify(Type(expr))
) );

	/* fraction and its operands are never bracketed except when they are under power */
100 # TeXForm(_x / _y, _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings("\\frac{", TeXForm(x, TeXFormMaxPrec()), "}{", TeXForm(y, TeXFormMaxPrec()), "} ") );

	/* power's argument is never bracketed but it must be put in braces. Chained powers must be bracketed. Powers of 1/n are displayed as roots. */
100 # TeXForm(_x ^ (1/2), _p) <-- ConcatStrings("\\sqrt{", TeXForm(x, TeXFormMaxPrec()), "}");
101 # TeXForm(_x ^ (1/_y), _p) <-- ConcatStrings("\\sqrt[", TeXForm(y, TeXFormMaxPrec()), "]{", TeXForm(x, TeXFormMaxPrec()), "}");


120 # TeXForm(_x ^ _y, _p) <-- TeXFormBracketIf(p<=OpPrecedence("^"), ConcatStrings(TeXForm(x, OpPrecedence("^")), " ^{", TeXForm(y, TeXFormMaxPrec()), "}" ) );

/* functions */


LocalSymbols(TeXFormRegularOps, TeXFormRegularPrefixOps, TeXFormGreekLetters, TeXFormSpecialNames) [

	/* addition, subtraction, multiplication, all comparison and logical operations are "regular" */

  TeXFormRegularOps :=
  {
    {"+"," + "},
    {"-"," - "},
    {"*"," "},
    {":=","\\equiv "},
    {"=="," = "},
    {"="," = "},
    {"!=","\\neq "},
    {"<=","\\leq "},
    {">=","\\geq "},
    {"<"," < "},
    {">"," > "},
    {"And","\\wedge "},
    {"Or", "\\vee "},
    {"<>", "\\sim "},
    {"<=>", "\\approx "},
    {"=>", "\\Rightarrow "},
    {"%", "\\bmod "},
  };

  TeXFormRegularPrefixOps := { {"+"," + "}, {"-"," - "}, {"Not"," \\neg "} };



    /* Unknown function: precedence 200. Leave as is, never bracket the function itself and bracket the argument(s) automatically since it's a list. Other functions are precedence 100 */

  TeXFormGreekLetters := {"Gamma", "Delta", "Theta", "Lambda", "Xi", "Pi", "Sigma", "Upsilon", "Phi", "Psi", "Omega", "alpha", "beta", "gamma", "delta", "epsilon", "zeta", "eta", "theta", "iota", "kappa", "lambda", "mu", "nu", "xi", "pi", "rho", "sigma", "tau", "upsilon", "phi", "chi", "psi", "omega", "varpi", "varrho", "varsigma", "varphi", "varepsilon"};
  TeXFormSpecialNames := {
    {"I", "\\imath "},	// this prevents a real uppercase I, use BesselI instead
    {"Pi", "\\pi "},	// this makes it impossible to have an uppercase Pi... hopefully it's not needed
    {"Infinity", "\\infty "},
    {"TeX", "\\textrm{\\TeX\\/}"},
    {"LaTeX", "\\textrm{\\LaTeX\\/}"},
    {"Max", "\\max "},	// this replaces these function names
    {"Min", "\\min "},
    {"Prog", " "},
    {"Zeta", "\\zeta "},
  };


  /* this function will take a user-defined variable or function name and output either this name unmodified if it's only 2 characters long, or the name in normal text if it's longer, or a TeX Greek letter code */
  Function ("TeXFormTeXify", {string})
  [
    Check(IsString(string), "TeXForm internal error: non-string argument of TeXFormTeXify");
    /* Check if it's a greek letter or a special name */
    If (Contains(AssocIndices(TeXFormSpecialNames), string), TeXFormSpecialNames[string],
    If (Contains(TeXFormGreekLetters, string), ConcatStrings("\\", string, " "),
    If (Contains(AssocIndices(TeXFormRegularOps), string), TeXFormRegularOps[string],
    If (Contains(AssocIndices(TeXFormRegularPrefixOps), string), TeXFormRegularPrefixOps[string],
    If (Length(string) >= 2 And IsNumber(Atom(StringMid'Get(2, Length(string)-1, string))), ConcatStrings(StringMid'Get(1,1,string), "_{", StringMid'Get(2, Length(string)-1, string), "}"),
    If (Length(string) > 2, ConcatStrings("\\mathrm{ ", string, " }"),
    string
    ))))));
  ];

];

/* */

/* Unknown bodied function */

200 # TeXForm(x_IsFunction, _p) _ (IsBodied(Type(x))) <-- [
	Local(func, args, last'arg);
	func := Type(x);
	args := Tail(Listify(x));
	last'arg := PopBack(args);
	TeXFormBracketIf(p<OpPrecedence(func), ConcatStrings(
	  TeXFormTeXify(func), TeXForm(args, TeXFormMaxPrec()),  TeXForm(last'arg, OpPrecedence(func))
	));
];

/* Unknown infix function : already done above
210 # TeXForm(x_IsFunction, _p)_(IsInfix(Type(x))) <-- ConcatStrings(TeXFormTeXify(Type(x)), TeXForm(Tail(Listify(x)), TeXFormMaxPrec()) );
*/
/* Unknown function that is not prefix, infix or postfix */
220 # TeXForm(x_IsFunction, _p) <-- ConcatStrings(TeXFormTeXify(Type(x)), TeXForm(Tail(Listify(x)), TeXFormMaxPrec()) );

	/* Never bracket Sqrt or its arguments */
100 # TeXForm(Sqrt(_x), _p) <-- ConcatStrings("\\sqrt{", TeXForm(x, TeXFormMaxPrec()), "}");

	/* Always bracket Exp's arguments */
100 # TeXForm(Exp(_x), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings("\\exp ", TeXFormBracketIf(True, TeXForm(x, TeXFormMaxPrec())) ) );


LocalSymbols(TeXFormMathFunctions, TeXFormMathFunctions2) [

  /* Sin, Cos, etc. and their argument is bracketed when it's a sum or product but not bracketed when it's a power. */
  /// supported Yacas functions: "mathematical" functions of one argument which sometimes do not require parentheses (e.g. $\sin x$ )
  TeXFormMathFunctions := { {"Cos","\\cos "}, {"Sin","\\sin "}, {"Tan","\\tan "}, {"Cosh","\\cosh "}, {"Sinh","\\sinh "}, {"Tanh","\\tanh "}, {"Ln","\\ln "}, {"ArcCos","\\arccos "}, {"ArcSin","\\arcsin "}, {"ArcTan","\\arctan "}, {"ArcCosh","\\mathrm{arccosh}\\, "}, {"ArcSinh","\\mathrm{arcsinh}\\, "}, {"ArcTanh","\\mathrm{arctanh}\\, "},
  {"Erf", "\\mathrm{erf}\\, "}, {"Erfc", "\\mathrm{erfc}\\, "},
  };

  /// supported Yacas functions: functions of two arguments of the form $A_n(x)$
  TeXFormMathFunctions2 := {
  {"BesselI", "I "}, {"BesselJ", "J "},
  {"BesselK", "K "}, {"BesselY", "Y "},
  {"OrthoH", "H "}, {"OrthoP", "P "},
  {"OrthoT", "T "}, {"OrthoU", "U "},
  };

  // generic two-argument functions of the form $A(x,y)$ where just the name has to be changed: handle this using the usual naming conversion scheme (TeXFormSpecialNames)

  /* Precedence of 120 because we'd like to process other functions like sqrt or exp first */

  // generic math functions of one argument
  120 # TeXForm(expr_IsFunction, _p) _ (NrArgs(expr)=1 And Contains(AssocIndices(TeXFormMathFunctions), Type(expr)) ) <-- TeXFormBracketIf(p<OpPrecedence("*"), ConcatStrings(TeXFormMathFunctions[Type(expr)], TeXForm( Listify(expr)[2], TeXForm'FuncPrec) ) );

  /// math functions two arguments of the form $A_n(x)$
  120 # TeXForm(expr_IsFunction, _p) _ (NrArgs(expr)=2 And Contains(AssocIndices(TeXFormMathFunctions2), Type(expr)) ) <-- TeXFormBracketIf(p<OpPrecedence("*"),
    ConcatStrings(
    TeXFormMathFunctions2[Type(expr)],
    "_{",
    TeXForm( Listify(expr)[2], TeXFormMaxPrec()),	// never bracket the subscript
    "}",
    TeXFormBracketIf(True, TeXForm(Listify(expr)[3], TeXFormMaxPrec()) ) // always bracket the function argument
    )
  );

]; // LocalSymbols(TeXFormMathFunctions, TeXFormMathFunctions2)


/* Complex numbers */
100 # TeXForm(Complex(0, 1), _p) <-- TeXForm(Hold(I), p);
100 # TeXForm(Complex(_x, 0), _p) <-- TeXForm(x, p);
110 # TeXForm(Complex(_x, 1), _p) <-- TeXForm(x+Hold(I), p);
110 # TeXForm(Complex(0, _y), _p) <-- TeXForm(Hold(I)*y, p);
120 # TeXForm(Complex(_x, _y), _p) <-- TeXForm(x+Hold(I)*y, p);

/* Abs(), Floor(), Ceil() are displayed as special brackets */

100 # TeXForm(Abs(_x), _p) <-- ConcatStrings("\\left| ", TeXForm(x, TeXFormMaxPrec()), "\\right| ");
100 # TeXForm(Floor(_x), _p) <-- ConcatStrings("\\left\\lfloor ", TeXForm(x, TeXFormMaxPrec()), "\\right\\rfloor ");
100 # TeXForm(Ceil(_x), _p) <-- ConcatStrings("\\left\\lceil ", TeXForm(x, TeXFormMaxPrec()), "\\right\\rceil ");

/* Some functions which are displayed as infix: Mod, Union, Intersection, Difference, Contains */

100 # TeXForm(Mod(_x, _y), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(TeXForm(x, OpPrecedence("/")), "\\bmod ", TeXForm(y, OpPrecedence("/")) ) );

100 # TeXForm(Union(_x, _y), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(TeXForm(x, OpPrecedence("/")), "\\cup ", TeXForm(y, OpPrecedence("/")) ) );

100 # TeXForm(Intersection(_x, _y), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(TeXForm(x, OpPrecedence("/")), "\\cap ", TeXForm(y, OpPrecedence("/")) ) );

100 # TeXForm(Difference(_x, _y), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(TeXForm(x, OpPrecedence("/")), "\\setminus ", TeXForm(y, OpPrecedence("/")) ) );

/* "Contains" is displayed right to left */

100 # TeXForm(Contains(_x, _y), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(TeXForm(y, OpPrecedence("/")), "\\in ", TeXForm(x, OpPrecedence("/")) ) );

/// Binomial coefficients: always bracketed
100 # TeXForm(Bin(_n, _m), _p) <-- TeXFormBracketIf(False, ConcatStrings("{", TeXForm(n, TeXFormMaxPrec()), " \\choose ", TeXForm(m, TeXFormMaxPrec()), "}" )
);

/* Some functions with limits: Lim, Sum, Integrate */

100 # TeXForm(Sum(_x, _x1, _x2, _expr), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings("\\sum _{", TeXForm(x, TeXFormMaxPrec()), " = ", TeXForm(x1, TeXFormMaxPrec()), "} ^{", TeXForm(x2, TeXFormMaxPrec()), "} ", TeXForm(expr, OpPrecedence("*")) ) );

100 # TeXForm(Product(_x, _x1, _x2, _expr), _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings("\\prod _{", TeXForm(x, TeXFormMaxPrec()), " = ", TeXForm(x1, TeXFormMaxPrec()), "} ^{", TeXForm(x2, TeXFormMaxPrec()), "} ", TeXForm(expr, OpPrecedence("*")) ) );

100 # TeXForm(Integrate(_x, _x1, _x2) _expr, _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(
"\\int _{", TeXForm(x1, TeXFormMaxPrec()), "} ^{", TeXForm(x2, TeXFormMaxPrec()), " } ", TeXForm(expr, OpPrecedence("*")), " d", TeXForm(x, TeXFormMaxPrec())
) );

/* indeterminate integration */
100 # TeXForm(Integrate(_x) _expr, _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings(
"\\int ", TeXForm(expr, OpPrecedence("*")), " d", TeXForm(x, TeXFormMaxPrec())
) );

100 # TeXForm(Limit(_x, _x1) _expr, _p) <-- TeXFormBracketIf(p<OpPrecedence("/"), ConcatStrings("\\lim _{", TeXForm(x, TeXFormMaxPrec()), "\\rightarrow ", TeXForm(x1, TeXFormMaxPrec()), "} ", TeXForm(expr, OpPrecedence("/")) ) );

/* Derivatives */

/* Use partial derivative only when the expression has several variables */
100 # TeXForm(Deriv(_x)_y, _p) <-- TeXFormBracketIf(p<OpPrecedence("-"), ConcatStrings(
	If(Length(VarList(y))>1, "\\frac{\\partial}{\\partial ", "\\frac{d}{d "
	), TeXForm(x, OpPrecedence("^")), "}", TeXForm(y, OpPrecedence("/")) ) );

100 # TeXForm(Deriv(_x, _n)_y, _p) <-- TeXFormBracketIf(p<OpPrecedence("-"), ConcatStrings(
	If(
		Length(VarList(y))>1,
		"\\frac{\\partial^" : TeXForm(n, TeXFormMaxPrec()) : "}{\\partial ",
		"\\frac{d^" : TeXForm(n, TeXFormMaxPrec()) : "}{d "
	), TeXForm(x, OpPrecedence("^")), " ^", TeXForm(n, TeXFormMaxPrec()), "}", TeXForm(y, OpPrecedence("/")) ) );
100 # TeXForm(D(_x)_y, _p) <-- TeXForm(Deriv(x) y, p);
100 # TeXForm(D(_x, _n)_y, _p) <-- TeXForm(Deriv(x, n) y, p);

/* Indexed expressions */

/* This seems not to work because x[i] is replaced by Nth(x,i) */
/*
100 # TeXForm(_x [ _i ], _p) <-- ConcatStrings(TeXForm(x, TeXFormMaxPrec()), " _{", TeXForm(i, TeXFormMaxPrec()), "}");
*/
/* Need to introduce auxiliary function, or else have trouble with arguments of Nth being lists */
100 # TeXForm(Nth(Nth(_x, i_IsList), _j), _p) <-- TeXForm(TeXFormNth(x, Append(i,j)), p);
100 # TeXForm(TeXFormNth(Nth(_x, i_IsList), _j), _p) <-- TeXForm(TeXFormNth(x, Append(i,j)), p);
110 # TeXForm(Nth(Nth(_x, _i), _j), _p) <-- TeXForm(TeXFormNth(x, List(i,j)), p);
120 # TeXForm(Nth(_x, _i), _p) <-- ConcatStrings(TeXForm(x, TeXFormMaxPrec()), " _{", TeXForm(i, TeXFormMaxPrec()), "}");
120 # TeXForm(TeXFormNth(_x, _i), _p) <-- ConcatStrings(TeXForm(x, TeXFormMaxPrec()), " _{", TeXForm(i, TeXFormMaxPrec()), "}");

/* Matrices are always bracketed. Precedence 80 because lists are at 100. */

80 # TeXForm(M_IsMatrix, _p) <-- TeXFormBracketIf(True, TeXFormPrintMatrix(M));

Function ("TeXFormPrintMatrix", {M})
[
/*
	Want something like "\begin{array}{cc} a & b \\ c & d \\ e & f \end{array}"
	here, "cc" is alignment and must be given for each column
*/
	Local(row, col, result, ncol);
	result := "\\begin{array}{";
	ForEach(col, M[1]) result:=ConcatStrings(result, "c");
	result := ConcatStrings(result, "}");

	ForEach(row, 1 .. Length(M)) [
		ForEach(col, 1 .. Length(M[row])) [
			result := ConcatStrings( result, " ", TeXForm(M[row][col], TeXFormMaxPrec()), If(col = Length(M[row]), If(row = Length(M), "", " \\\\"), " &"));
		];
	];

	ConcatStrings(result, " \\end{array} ");
];

