// This file contains JavaScript functions to parse, display and manipulate 
// simple mathematical expressions. The emphasis is on translating from
// readable ascii expressions to reasonable HTML. 

// version of Jan 24 2001, by Peter Jipsen, http://math.vanderbilt.edu/~pjipsen

//check if the symbol font is available
geqVer5 = navigator.appVersion.slice(0,1)>4;

symb = navigator.appVersion.lastIndexOf('Win')!=-1 && !geqVer5 ||
    navigator.appVersion.lastIndexOf('Mac')!=-1;

vari = [
  {name:"x", arity:0, output:"<i>x</i>"},
  {name:"y", arity:0, output:"<i>y</i>"},
  {name:"z", arity:0, output:"<i>z</i>"},
  {name:"u", arity:0, output:"<i>u</i>"},
  {name:"v", arity:0, output:"<i>v</i>"},
  {name:"w", arity:0, output:"<i>w</i>"}
];

formulavari = [
  {name:"PP", arity:0, output:"<i>P</i>"},
  {name:"QQ", arity:0, output:"<i>Q</i>"},
  {name:"RR", arity:0, output:"<i>R</i>"}
];

function out(col,str,ent,alt,sp) {
  return sp+"<b><font"+(col==""?"" : " color="+col)+
    (alt==""?">"+str : (symb?" face=symbol>"+str : 
    (geqVer5?">"+ent : ">"+alt)))+"</font></b>"+sp;
}

bl="&nbsp;";

cons = [
  zero = {name:"0", arity:0, output:"0"},
  unit = {name:"e", arity:0, output:"<i>e</i>"},
  relid = {name:"1\'", arity:0, output:"1\'"},
  one  = {name:"1", arity:0, output:"1"},
  topp = {name:"top", arity:0, output:"T"},
  empty= {name:"{}", arity:0, output:out("","&#198;","&empty;","{}",""), tex:"emptyset"},
  qed  = {name:"qed", arity:0, output:out("","&#168;","&box;","[]",""), tex:"qed"},
  aleph= {name:"aleph", output:out("","&#192;","alefsym","&aleph;",""), tex:"aleph"},
  alpha= {name:"al", output:out("","a","&alpha;","alpha",""), tex:"alpha"},
  beta = {name:"be", output:out("","b","&beta;","beta",""), tex:"beta"},
  delta= {name:"de", output:out("","d","&delta;","delta",""), tex:"delta"},
  epsilon={name:"ep", output:out("","e","&epsilon;","epsilon",""), tex:"epsilon"},
  gamma= {name:"ga", output:out("","g","&gamma;","gamma",""), tex:"gamma"},
  pi   = {name:"pi", output:out("","p","&pi;","pi",""), tex:"pi"},
  dots = {name:"...", arity:0, output:"...", tex:"ldots"}
];

oper = [
  sub = {name:"_", arity:2, priority:40, infix:true, output:"<sub>", 
    endStr:"</sub>", tex:"_{", endTex:"}"},

  sup = {name:"^", arity:2, priority:40, infix:true, output:"<sup>",
    endStr:"</sup>", tex:"^{", endTex:"}"},

  si   = {name:"si", output:out("","s","&sigma;","sigma",""), paren:true, tex:"sigma"},

  boldface={name:"bf",arity:1, priority:38, output:"<b>", endStr:"</b>", tex:"mathbf"},

  underline={name:"ul",arity:1, priority:38, output:"<u>", endStr:"</u>", tex:"underline"},

  inverse = {name:"^-1", arity:1, priority:34, infix:true,
    output:out("Blue","<sup>-1</sup>","<sup>-1</sup>","",""), tex:"^{-1}"},

  tilde = {name:"~", arity:1, priority:34, infix:true,
    output:out("Blue","<sup>~</sup>","<sup>~</sup>","",""), tex:"^\\tilde"},

  converse = {name:"^u", arity:1, priority:34, infix:true,
    output:out("Blue","<sup><font size=-2>&#200;</font></sup>","<sup><font size=-2>&cup;</font></sup>","<sup>u<sup>",""), tex:"^\\tilde"},

  uparrow={name:"!^",arity:1, priority:32, 
    output:out("Blue","&#173;","&uarr;","!^",""), tex:"uparrow"},

  downarrow={name:"!v",arity:1, priority:32, 
    output:out("Blue","&#175;","&darr;","!v",""), tex:"downarrow"},

  diamond={name:"<>",arity:1, priority:32, 
    output:out("Blue","&#224;","&loz;","<>",""), tex:"diamond"},

  box = {name:"[]", arity:1, priority:32, 
    output:out("Blue","[]","&box;","",""), tex:"Box"},

  dot = {name:".", arity:2, priority:31, infix:true, assoc:true,
    output:"", tex:""},

  times = {name:"*", arity:2, priority:31, infix:true, assoc:true,
    output:out("Blue","*","*","*",""), tex:"star"},

  semicolon = {name:";", arity:2,priority:31, infix:true, assoc:true,
    output:out("Blue",";",";",";","")},

  over = {name:"/", arity:2, priority:29, infix:true,
    output:out("Blue","/","/","/","")},

  under = {name:"\\", arity:2, priority:29, infix:true,
    output:'<b><font color="Blue">\\</font></b>', tex:"backslash"},

  rconj = {name:"-;", arity:2, priority:28, infix:true,
    output:'<font color="Blue"> -; </font>', tex:"righttriangle"},

  lconj = {name:";-", arity:2, priority:28, infix:true,
    output:'<font color="Blue"> ;- </font>', tex:"lefttriangle"},

  arrow = {name:"->", arity:2, priority:26, infix:true,
    output:out("Blue","&#174;","&rarr;","-&gt;",bl), tex:"rightarrow"},

  larrow = {name:"<-", arity:2, priority:26, infix:true,
    output:out("Blue","&#172;","&larr;","&lt;-",bl), tex:"leftarrow"},

  minus = {name:"-", arity:2, priority:25, infix:true,
    output:out("Blue","-","-","-","")},

  plus = {name:"+", arity:2, priority:24, infix:true, assoc:true,
    output:out("Blue","+","+","+",bl)},

  oplus = {name:"o+", arity:2, priority:24, infix:true, assoc:true,
    output:out("Blue","&#197;","&oplus;","o+",bl)},

  meet = {name:"^^", arity:2, priority:24, infix:true, assoc:true,
    output:out("Blue","&#217;","&and;","^",bl), tex:"wedge"},

  join = {name:"vv", arity:2, priority:24, infix:true, assoc:true,
    output:out("Blue","&#218;","&or;","v",bl), tex:"vee"},

  union = {name:"uu", arity:2, infix:true, priority:22, assoc:true,
    output:out("Blue","&#200;","&cup;","u",bl), tex:"cup"},

  intersect = {name:"nn", arity:2, infix:true, priority:22, assoc:true,
    output:out("Blue","&#199;","&cap;","n",bl), tex:"cap"},

  cross = {name:"cross", arity:2, infix:true, priority:22, assoc:true,
    output:out("Blue","&#180;","&times;","cross",bl), tex:"times"},

  norm = {name:"|", endName:"|", arity:1, priority:20, endSymb:true,
    output:"|", endStr:"|"},

  floor = {name:"|_", endName:"_|", arity:1, priority:20, 
    output:out("","&#235;","|_","|_",""), tex:"lfloor", endTex:"rfloor", 
    endStr:out("","&#251;","_|","_|","")},
    {name:"_|", endSymb:true, output:"_|"},

  ceiling = {name:"|~", endName:"~|", arity:1, priority:20, 
    output:out("","&#233;","|~","|~",""), tex:"lceiling", endTex:"rceiling", 
    endStr:out("","&#249;","~|","~|","")},
    {name:"~|", endSymb:true, output:"~|"},

  tuple = {name:"<<", endName:">>", arity:1, priority:18,
    output:out("","&#225;","&lt;","&lt;",""), tex:"langle", endTex:"rangle", 
    endStr:out("","&#241;","&gt;","&gt;","")},

  comma = {name:",", arity:2, priority:18, infix:true, assoc:true, output:', '}
];

pred = [
  eq   = {name:"=",  arity:2,  infix:true,  priority:16, assoc:true,
    output:out("Red","=","=","=",bl)},

  noteq = {name:"!=", arity:2, infix:true, priority:16, assoc:true,
    output:out("Red","&#185;","&ne;","!=",bl), tex:"noteq"},

  leq  = {name:"<=", arity:2, infix:true,  priority:16, assoc:true,
    output:out("Red","&#163;","&le;","&lt;=",bl), tex:"leq"},

  geq  = {name:">=", arity:2,  infix:true,  priority:16, assoc:true,
    output:out("Red","&#179;","&ge;","&gt;=",bl), tex:"geq"},

  less = {name:"<",  arity:2, infix:true, priority:16, assoc:true,
    output:'<font color="Red"> &lt; </font>'},

  greater = {name:">",  arity:2, infix:true,  priority:16, assoc:true,
    output:'<font color="Red"> &gt; </font>'},

  lcover = {name:"-<",  arity:2, infix:true,  priority:16, assoc:true,
    output:'<font color="Red"> -&lt; </font>', tex:"prec"},

  ucover = {name:">-",  arity:2,  infix:true,  priority:16, assoc:true,
    output:'<font color="Red"> &gt;- </font>', tex:"succ"},

  elem = {name:"in", arity:2, infix:true, priority:16, assoc:true,
    output:out("Red","&#206;","&isin;","in",bl)},

  notelem = {name:"notin", arity:2, infix:true, priority:16, assoc:true,
    output:out("Red","&#207;","&notin;","notin",bl)},

  subset = {name:"subset", arity:2, infix:true, priority:16, assoc:true,
    output:out("Red","&#205;","&sube;","subset",bl), tex:"subseteq"}
];

conn = [
  allSymbol = {name:"AA",  arity:2, priority:14,
    output:out("Blue","&#034;","&forall;","A",""), tex:"forall"},

  exSymbol = {name:"EE",  arity:2, priority:14,
    output:out("Blue","&#036;","&exists;","E",""), tex:"exists"},

  notSymbol = {name:"not",  arity:1, priority:14,
    output:out("Blue","&#216;","&not;","not",""), tex:"neg"},
//    output:'<font color="Blue"> not </font>', tex:"neg"},

  andSymbol = {name:"and", arity:2, priority:12, infix:true, assoc:true,
    output:'&nbsp;<font color="Blue"> and </font>&nbsp;', tex:"mathrm{\\quad and\\quad}"},

  orSymbol = {name:"or", arity:2, priority:10, infix:true, assoc:true,
    output:'&nbsp;<font color="Blue"> or </font>&nbsp;', tex:"mathrm{\\quad or\\quad}"},

  impSymbol = {name:"implies", arity:2, priority:8, infix:true,
    output:out("Blue","&#222;","&rArr;","=>",bl+bl), tex:"Rightarrow"},
//    output:'&nbsp;<font color="Blue"> implies </font>&nbsp;', tex:"Rightarrow"},

  iffSymbol = {name:"iff", arity:2, priority:6, infix:true, assoc:true,
    output:out("Blue","&#219;","&hArr;","<=>",bl+bl), tex:"LeftRightarrow"},
//    output:'&nbsp;&nbsp;<font color="Blue"> iff </font>&nbsp;&nbsp;', tex:"LeftRightarrow"},

  colon = {name:":", arity:2, priority:4, infix:true, output:'&nbsp;:&nbsp;'},

  set = {name:"{", endName:"}", arity:1, priority:2, output:"{", tex:"\\{", endTex:"\\}", 
         endStr:"}"}
];

maxPriority=50;

lang = vari.concat(formulavari,oper,cons,pred); 
for (var i=0; i<lang.length; i++) lang[i].index=i; //start with variables
for (var i=0; i<vari.length; i++) {
  vari[i].vari=true; 
  vari[i].priority=maxPriority;
}
for (var i=0; i<formulavari.length; i++) {
  formulavari[i].formulavari=true; 
  formulavari[i].priority=maxPriority;
}
for (var i=0; i<cons.length; i++) {
  cons[i].cons=true;
  cons[i].priority=maxPriority;
}
for (var i=0; i<oper.length; i++) oper[i].oper=true;
for (var i=0; i<pred.length; i++) pred[i].pred=true;
for (var i=0; i<conn.length; i++) conn[i].conn=true;

punc = [
//  comma = {name:",", punc:true},
  leftBracket = {name:"(", punc:true},
  rightBracket = {name:")", punc:true}
];

function add(arr, elt){    // the push method is not supported in IExplorer
  arr[arr.length]=elt;
}

function Expr(symbol, arg, priority){ 
// the third argument is only used during parsing
  this.symbol = symbol;
  this.arg = arg;
  this.priority = priority;
}

function e2s(q) {//convert expression to string using priority
  if (q==null) return "NULL";
  if (q.arg.length==0) return q.symbol.output;
  var p=q.symbol.priority;
  var qis=q.arg[0].symbol;
  var st0 = (p>=qis.priority && !q.symbol.paren && !qis.paren && 
     (q.symbol.endStr==null && !q.symbol.infix && qis.infix || 
     q.symbol.infix && !(qis.infix && q.arg[0].arg.length==1 && qis!=minus)
     && !(q.symbol==qis && qis.assoc))?
    "("+e2s(q.arg[0])+")" : e2s(q.arg[0]));
  var st="";
  for (var i = 1; i < q.arg.length; i++) {
    if (q.symbol.paren) st = st+",";
    qis=q.arg[i].symbol;
    st = st + (!q.symbol.paren && qis.infix && p>=qis.priority &&
      q.symbol.endStr==null ? "("+e2s(q.arg[i])+")" : e2s(q.arg[i]));
  }
  if (q.symbol.paren) return q.symbol.output + "(" + st0 + st + ")";
  else if (q.symbol.infix && !(q.symbol==minus && q.arg.length==1)) 
    return st0 + q.symbol.output + st + 
      (q.symbol.endStr!=null?q.symbol.endStr:"");
  else if (q.symbol==allSymbol || q.symbol==exSymbol) 
    return q.symbol.output + st0 + "&nbsp;" + st;
  return q.symbol.output + st0 + st +
    (q.symbol.endStr!=null?q.symbol.endStr:"");
}

function latex(sym) {
  var st=sym.tex;
  if (st==null) st=sym.name;
  if ((st[0]>="A" && st[0]<="Z" || st[0]>="a" && st[0]<="z") && st.length>1)
    return "\\"+st+" ";
  else return st;
}

function e2text(q,tex) {//convert expression to string using priority
  if (q==null) return "NULL";
  if (q.arg.length==0) return (tex?latex(q.symbol):q.symbol.name);
  var p=q.symbol.priority;
  var qis=q.arg[0].symbol;
  var st0 = (p>=qis.priority && !q.symbol.paren && !qis.paren && 
     q.symbol.endStr==null && (!q.symbol.infix && qis.infix || 
     q.symbol.infix && !(qis.infix && q.arg[0].arg.length==1 && qis!=minus)
     && !(q.symbol==qis && qis.assoc))?
    "("+e2text(q.arg[0],tex)+")" : e2text(q.arg[0],tex));
  var st="";
  for (var i = 1; i < q.arg.length; i++) {
    if (q.symbol.paren) st = st+",";
    qis=q.arg[i].symbol;
    st = st + (!q.symbol.paren && qis.infix && p>=qis.priority &&
      q.symbol.endStr==null ? "("+e2text(q.arg[i],tex)+")" : e2text(q.arg[i],tex));
  }
  if (q.symbol.paren) return (tex?latex(q.symbol):q.symbol.name) + "(" + st0 + st + ")";
  else if (q.symbol.infix && !(q.symbol==minus && q.arg.length==1)) 
    return st0 + (tex?latex(q.symbol):(q.arg.length==1?q.symbol.name:
           " "+q.symbol.name+" ")) + st + 
      (q.symbol.endStr!=null?(tex?latex(q.symbol.endTex):q.symbol.endName):"");
  else if (q.symbol==allSymbol || q.symbol==exSymbol) 
    return (tex?latex(q.symbol):q.symbol.name) + st0 + " " + st;
  return (tex?latex(q.symbol):q.symbol.name) + st0 + st +
    (q.symbol.endStr!=null?(tex?latex(q.symbol.endTex):q.symbol.endName):"");
}

function exprtree(q) {//convert expression to string
  if (q==null) return "NULL";
  var st="";
  if (q.arg.length>0) {
    st = exprtree(q.arg[0]);
    for (var i = 1; i < q.arg.length; i++)
      st = st + "," + exprtree(q.arg[i]);
    return q.symbol.output + "(" + st + ")";
  }
  return q.symbol.output;
}

//Parsing ascii strings into expressions

//First we concatenate all the symbol arrays and sort the symbols

function compareNames(s1,s2) {
  if (s1.name > s2.name) return 1
  else return -1;
}

names=[];
other=[]; // additional list of symbols created during parsing

function initSymbols() {
  symbols = vari.concat(formulavari,cons, oper, pred, conn, punc, other);
  symbols.sort(compareNames);
  names = [];
  for (var i=0; i<symbols.length; i++) names[i] = symbols[i].name;

  maxPriority=0;
  for (var i=0; i<symbols.length; i++)
    if (symbols[i].priority && symbols[i].priority>maxPriority)
      maxPriority = symbols[i].priority;
  maxPriority++;
}

function newSymbol(st,outst){
  if (outst=="") outst=st;
  if (outst.length==1 && (("A"<=outst && outst<="Z") || ("a"<=outst && outst<="z"))) outst="<i>"+outst+"</i>";
  add(other,{name:st, output:outst, paren:true, index:symbols.length});
  var k=position(names,st,0);
  names=names.slice(0,k).concat([st],names.slice(k));
  symbols=symbols.slice(0,k).concat(other.slice(other.length-1),symbols.slice(k));
}

initSymbols();

//names should contain no blanks

function removeCharsAndBlanks(str,n) {
  var st = str.slice(n);
  for (var i=0; i<st.length && st.charCodeAt(i)<=32; i=i+1);
  return st.slice(i);
}

function position(arr, str, n) { 
// return position >=n where str appears or would be inserted
// assumes arr is sorted
  if (n==0) {
    var h,m;
    n=-1;
    h=arr.length;
    while (n+1<h) {
      m=(n+h) >> 1;
      if (arr[m]<str) n=m; else h=m;
    }
    return h;
  } else
    for (var i=n; i<arr.length && arr[i]<str; i++);
  return i; // i=arr.length || arr[i]>=str
}

function getSymbol(str) {
//return maximal initial substring of str that appears in names
//return null if there is none
  var k=0; //new pos
  var j=0; //old pos
  var mk; //match pos
  var st;
  var match="";
  var more = true;
  for (var i=1; i<=str.length && more; i++) {
    st=str.slice(0,i); //initial substring of length i
    j=k;
    k=position(names, st, j);
    if (k<names.length && str.slice(0,names[k].length)==names[k]){
      match=names[k];
      mk=k;
      i=match.length;
    }
    more=k<names.length && str.slice(0,names[k].length)>=names[k];
  }
  if (match!="") return symbols[mk]; 
// if str[0] is a letter return maxsubstring of letters followed by numbers
  k=1;
  st=str.slice(0,1);
  while (k<=str.length && ("A"<=st && st<="Z") || ("a"<=st && st<="z")) {
    st=str.slice(k,k+1);
    k++;
  }
// if str[0] is a number return maxsubstring of numbers
  while (k<=str.length && "0"<=st && st<="9") {
    st=str.slice(k,k+1);
    k++;
  }
  st=str.slice(0,k-1);
  if (k>1) {
    newSymbol(st,st);
    return symbols[position(names, st, 0)];
  }
// otherwise return null
  return null;
}

//document.writeln("names=",names,"<p>");

/*
blanks ( ) [ ] , are seperators (not allowed in names of symbols)
Arguments may be separated by spaces or single commas.
Parenthesis must be balanced.

Symbols are either terminal (arity==0) or prefix or infix (unary infix defaults to postfix)

expr ::= terminal | prefix expr1 ... exprN | prefix(expr1, ... , exprN) | 

         expr infix | expr1 infix exprN | (expr) | [expr]

Expressions are read from left to right, choosing the longest initial substring
that matches the name of a symbol. Symbol priority is taken into account.
*/

function str2expr(str,pri) { //builds expr and returns [expr,substring]
// If returned substring starts with "Error: ..." it contains information about a parsing error
  var arg=[];
  var token="";
  var symbol;
  var nextsymbol;
  var expr;
  var estr;
  var more;
  str=removeCharsAndBlanks(str,0);
  symbol=getSymbol(str); //either a symbol or a bracket or empty
//document.writeln("@@",str,"#",symbol,"<br>");
  if (symbol==null||symbol==rightBracket) 
    return [null,"Error, '(' or symbol expected: "+str];
  str=removeCharsAndBlanks(str,symbol.name.length); 

  if (symbol==leftBracket) {                        //read (expr)
    estr=str2expr(str,0);expr=estr[0];str=estr[1];
    if (str.slice(0,5)=="Error") return [null,str];
    expr.priority = maxPriority;
    symbol=getSymbol(str);
    if (symbol==null) return [null,"Error, unknown symbol: "+str];
    str=removeCharsAndBlanks(str,symbol.name.length); 
    if (symbol!=rightBracket) return [null,"Error, ')' expected : "+str];

  } else if (symbol.arity==0 || symbol.infix && symbol!=minus) {//read terminal
    expr=new Expr(symbol,[], maxPriority);

  } else {                  //read prefix expr1 ... exprN or prefix(...)
    nextsymbol = getSymbol(str);
    var commas=false;
    if (nextsymbol==leftBracket && symbol.paren) {
      str = removeCharsAndBlanks(str,nextsymbol.name.length);
      nextsymbol = getSymbol(str);
      commas=true;
    }
    more = nextsymbol!=null && 
      (commas || !nextsymbol.infix && !nextsymbol.endSymb) && //nextsymbol!=norm) && 
      nextsymbol!=rightBracket && nextsymbol!=comma;
    while (str!="" && more && (symbol.endName==null||
        symbol.endName!=str.slice(0,symbol.endName.length))) {
      nextsymbol = getSymbol(str);
//document.write(symbol.name,nextsymbol.name,"@",str,"<br>");
      if (nextsymbol==null) return [null,"Error, incorrect symbol: "+str];
      if (commas)
        estr=str2expr(str,-1);
      else estr=str2expr(str,symbol.priority);
      expr=estr[0];str=estr[1];
      if (str.slice(0,5)=="Error") return [null,str];
      add(arg,expr);
      nextsymbol = getSymbol(str);
      more = nextsymbol != null && 
        (commas || !nextsymbol.infix && nextsymbol!=norm) &&
        nextsymbol!=rightBracket;
      if (commas)
        if (str=="") return [null,"Error, ')' expected: "+str];
        else if (str.charAt(0)==",") str=removeCharsAndBlanks(str,1);
        else if (str.charAt(0)==")") {
          more = false;
          str=removeCharsAndBlanks(str,1);
        }
    }
    expr=new Expr(symbol, arg, maxPriority);
    if (symbol.endName!=null) {
//document.write(symbol.name,"#",str,"<br>");
      if (symbol.endName!=str.slice(0,symbol.endName.length))
        return [null,"Error, "+symbol.endName+" expected: "+str];
      str=removeCharsAndBlanks(str,symbol.endName.length);
    }
  }
//parse right argument of infix operation
  symbol = getSymbol(str);// infix op symb
  var insert=null;
  if (symbol==null || !symbol.infix || symbol.priority<pri || 
    symbol==comma && pri==-1) 
    return [expr,str];//complete subterm
  str = removeCharsAndBlanks(str,symbol.name.length);
  more=true;
  while (more) {
    more=false;
    nextsymbol = getSymbol(str);
    if (str=="" || str.charAt(0)==")" || nextsymbol!=null && 
      nextsymbol.infix && nextsymbol!=minus) { //unary infix symbol
      expr=new Expr(symbol, [expr], maxPriority); 
      more=nextsymbol!=null && nextsymbol.infix;
      if (!more) return [expr,str];
      symbol = nextsymbol;
      str = removeCharsAndBlanks(str,symbol.name.length);
    }
  }
  var right=null;
  estr = str2expr(str,pri);right=estr[0];str=estr[1];
  if (str.slice(0,5)=="Error") return [null,str];
  if (right.priority>symbol.priority || 
      (right.priority==symbol.priority && right.symbol.rightparen)) {
    expr=new Expr(symbol, [expr,right], symbol.priority); 
  } else {
    insert=right;
    while (insert.arg[0].priority && insert.arg[0].priority<symbol.priority) 
      insert=insert.arg[0];
    if (insert.arg[0].priority>symbol.priority || 
      insert.arg[0].symbol.rightparen) {
      expr=new Expr(symbol, [expr,insert.arg[0]], symbol.priority);
      insert.arg[0]=expr;
    } else {
      expr=new Expr(symbol, [expr,insert.arg[0].arg[0]], symbol.priority);
      insert.arg[0].arg[0]=expr;
    }
    expr=right;
  }
  return [expr,str];
}

function parse(str) {
  for (var i=str.length-1; i>=0 && str.charCodeAt(i)<=32; i--);
  str=str.slice(0,i+1);
  var temp = str2expr(str,0);
  if (temp[1].slice(0,5)=="Error") document.write("<P>"+temp[1]+"<P>");
  else if (temp[1].slice(0,5)!="") {
    alert("Parse Leftover: >>>"+temp[1]+"<<<");
  } //arities of expressions are not checked
  return temp[0];
}

mathdelimit="`";

function p(str) {
  var arr=str.split(mathdelimit); // string to delimit math expressions
  var expr=false;
  for (var i=0; i<arr.length; i++) {
    if (expr) {
      ex = parse(arr[i]);
      document.write(e2s(ex));
    }
    else document.writeln(
      (arr[i].charAt(0)==","||arr[i].charAt(0)=="."||arr[i].charAt(0)=="-"?
      "":" &nbsp; "),arr[i]," &nbsp; ");
    expr=!expr;
  }
}

function parseascii(str) {
  var arr=str.split(mathdelimit); 
  var expr=false;
  for (var i=0; i<arr.length; i++) {
    if (expr) 
      arr[i] = e2s(parse(arr[i]));
    else arr[i]=
      (arr[i].charAt(0)==","||arr[i].charAt(0)=="."||arr[i].charAt(0)=="-"?
      "":" &nbsp; ")+arr[i]+" &nbsp; ";
    expr=!expr;
  }
  return arr.join("");
}












