\documentclass{article} \usepackage{open-axiom} \begin{document} \title{\$SPAD/src/algebra mathml.spad} \author{Arthur C. Ralfs} \maketitle \begin{abstract} MathMLFormat is a package to produce presentation mathematical markup language from OutputForm. \end{abstract} \eject \tableofcontents \eject \section{Preface} Both this code and documentation are still under development and I don't pretend they are anywhere close to perfect or even finished. However the code does work and I hope it might be useful to somebody both for it's ability to output MathML from Axiom and as an example of how to write a new output form. \section{Introduction to Mathematical Markup Language} MathML exists in two forms: presentation and content. At this time (2007-02-11) the package only has a presentation package. A content package is in the works however it is more difficult. Unfortunately Axiom does not make its semantics easily available. The \spadtype{OutputForm} domain mediates between the individual Axiom domains and the user visible output but \spadtype{OutputForm} does not provide full semantic information. From my currently incomplete understanding of Axiom it appears that remedying this would entail going back to the individual domains and rewriting a lot of code. However some semantics are conveyed directly by \spadtype{OutputForm} and other things can be deduced from \spadtype{OutputForm} or from the original user command. \section{Displaying MathML} The MathML string produced by ")set output mathml on" can be pasted directly into an appropriate xhtml page and then viewed in Firefox or some other MathML aware browser. The boiler plate code needed for a test page, testmathml.xml, is: \begin{verbatim} ]> MathML Test \end{verbatim} Paste the MathML string into the body element and it should display nicely in Firefox. \section{Test Cases} Here's a list of test cases that currently format correctly: 1. (x+y)**2 2. integrate(x**x,x) 3. integral(x**x,x) 4. (5 + sqrt 63 + sqrt 847)**(1/3) 5. set $[$1,2,3$]$ 6. multiset $[$x rem 5 for x in primes(2,1000)$]$ 7. series(sin(a*x),x=0) 8. matrix $[$ $[$x**i + y**j for i in 1..10$]$ for j in 1..10$]$ 9. y := operator 'y a. D(y(x,z),$[$x,x,z,x$]$) b. D(y x,x,2) 10. x := series 'x a. sin(1+x) 11. series(1/log(y),y=1) 12. y:UTS(FLOAT,'z,0) := exp(z) 13. a. c := continuedFraction(314159/100000) b. c := continuedFraction(14159/100000) c. c := continuedFraction(3,repeating [1], repeating [3,6]) 14. F := operator F x := operator x y := operator y a := F(x z,y z,z**2) + x y(z+1) D(a,z) The \spadtype{TexFormat} domain has the capability to format an object with subscripts, superscripts, presubscripts and presuperscripts however I don't know of any Axiom command that produces such an object. In fact at present I see the case of "SUPERSUB" being used for putting primes in the superscript position to denote ordinary differentiation. I also only see the "SUB" case being used to denote partial derivatives. \section{)set output mathml on} Making mathml appear as output during a normal Axiom session by invoking ")set output mathml on" proved to be a bit tedious and seems to be undocumented. I document my experience here in case it proves useful to somebody else trying to get a new output format from Axiom. In \spadtype{MathMLFormat} the functions \spadfun{coerce(expr : OutputForm) : String} and \spadfun{display(s : String) : Void} provide the desired mathml output. Note that this package was constructed by close examination of Robert Sutor's \spadtype{TexFormat} domain and much remains from that source. To have mathml displayed as output we need to get Axiom to call display(coerce(expr)) at the appropriate place. Here's what I did to get that to happen. Note that my starting point here was an attempt by Andrey Grozin to do the same. To figure things out I searched through files for "tex" to see what was done for the \spadtype{TexFormat} domain, and used grep to find which files had mention of \spadtype{TexFormat}. \subsection{File src/interp/setvars.boot.pamphlet} Create an output mathml section by analogy to the tex section. Remember to add the code chunk "outputmathmlCode" at the end. setvars.boot is a bootstrap file which means that it has to be precompiled into lisp code and then that code has to be inserted back into setvars.boot. To do this extract the boot code by running "notangle" on it. I did this from the "tmp" directory. From inside axiom run ")lisp (bbottran::boottocl "tmp/setvars.boot") which put "setvars.clisp" into "int/interp/setvars.clisp". Then replace the lisp in "setvars.boot.pamphlet" with that in the newly generated "setvars.clisp". The relevant code chunks appearing in "setvars.boot.pamphlet" are: \begin{verbatim} outputmathmlCode setOutputMathml describeSetOutputMathml \end{verbatim} and the relevant variables are: \begin{verbatim} setOutputMathml $mathmlOutputStream $mathmlOutputFile $mathmlFormat describeSetOutputMathml \end{verbatim} \subsection{File setvart.boot.pamphlet} Create an output mathml section in "setvart.boot.pamphlet" again patterned after the tex section. I changed the default file extension from ".stex" to ".smml". To the "section{output}" table I added the line \begin{verbatim} mathml created output in MathML style Off:CONSOLE \end{verbatim} Added the code chunk "outputmathml" to the code chunk "output" in "section{output}". Relevant code chunks: \begin{verbatim} outputmathml \end{verbatim} Relevant variables: \begin{verbatim} setOutputMathml $mathmlFormat $mathmlOutputFile \end{verbatim} Note when copying the tex stuff I changed occurrences of "tex" to "mathml", "Tex" to "Mathml" and "TeX" to "MathML". \subsection{File src/algebra/Makefile.pamphlet} The file "src/algebra/tex.spad.pamphlet" contains the domain \spadtype{TexFormat} (TEX) and the package \spadtype{TexFormat1} (TEX1). However the sole function of \spadtype{TexFormat1} is to \spadfun{coerce} objects from a domain into \spadtype{OutputForm} and then apply \spadtype{TexFormat} to them. It is to save programmers the trouble of doing the coercion themselves from inside spad code. It does not appear to be used for the main purpose of delivering Axiom output in TeX format. In order to keep the mathml package as simple as possible, and because I didn't see much use for this, I didn't copy the \spadtype{TexFormat1} package. So no analog of the TEX1 entries in "Makefile.pamphlet" were needed. One curiosity I don't understand is why TEX1 appears in layer 4 when it seems to depend on TEX which appears in layer 14. Initially I added "\${OUT}/MMLFORM.o" to layer 14 and "mathml.spad.pamphlet" to completed spad files in layer 14. When trying to compile the build failed at MMLFORM. It left "MMLFORM.erlib" in "int/algebra" instead of "MMLFORM.NRLIB" which confused me at first because mathml.spad compiled under a running axiom. By examining the file "obj/tmp/trace" I saw that a new dependency had been introduced, compared to TexFormat, with the function eltName depending on the domain FSAGG in layer 16. So the lines had to be moved from layer 14 to layer 17. Added appropriate lines to "SPADFILES" and "DOCFILES". \subsection{File src/algebra/exposed.lsp.pamphlet} Add the line "(|MathMLFormat| . MMLFORM)" \subsection{File src/algebra/Lattice.pamphlet} I don't see that this file is used anywhere but I made the appropriate changes anyway by searching for "TEX" and mimicing everything for MMLFORM. \subsection{File src/doc/axiom.bib.pamphlet} Added mathml.spad subsection to "src/doc/axiom.bib.pamphlet". \subsection{File interp/i-output.boot.pamphlet} This is where the \spadfun{coerce} and \spadfun{display} functions from MathMLFormat actually get called. The following was added: \begin{verbatim} mathmlFormat expr == mml := '(MathMLFormat) mmlrep := '(String) formatFn := getFunctionFromDomain("coerce",mml,[$OutputForm]) displayFn := getFunctionFromDomain("display",mml,[mmlrep]) SPADCALL(SPADCALL(expr,formatFn),displayFn) writeNewline $mathmlOutputStream flushOutput $mathmlOutputStream NIL \end{verbatim} Note that compared to the texFormat function there are a couple of differences. Since \spadtype{MathMLFormat} is currently a package rather than a domain there is the "mmlrep" variable whereas in texFormat the argument of the "display" function is an instance of the domain. Also the \spadfun{coerce} function here only has one argument, namely "\$OutputForm". Also for the function "output(expr,domain)" add lines for mathml, e.g. "if \$mathmlFormat then mathmlFormat expr". After these changes Axiom compiled with mathml enabled under )set output. \section{package MMLFORM MathMLFormat} \subsection{Public Declarations} The declarations \begin{verbatim} E ==> OutputForm I ==> Integer L ==> List S ==> String US ==> UniversalSegment(Integer) \end{verbatim} provide abbreviations for domains used heavily in the code. The publicly exposed functions are: \spadfun{coerce: E -$>$ S} This function is the main one for converting an expression in domain OutputForm into a MathML string. \spadfun{coerceS: E -$>$ S} This function is for use from the command line. It converts an OutputForm expression into a MathML string and does some formatting so that the output is not one long line. If you take the output from this function, stick it in an emacs buffer in nxml-mode and then indent according to mode, you'll get something that's nicer to look at than what comes from coerce. Note that coerceS returns the same value as coerce but invokes a display function as well so that the result will be printed twice in different formats. The need for this is that the output from coerce is automatically formatted with line breaks by Axiom's output routine that are not in the right place. \spadfun{coerceL: E -$>$ S} Similar to coerceS except that the displayed result is the MathML string in one long line. These functions can be used, for instance, to get the MathML for the previous result by typing coerceL(%)\$MMLFORM. \spadfun{exprex: E -$>$ S} Converts \spadtype{OutputForm} to \spadtype{String} with the structure preserved with braces. This is useful in developing this package. Actually this is not quite accurate. The function \spadfun{precondition} is first applied to the \spadtype{OutputForm} expression before \spadfun{exprex}. Raw \spadtype{OutputForm} and the nature of the \spadfun{precondition} function is still obscure to me at the time of this writing (2007-02-14), however I probably need to understand it to make sure I'm not missing any semantics. The spad function \spadfun{precondition} is just a wrapper for the lisp function outputTran\$Lisp, which I guess is compiled from boot. \spadfun{display: S -$>$ Void} This one prints the string returned by coerce as one long line, adding "math" tags: $ ... $. Thus the output from this can be stuck directly into an appropriate html/xhtml page and will be displayed nicely by a MathML aware browser. \spadfun{displayF: S -$>$ Void} This function doesn't exist yet but it would be nice to have a humanly readable formatted output as well. The basics do exist in the coerceS function however the formatting still needs some work to be really good. <>= )abbrev domain MMLFORM MathMLFormat ++ Author: Arthur C. Ralfs ++ Date: January 2007 ++ This package is based on the TeXFormat domain by Robert S. Sutor ++ without which I wouldn't have known where to start. ++ Basic Operations: coerce, coerceS, coerceL, exprex, display ++ Description: ++ \spadtype{MathMLFormat} provides a coercion from \spadtype{OutputForm} ++ to MathML format. MathMLFormat(): public == private where E ==> OutputForm I ==> Integer L ==> List S ==> String US ==> UniversalSegment(Integer) public == SetCategory with coerce: E -> S ++ coerceS(o) changes o in the standard output format to MathML ++ format. coerceS: E -> S ++ coerceS(o) changes o in the standard output format to MathML ++ format and displays formatted result. coerceL: E -> S ++ coerceS(o) changes o in the standard output format to MathML ++ format and displays result as one long string. exprex: E -> S ++ coverts \spadtype{OutputForm} to \spadtype{String} with the ++ structure preserved with braces. Actually this is not quite ++ accurate. The function \spadfun{precondition} is first ++ applied to the ++ \spadtype{OutputForm} expression before \spadfun{exprex}. ++ The raw \spadtype{OutputForm} and ++ the nature of the \spadfun{precondition} function is ++ still obscure to me ++ at the time of this writing (2007-02-14). display: S -> Void ++ prints the string returned by coerce, adding tags. @ \subsection{Private Constant Declarations} <>= private == add import OutputForm import Character import Integer import List OutputForm import List String -- local variable declarations and definitions expr: E prec,opPrec: I str: S blank : S := " \ " maxPrec : I := 1000000 minPrec : I := 0 unaryOps : L S := ["-","^"]$(L S) unaryPrecs : L I := [700,260]$(L I) -- the precedence of / in the following is relatively low because -- the bar obviates the need for parentheses. binaryOps : L S := ["+->","|","**","/","<",">","=","OVER"]$(L S) binaryPrecs : L I := [0,0,900, 700,400,400,400, 700]$(L I) naryOps : L S := ["-","+","*",blank,",",";"," ","ROW","", " \cr ","&",""]$(L S) naryPrecs : L I := [700,700,800, 800,110,110, 0, 0, 0, 0, 0, 0]$(L I) naryNGOps : L S := ["ROW","&"]$(L S) plexOps : L S := ["SIGMA","SIGMA2","PI","PI2","INTSIGN","INDEFINTEGRAL"]$(L S) plexPrecs : L I := [ 700, 800, 700, 800 , 700, 700]$(L I) specialOps : L S := ["MATRIX","BRACKET","BRACE","CONCATB","VCONCAT", _ "AGGLST","CONCAT","OVERBAR","ROOT","SUB","TAG", _ "SUPERSUB","ZAG","AGGSET","SC","PAREN", _ "SEGMENT","QUOTE","theMap" ] -- the next two lists provide translations for some strings for -- which MML provides special macros. specialStrings : L S := ["cos", "cot", "csc", "log", "sec", "sin", "tan", "cosh", "coth", "csch", "sech", "sinh", "tanh", "acos","asin","atan","erf","...","$","infinity","Gamma"] specialStringsInMML : L S := ["cos","cot","csc","log","sec","sin","tan", "cosh","coth","csch","sech","sinh","tanh", "arccos","arcsin","arctan","erf","","$","","Г"] @ \subsection{Private Function Declarations} These are the local functions: addBraces:S -$>$ S addBrackets:S -$>$ S atomize:E -$>$ L E displayElt:S -$>$ Void function for recursively displaying mathml nicely formatted eltLimit:(S,I,S) -$>$ I demarcates end postion of mathml element with name:S starting at position i:I in mathml string s:S and returns end of end tag as i:I position in mathml string, i.e. find start and end of substring: $...$ eltName:(I,S) -$>$ S find name of mathml element starting at position i:I in string s:S group:S -$>$ S formatBinary:(S,L E, I) -$>$ S formatFunction:(S,L E, I) -$>$ S formatMatrix:L E -$>$ S formatNary:(S,L E, I) -$>$ S formatNaryNoGroup:(S,L E, I) -$>$ S formatNullary:S -$>$ S formatPlex:(S,L E, I) -$>$ S formatSpecial:(S,L E, I) -$>$ S formatUnary:(S, E, I) -$>$ S formatMml:(E,I) -$>$ S newWithNum:I -$>$ \$ this is a relic from tex.spad and is not used here so far. I'll probably remove it. parenthesize:S -$>$ S precondition:E -$>$ E this function is applied to the OutputForm expression before doing anything else. postcondition:S -$>$ S this function is applied after all other OutputForm -$>$ MathML transformations. In the TexFormat domain the ungroup function first peels off the outermost set of braces however I have replaced braces with $s here and sometimes the outermost set of $s is necessary to get proper display in Firefox. For instance with getting the correct size of brackets on a matrix the whole expression needs to be enclosed in a mrow element. It also checks for +- and removes the +. stringify:E -$>$ S tagEnd:(S,I,S) -$>$ I finds closing "$>$" of start or end tag for mathML element for formatting MathML string for human readability. No analog in TexFormat. ungroup:S -$>$ S <>= -- local function signatures addBraces: S -> S addBrackets: S -> S atomize: E -> L E displayElt: S -> Void ++ function for recursively displaying mathml nicely formatted eltLimit: (S,I,S) -> I ++ demarcates end postion of mathml element with name:S starting at ++ position i:I in mathml string s:S and returns end of end tag as ++ i:I position in mathml string, i.e. find start and end of ++ substring: ... eltName: (I,S) -> S ++ find name of mathml element starting at position i:I in string s:S group: S -> S formatBinary: (S,L E, I) -> S formatFunction: (S,L E, I) -> S formatIntSign: (L E, I) -> S formatMatrix: L E -> S formatNary: (S,L E, I) -> S formatNaryNoGroup: (S,L E, I) -> S formatNullary: S -> S formatPlex: (S,L E, I) -> S formatSpecial: (S,L E, I) -> S formatSub: (E, L E, I) -> S formatSuperSub: (E, L E, I) -> S formatSuperSub1: (E, L E, I) -> S formatUnary: (S, E, I) -> S formatMml: (E,I) -> S formatZag: L E -> S formatZag1: L E -> S newWithNum: I -> $ parenthesize: S -> S precondition: E -> E postcondition: S -> S stringify: E -> S tagEnd: (S,I,S) -> I ++ finds closing ">" of start or end tag for mathML element ungroup: S -> S @ \subsection{Public Function Definitions} Note that I use the function sayTeX$Lisp much as I would printf in a C program. I've noticed in grepping the code that there are other "say" functions, sayBrightly and sayMessage for instance, but I have no idea what the difference is between them at this point. sayTeX$Lisp does the job so for the time being I'll use that until I learn more. The functions coerceS and coerceL should probably be changed to display functions, {\it i.e.}\/ \spadfun{displayS} and \spadfun{display L}, returning Void. I really only need the one coerce function. <>= -- public function definitions coerce(expr : E): S == s : S := postcondition formatMml(precondition expr, minPrec) s coerceS(expr : E): S == s : S := postcondition formatMml(precondition expr, minPrec) sayTeX$Lisp "" displayElt(s) sayTeX$Lisp "" s coerceL(expr : E): S == s : S := postcondition formatMml(precondition expr, minPrec) sayTeX$Lisp "" sayTeX$Lisp s sayTeX$Lisp "" s display(mathml : S): Void == sayTeX$Lisp "" sayTeX$Lisp mathml sayTeX$Lisp "" exprex(expr : E): S == -- This breaks down an expression into atoms and returns it as -- a string. It's for developmental purposes to help understand -- the expressions. expr := precondition expr -- sayTeX$Lisp "0: "stringify expr (not %pair?(expr)$Foreign(Builtin)) or (stringify expr = "NOTHING") => concat ["{",stringify expr,"}"] le : L E := (expr pretend L E) op := first le sop : S := exprex op args : L E := rest le nargs : I := #args -- sayTeX$Lisp concat ["1: ",stringify first le," : ",string(nargs)$S] s : S := concat ["{",sop] if positive? nargs then for a in args repeat -- sayTeX$Lisp concat ["2: ",stringify a] s1 : S := exprex a s := concat [s,s1] s := concat [s,"}"] @ \subsection{Private Function Definitions} \subsubsection{Display Functions} displayElt(mathml:S):Void eltName(pos:I,mathml:S):S eltLimit(name:S,pos:I,mathml:S):I tagEnd(name:S,pos:I,mathml:S):I <>= displayElt(mathML:S): Void == -- Takes a string of syntactically complete mathML -- and formats it for display. -- sayTeX$Lisp "****displayElt1****" -- sayTeX$Lisp mathML enT:I -- marks end of tag, e.g. "" enE:I -- marks end of element, e.g. " ... " end:I -- marks end of mathML string u:US end := #mathML length:I := 60 -- sayTeX$Lisp "****displayElt1.1****" name:S := eltName(1,mathML) -- sayTeX$Lisp name -- sayTeX$Lisp concat("****displayElt1.2****",name) enE := eltLimit(name,2+#name,mathML) -- sayTeX$Lisp "****displayElt2****" if enE < length then -- sayTeX$Lisp "****displayElt3****" u := segment(1,enE)$US sayTeX$Lisp mathML.u else -- sayTeX$Lisp "****displayElt4****" enT := tagEnd(name,1,mathML) u := segment(1,enT)$US sayTeX$Lisp mathML.u u := segment(enT+1,enE-#name-3)$US displayElt(mathML.u) u := segment(enE-#name-2,enE)$US sayTeX$Lisp mathML.u if end > enE then -- sayTeX$Lisp "****displayElt5****" u := segment(enE+1,end)$US displayElt(mathML.u) eltName(pos:I,mathML:S): S == -- Assuming pos is the position of "<" for a start tag of a mathML -- element finds and returns the element's name. i:I := pos+1 --sayTeX$Lisp "eltName:mathmML string: "mathML while member?(mathML.i,lowerCase()$CharacterClass)$CharacterClass repeat i := i+1 u:US := segment(pos+1,i-1) name:S := mathML.u eltLimit(name:S,pos:I,mathML:S): I == -- Finds the end of a mathML element like " ... " -- where pos is the position of the space after name in the start tag -- although it could point to the closing ">". Returns the position -- of the ">" in the end tag. pI:I := pos startI:I endI:I startS:S := concat ["<",name] endS:S := concat [""] level:I := 1 --sayTeX$Lisp "eltLimit: element name: "name while positive? level repeat startI := position(startS,mathML,pI)$String endI := position(endS,mathML,pI)$String if (startI = 0) then level := level-1 --sayTeX$Lisp "****eltLimit 1******" pI := tagEnd(name,endI,mathML) else if (startI < endI) then level := level+1 pI := tagEnd(name,startI,mathML) else level := level-1 pI := tagEnd(name,endI,mathML) pI tagEnd(name:S,pos:I,mathML:S):I == -- Finds the closing ">" for either a start or end tag of a mathML -- element, so the return value is the position of ">" in mathML. pI:I := pos while (mathML.pI ~= char ">") repeat pI := pI+1 u:US := segment(pos,pI)$US --sayTeX$Lisp "tagEnd: "mathML.u pI @ \subsubsection{Formatting Functions} Still need to format \\zag in formatSpecial! In formatPlex the case op = "INTSIGN" is now passed off to formatIntSign which is a change from the TexFormat domain. This is done here for presentation mark up to replace the ugly bound variable that Axiom delivers. For content mark up this has to be done anyway. The formatPlex function also allows for op = "INDEFINTEGRAL". However I don't know what Axiom command gives rise to this case. The INTSIGN case already allows for both definite and indefinite integrals. In the function formatSpecial various cases are handled including SUB and SUPERSUB. These cases are now caught in formatMml and so the code in formatSpecial doesn't get executed. The only cases I know of using these are partial derivatives for SUB and ordinary derivatives or SUPERSUB however in TexFormat the capability is there to handle multiscripts, i.e. an object with subscripts, superscripts, pre-subscripts and pre-superscripts but I am so far unaware of any Axiom command that produces such a multiscripted object. Another question is how to represent derivatives. At present I have differential notation for partials and prime notation for ordinary derivatives, but it would be nice to allow for different derivative notations in different circumstances, maybe some options to )set output mathml on. Ordinary derivatives are formatted in formatSuperSub and there are 2 versions, formatSuperSub and formatSuperSub1, which at this point have to be switched by swapping names. <>= atomize(expr : E): L E == -- This breaks down an expression into a flat list of atomic expressions. -- expr should be preconditioned. le : L E := nil() letmp : L E (not %pair?(expr)$Foreign(Builtin)) or (stringify expr = "NOTHING") => le := append(le,list(expr)) letmp := expr pretend L E for a in letmp repeat le := append(le,atomize a) le ungroup(str: S): S == len : I := #str len < 14 => str lrow : S := "" rrow : S := "" -- drop leading and trailing mrows u1 : US := segment(1,6)$US u2 : US := segment(len-6,len)$US if (str.u1 =$S lrow) and (str.u2 =$S rrow) then u : US := segment(7,len-7)$US str := str.u str postcondition(str: S): S == -- str := ungroup str len : I := #str plusminus : S := "+-" pos : I := position(plusminus,str,1) if positive? pos then ustart:US := segment(1,pos-1)$US uend:US := segment(pos+20,len)$US str := concat [str.ustart,"-",str.uend] if pos < len-18 then str := postcondition(str) str stringify expr == (mathObject2String$Lisp expr)@S group str == concat ["",str,""] addBraces str == concat ["{",str,"}"] addBrackets str == concat ["[",str,"]"] parenthesize str == concat ["(",str,")"] precondition expr == outputTran$Lisp expr formatSpecial(op : S, args : L E, prec : I) : S == arg : E prescript : Boolean := false op = "theMap" => "theMap(...)" op = "AGGLST" => formatNary(",",args,prec) op = "AGGSET" => formatNary(";",args,prec) op = "TAG" => group concat [formatMml(first args,prec), "", formatMml(second args,prec)] --RightArrow op = "VCONCAT" => group concat("", concat(concat([concat("",concat(formatMml(u, minPrec),"")) for u in args]::L S), "")) op = "CONCATB" => formatNary(" ",args,prec) op = "CONCAT" => formatNary("",args,minPrec) op = "QUOTE" => group concat("'",formatMml(first args, minPrec)) op = "BRACKET" => group addBrackets ungroup formatMml(first args, minPrec) op = "BRACE" => group addBraces ungroup formatMml(first args, minPrec) op = "PAREN" => group parenthesize ungroup formatMml(first args, minPrec) op = "OVERBAR" => null args => "" group concat ["",formatMml(first args,minPrec),"¯"] --OverBar op = "ROOT" => null args => "" tmp : S := group formatMml(first args, minPrec) null rest args => concat ["",tmp,""] group concat ["",tmp,"",formatMml(first rest args, minPrec),""] op = "SEGMENT" => tmp : S := concat [formatMml(first args, minPrec),".."] group null rest args => tmp concat [tmp,formatMml(first rest args, minPrec)] -- SUB should now be diverted in formatMml although I'll leave -- the code here for now. op = "SUB" => group concat ["",formatMml(first args, minPrec), formatSpecial("AGGLST",rest args,minPrec),""] -- SUPERSUB should now be diverted in formatMml although I'll leave -- the code here for now. op = "SUPERSUB" => base:S := formatMml(first args, minPrec) args := rest args if #args = 1 then ""base""formatMml(first args, minPrec)"" else if #args = 2 then -- it would be nice to substitue ′ for , in the case of -- an ordinary derivative, it looks a lot better. ""base""formatMml(first args,minPrec)""formatMml(first rest args, minPrec)"" else if #args = 3 then ""base""formatMml(first args,minPrec)""formatMml(first rest args,minPrec)""formatMml(first rest rest args,minPrec)"" else if #args = 4 then ""base""formatMml(first args,minPrec)""formatMml(first rest args,minPrec)""formatMml(first rest rest args,minPrec)""formatMml(first rest rest rest args,minPrec)"" else "Problem with multiscript object" op = "SC" => -- need to handle indentation someday null args => "" tmp := formatNaryNoGroup("", args, minPrec) group concat ["",tmp,""] op = "MATRIX" => formatMatrix rest args op = "ZAG" => -- {{+}{3}{{ZAG}{1}{7}}{{ZAG}{1}{15}}{{ZAG}{1}{1}}{{ZAG}{1}{25}}{{ZAG}{1}{1}}{{ZAG}{1}{7}}{{ZAG}{1}{4}}} -- to format continued fraction traditionally need to intercept it at the -- formatNary of the "+" concat [" \zag{",formatMml(first args, minPrec),"}{", formatMml(first rest args,minPrec),"}"] concat ["not done yet for: ",op,""] formatSub(expr : E, args : L E, opPrec : I) : S == -- This one produces differential notation partial derivatives. -- It doesn't work in all cases and may not be workable, use -- formatSub1 below for now. -- At this time this is only to handle partial derivatives. -- If the SUB case handles anything else I'm not aware of it. -- This an example of the 4th partial of y(x,z) w.r.t. x,x,z,x -- {{{SUB}{y}{{CONCAT}{{CONCAT}{{CONCAT}{{CONCAT}{,}{1}}{{CONCAT}{,}{1}}}{{CONCAT}{,}{2}}}{{CONCAT}{,}{1}}}}{x}{z}} atomE : L E := atomize(expr) op : S := stringify first atomE op ~= "SUB" => "Mistake in formatSub: no SUB" stringify first rest rest atomE ~= "CONCAT" => "Mistake in formatSub: no CONCAT" -- expecting form for atomE like --[{SUB}{func}{CONCAT}...{CONCAT}{,}{n}{CONCAT}{,}{n}...{CONCAT}{,}{n}], --counting the first CONCATs before the comma gives the number of --derivatives ndiffs : I := 0 tmpLE : L E := rest rest atomE while stringify first tmpLE = "CONCAT" repeat ndiffs := ndiffs+1 tmpLE := rest tmpLE numLS : L S := nil i : I := 1 while i < ndiffs repeat numLS := append(numLS,list(stringify first rest tmpLE)) tmpLE := rest rest rest tmpLE i := i+1 numLS := append(numLS,list(stringify first rest tmpLE)) -- numLS contains the numbers of the bound variables as strings -- for the differentiations, thus for the differentiation [x,x,z,x] -- for y(x,z) numLS = ["1","1","2","1"] posLS : L S := nil i := 0 -- sayTeX$Lisp "formatSub: nargs = "string(#args) while i < #args repeat posLS := append(posLS,list(string(i+1))) i := i+1 -- posLS contains the positions of the bound variables in args -- as a list of strings, e.g. for the above example ["1","2"] tmpS: S := stringify atomE.2 if ndiffs = 1 then s : S := ""tmpS"" else s : S := ""string(ndiffs)""tmpS"" -- need to find the order of the differentiation w.r.t. the i-th -- variable i := 1 j : I k : I tmpS: S while i < #posLS+1 repeat j := 0 k := 1 while k < #numLS + 1 repeat if numLS.k = string i then j := j + 1 k := k+1 if positive? j then tmpS := stringify args.i if j = 1 then s := s""tmpS"" else s := s""tmpS""string(j)"" i := i + 1 s := s"(" i := 1 while i < #posLS+1 repeat tmpS := stringify args.i s := s""tmpS"" if i < #posLS then s := s"," i := i+1 s := s")" formatSub1(expr : E, args : L E, opPrec : I) : S == -- This one produces partial derivatives notated by ",n" as -- subscripts. -- At this time this is only to handle partial derivatives. -- If the SUB case handles anything else I'm not aware of it. -- This an example of the 4th partial of y(x,z) w.r.t. x,x,z,x -- {{{SUB}{y}{{CONCAT}{{CONCAT}{{CONCAT}{{CONCAT}{,}{1}} -- {{CONCAT}{,}{1}}}{{CONCAT}{,}{2}}}{{CONCAT}{,}{1}}}}{x}{z}}, -- here expr is everything in the first set of braces and -- args is {{x}{z}} atomE : L E := atomize(expr) op : S := stringify first atomE op ~= "SUB" => "Mistake in formatSub: no SUB" stringify first rest rest atomE ~= "CONCAT" => "Mistake in formatSub: no CONCAT" -- expecting form for atomE like --[{SUB}{func}{CONCAT}...{CONCAT}{,}{n}{CONCAT}{,}{n}...{CONCAT}{,}{n}], --counting the first CONCATs before the comma gives the number of --derivatives ndiffs : I := 0 tmpLE : L E := rest rest atomE while stringify first tmpLE = "CONCAT" repeat ndiffs := ndiffs+1 tmpLE := rest tmpLE numLS : L S := nil i : I := 1 while i < ndiffs repeat numLS := append(numLS,list(stringify first rest tmpLE)) tmpLE := rest rest rest tmpLE i := i+1 numLS := append(numLS,list(stringify first rest tmpLE)) -- numLS contains the numbers of the bound variables as strings -- for the differentiations, thus for the differentiation [x,x,z,x] -- for y(x,z) numLS = ["1","1","2","1"] posLS : L S := nil i := 0 -- sayTeX$Lisp "formatSub: nargs = "string(#args) while i < #args repeat posLS := append(posLS,list(string(i+1))) i := i+1 -- posLS contains the positions of the bound variables in args -- as a list of strings, e.g. for the above example ["1","2"] funcS: S := stringify atomE.2 s : S := ""funcS"" i := 1 while i < #numLS+1 repeat s := s","numLS.i"" i := i + 1 s := s"(" i := 1 while i < #posLS+1 repeat -- tmpS := stringify args.i tmpS := formatMml(first args,minPrec) args := rest args s := s""tmpS"" if i < #posLS then s := s"," i := i+1 s := s")" formatSuperSub(expr : E, args : L E, opPrec : I) : S == -- this produces prime notation ordinary derivatives. -- first have to divine the semantics, add cases as needed atomE : L E := atomize(expr) op : S := stringify first atomE op ~= "SUPERSUB" => "Mistake in formatSuperSub: no SUPERSUB1" not one?(#args) => "Mistake in SuperSub1: #args <> 1" var : E := first args -- should be looking at something like {{SUPERSUB}{var}{ }{,,...,}} for -- example here's the second derivative of y w.r.t. x -- {{{SUPERSUB}{y}{ }{,,}}{x}}, expr is the first {} and args is the -- {x} funcS : S := stringify first rest atomE bvarS : S := stringify first args -- count the number of commas commaS : S := stringify first rest rest rest atomE commaTest : S := "," i : I := 0 while positive? position(commaTest,commaS,1) repeat i := i+1 commaTest := commaTest"," s : S := ""funcS"" j : I := 0 while j < i repeat s := s"" j := j + 1 s := s"("formatMml(first args,minPrec)")" formatSuperSub1(expr : E, args : L E, opPrec : I) : S == -- This one produces ordinary derivatives with differential notation, -- it needs a little more work yet. -- first have to divine the semantics, add cases as needed atomE : L E := atomize(expr) op : S := stringify first atomE op ~= "SUPERSUB" => "Mistake in formatSuperSub: no SUPERSUB" not one?(#args) => "Mistake in SuperSub: #args <> 1" var : E := first args -- should be looking at something like {{SUPERSUB}{var}{ }{,,...,}} for -- example here's the second derivative of y w.r.t. x -- {{{SUPERSUB}{y}{ }{,,}}{x}}, expr is the first {} and args is the -- {x} funcS : S := stringify first rest atomE bvarS : S := stringify first args -- count the number of commas commaS : S := stringify first rest rest rest atomE commaTest : S := "," ndiffs : I := 0 while positive? position(commaTest,commaS,1) repeat ndiffs := ndiffs+1 commaTest := commaTest"," s : S := ""string(ndiffs)""funcS""formatMml(first args,minPrec)""string(ndiffs)"("formatMml(first args,minPrec)")" formatPlex(op : S, args : L E, prec : I) : S == checkarg:Boolean := false hold : S p : I := position(op,plexOps) p < 1 => error "unknown plex op" op = "INTSIGN" => formatIntSign(args,minPrec) opPrec := plexPrecs.p n : I := #args (n ~= 2) and (n ~= 3) => error "wrong number of arguments for plex" s : S := op = "SIGMA" => checkarg := true "" -- Sum op = "SIGMA2" => checkarg := true "" -- Sum op = "PI" => checkarg := true "" -- Product op = "PI2" => checkarg := true "" -- Product -- op = "INTSIGN" => "" -- Integral, int op = "INDEFINTEGRAL" => "" -- Integral, int "????" hold := formatMml(first args,minPrec) args := rest args if op ~= "INDEFINTEGRAL" then if hold ~= "" then s := concat ["",s,group hold] else s := concat ["",s,group " "] if not null rest args then hold := formatMml(first args,minPrec) if hold ~= "" then s := concat [s,group hold,""] else s := concat [s,group " ",""] args := rest args -- if checkarg true need to test op arg for "+" at least -- and wrap parentheses if so if checkarg then la : L E := (first args pretend L E) opa : S := stringify first la if opa = "+" then s := concat [s,"(",formatMml(first args,minPrec),")"] else s := concat [s,formatMml(first args,minPrec)] else s := concat [s,formatMml(first args,minPrec)] else hold := group concat [hold,formatMml(first args,minPrec)] s := concat [s,hold] -- if opPrec < prec then s := parenthesize s -- getting ugly parentheses on fractions group s formatIntSign(args : L E, opPrec : I) : S == -- the original OutputForm expression looks something like this: -- {{INTSIGN}{NOTHING or lower limit?} -- {bvar or upper limit?}{{*}{integrand}{{CONCAT}{d}{axiom var}}}} -- the args list passed here consists of the rest of this list, i.e. -- starting at the NOTHING or ... (stringify first args) = "NOTHING" => -- the bound variable is the second one in the argument list bvar : E := first rest args bvarS : S := stringify bvar tmpS : S i : I := 0 u1 : US u2 : US -- this next one atomizes the integrand plus differential atomE : L E := atomize(first rest rest args) -- pick out the bound variable used by axiom varRS : S := stringify last(atomE) tmpLE : L E := ((first rest rest args) pretend L E) integrand : S := formatMml(first rest tmpLE,minPrec) -- replace the bound variable, i.e. axiom uses someting of the form -- %A for the bound variable and puts the original variable used -- in the input command as a superscript on the integral sign. -- I'm assuming that the axiom variable is 2 characters. while positive?(i := position(varRS,integrand,i+1)) repeat u1 := segment(1,i-1)$US u2 := segment(i+2,#integrand)$US integrand := concat [integrand.u1,bvarS,integrand.u2] concat ["" integrand "" bvarS ""] lowlim : S := stringify first args highlim : S := stringify first rest args bvar : E := last atomize(first rest rest args) bvarS : S := stringify bvar tmpLE : L E := ((first rest rest args) pretend L E) integrand : S := formatMml(first rest tmpLE,minPrec) concat ["" lowlim "" highlim "" integrand "" bvarS ""] formatMatrix(args : L E) : S == -- format for args is [[ROW ...],[ROW ...],[ROW ...]] -- generate string for formatting columns (centered) group addBrackets concat ["",formatNaryNoGroup("",args,minPrec), ""] formatFunction(op : S, args : L E, prec : I) : S == group concat ["",op,"",parenthesize formatNary(",",args,minPrec)] formatNullary(op : S) == op = "NOTHING" => "" group concat ["",op,"()"] formatUnary(op : S, arg : E, prec : I) == p : I := position(op,unaryOps) p < 1 => error "unknown unary op" opPrec := unaryPrecs.p s : S := concat ["",op,"",formatMml(arg,opPrec)] opPrec < prec => group parenthesize s op = "-" => s group s formatBinary(op : S, args : L E, prec : I) : S == p : I := position(op,binaryOps) p < 1 => error "unknown binary op" opPrec := binaryPrecs.p -- if base op is product or sum need to add parentheses if not %pair?(first args)$Foreign(Builtin) then opa:S := stringify first args else la : L E := (first args pretend L E) opa : S := stringify first la if (opa = "SIGMA" or opa = "SIGMA2" or opa = "PI" or opa = "PI2") and op = "**" then s1 : S := concat ["(",formatMml(first args, opPrec),")"] else s1 : S := formatMml(first args, opPrec) s2 : S := formatMml(first rest args, opPrec) op := op = "|" => s := concat ["",s1,"",op,"",s2,""] op = "**" => s := concat ["",s1,"",s2,""] op = "/" => s := concat ["",s1,"",s2,""] op = "OVER" => s := concat ["",s1,"",s2,""] op = "+->" => s := concat ["",s1,"",op,"",s2,""] s := concat ["",s1,"",op,"",s2,""] group op = "OVER" => s -- opPrec < prec => parenthesize s -- ugly parentheses? s formatNary(op : S, args : L E, prec : I) : S == group formatNaryNoGroup(op, args, prec) formatNaryNoGroup(op : S, args : L E, prec : I) : S == checkargs:Boolean := false null args => "" p : I := position(op,naryOps) p < 1 => error "unknown nary op" -- need to test for "ZAG" case and divert it here -- ex 1. continuedFraction(314159/100000) -- {{+}{3}{{ZAG}{1}{7}}{{ZAG}{1}{15}}{{ZAG}{1}{1}}{{ZAG}{1}{25}} -- {{ZAG}{1}{1}}{{ZAG}{1}{7}}{{ZAG}{1}{4}}} -- this is the preconditioned output form -- including "op", the args list would be the rest of this -- i.e op = '+' and args = {{3}{{ZAG}{1}{7}}{{ZAG}{1}{15}} -- {{ZAG}{1}{1}}{{ZAG}{1}{25}}{{ZAG}{1}{1}}{{ZAG}{1}{7}}{{ZAG}{1}{4}}} -- ex 2. continuedFraction(14159/100000) -- this one doesn't have the leading integer -- {{+}{{ZAG}{1}{7}}{{ZAG}{1}{15}}{{ZAG}{1}{1}}{{ZAG}{1}{25}} -- {{ZAG}{1}{1}}{{ZAG}{1}{7}}{{ZAG}{1}{4}}} -- -- ex 3. continuedFraction(3,repeating [1], repeating [3,6]) -- {{+}{3}{{ZAG}{1}{3}}{{ZAG}{1}{6}}{{ZAG}{1}{3}}{{ZAG}{1}{6}} -- {{ZAG}{1}{3}}{{ZAG}{1}{6}}{{ZAG}{1}{3}}{{ZAG}{1}{6}} -- {{ZAG}{1}{3}}{{ZAG}{1}{6}}{...}} -- In each of these examples the args list consists of the terms -- following the '+' op -- so the first arg could be a "ZAG" or something -- else, but the second arg looks like it has to be "ZAG", so maybe -- test for #args > 1 and args.2 contains "ZAG". -- Note that since the resulting MathML s are nested we need -- to handle the whole continued fraction at once, i.e. we can't -- just look for, e.g., {{ZAG}{1}{6}} (#args > 1) and positive? position("ZAG",stringify first rest args,1) => tmpS : S := stringify first args positive? position("ZAG",tmpS,1) => formatZag(args) -- positive? position("ZAG",tmpS,1) => formatZag1(args) concat [formatMml(first args,minPrec) "+" formatZag(rest args)] -- At least for the ops "*","+","-" we need to test to see if a sigma or pi -- is one of their arguments because we might need parentheses as indicated -- by the problem with summation(operator(f)(i),i=1..n)+1 versus -- summation(operator(f)(i)+1,i=1..n) having identical displays as of -- 2007-12-21 op := op = "," => "," --originally , \: op = ";" => ";" --originally ; \: should figure these out op = "*" => checkargs := true "" -- InvisibleTimes op = " " => "" op = "ROW" => "" op = "+" => checkargs := true "+" op = "-" => checkargs := true "-" op l : L S := nil opPrec := naryPrecs.p -- if checkargs is true check each arg except last one to see if it's -- a sigma or pi and if so add parentheses. Other op's may have to be -- checked for in future count:I := 1 for a in args repeat if checkargs then if count < #args then -- check here for sum or product if not %pair?(a)$Foreign(Builtin) then opa:S := stringify a else la : L E := (a pretend L E) opa : S := stringify first la if opa = "SIGMA" or opa = "SIGMA2" or opa = "PI" or opa = "PI2" then l := concat(op,concat(concat ["(",formatMml(a,opPrec),")"],l)$L(S))$L(S) else l := concat(op,concat(formatMml(a,opPrec),l)$L(S))$L(S) else l := concat(op,concat(formatMml(a,opPrec),l)$L(S))$L(S) else l := concat(op,concat(formatMml(a,opPrec),l)$L(S))$L(S) count := count + 1 s : S := concat reverse rest l opPrec < prec => parenthesize s s formatZag(args : L E) : S == -- args will be a list of things like this {{ZAG}{1}{7}}, the ZAG -- must be there, the '1' and '7' could conceivably be more complex -- expressions tmpZag : L E := first args pretend L E -- may want to test that tmpZag contains 'ZAG' #args > 1 => ""formatMml(first rest tmpZag,minPrec)""formatMml(first rest rest tmpZag,minPrec)"+"formatZag(rest args)"" (first args = "...":: E)@Boolean => "" op:S := stringify first args positive? position("ZAG",op,1) => ""formatMml(first rest tmpZag,minPrec)formatMml(first rest rest tmpZag,minPrec)"" error "formatZag: Last argument in ZAG construct has unknown operator: "op formatZag1(args : L E) : S == -- make alternative ZAG format without diminishing fonts, maybe -- use a table -- {{ZAG}{1}{7}} tmpZag : L E := first args pretend L E #args > 1 => ""formatMml(first rest tmpZag,minPrec)""formatMml(first rest rest tmpZag,minPrec)"+"formatZag(rest args)"" (first args = "...":: E)@Boolean => "" error "formatZag1: Unexpected kind of ZAG" formatMml(expr : E,prec : I) == i,len : Integer intSplitLen : Integer := 20 not %pair?(expr)$Foreign(Builtin) => str := stringify expr len := #str -- this bit seems to deal with integers %integer?(expr)$Foreign(Builtin) => i := expr pretend Integer if negative? i or (i > 9) then group nstr : String := "" -- insert some blanks into the string, if too long while ((len := #str) > intSplitLen) repeat nstr := concat [nstr," ", elt(str,segment(1,intSplitLen)$US)] str := elt(str,segment(intSplitLen+1)$US) empty? nstr => concat ["",str,""] nstr := empty? str => nstr concat [nstr," ",str] concat ["",elt(nstr,segment(2)$US),""] else str := concat ["",str,""] str = "%pi" => "π" -- pi str = "%e" => "" -- ExponentialE str = "%i" => "" -- ImaginaryI positive? len and str.1 = char "%" => concat(concat("",str),"") len > 1 and digit? str.1 => concat ["",str,""] -- should handle floats -- presumably this is a literal string positive? len and str.1 = char "_"" => concat(concat("",str),"") len = 1 and str.1 = char " " => " " positive?(i := position(str,specialStrings)) => specialStringsInMML.i positive?(i := position(char " ",str)) => -- We want to preserve spacing, so use a roman font. -- What's this for? Leave the \rm in for now so I can see -- where it arises. Removed 2007-02-14 concat(concat("",str),"") -- if we get to here does that mean it's a variable? concat ["",str,""] l : L E := (expr pretend L E) null l => blank op : S := stringify first l args : L E := rest l nargs : I := #args -- need to test here in case first l is SUPERSUB case and then -- pass first l and args to formatSuperSub. positive? position("SUPERSUB",op,1) => formatSuperSub(first l,args,minPrec) -- now test for SUB positive? position("SUB",op,1) => formatSub1(first l,args,minPrec) -- special cases member?(op, specialOps) => formatSpecial(op,args,prec) member?(op, plexOps) => formatPlex(op,args,prec) -- nullary case 0 = nargs => formatNullary op -- unary case (1 = nargs) and member?(op, unaryOps) => formatUnary(op, first args, prec) -- binary case (2 = nargs) and member?(op, binaryOps) => formatBinary(op, args, prec) -- nary case member?(op,naryNGOps) => formatNaryNoGroup(op,args, prec) member?(op,naryOps) => formatNary(op,args, prec) op := formatMml(first l,minPrec) formatFunction(op,args,prec) @ \section{Mathematical Markup Language Form} <>= <> <> <> <> <> <> @ \section{License} <>= --Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd. --All rights reserved. -- --Redistribution and use in source and binary forms, with or without --modification, are permitted provided that the following conditions are --met: -- -- - Redistributions of source code must retain the above copyright -- notice, this list of conditions and the following disclaimer. -- -- - Redistributions in binary form must reproduce the above copyright -- notice, this list of conditions and the following disclaimer in -- the documentation and/or other materials provided with the -- distribution. -- -- - Neither the name of The Numerical ALgorithms Group Ltd. nor the -- names of its contributors may be used to endorse or promote products -- derived from this software without specific prior written permission. -- --THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS --IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED --TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A --PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER --OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, --EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, --PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR --PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF --LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING --NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS --SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @ <<*>>= <> <> @ \eject \begin{thebibliography}{99} \bibitem{1} nothing \end{thebibliography} \end{document}