]> git.lyx.org Git - lyx.git/blob - src/mathed/MathExtern.cpp
Fix a few rename inconsistencies discovered by JMarc in insets and mathed, step 1
[lyx.git] / src / mathed / MathExtern.cpp
1 /**
2  * \file MathExtern.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 // This file contains most of the magic that extracts "context
12 // information" from the unstructered layout-oriented stuff in an
13 // MathArray.
14
15 #include <config.h>
16
17 #include "MathExtern.h"
18 #include "InsetMathArray.h"
19 #include "InsetMathChar.h"
20 #include "InsetMathDelim.h"
21 #include "MathArray.h"
22 #include "InsetMathDiff.h"
23 #include "InsetMathExFunc.h"
24 #include "InsetMathExInt.h"
25 #include "InsetMathFont.h"
26 #include "InsetMathFrac.h"
27 #include "InsetMathLim.h"
28 #include "InsetMathMatrix.h"
29 #include "MathStream.h"
30 #include "InsetMathNumber.h"
31 #include "InsetMathScript.h"
32 #include "InsetMathString.h"
33 #include "InsetMathSymbol.h"
34 #include "MathParser.h"
35 #include "debug.h"
36 #include "support/filetools.h"
37 #include "support/lstrings.h"
38 #include "support/lyxlib.h"
39 #include "frontends/controllers/ControlMath.h"
40
41 #include <algorithm>
42 #include <sstream>
43 #include <fstream>
44
45
46 namespace lyx {
47
48 using support::cmd_ret;
49 using support::getVectorFromString;
50 using support::libFileSearch;
51 using support::runCommand;
52 using support::FileName;
53 using support::quoteName;
54 using support::tempName;
55 using support::unlink;
56 using support::subst;
57
58 using frontend::function_names;
59
60 using std::endl;
61 using std::find_if;
62 using std::auto_ptr;
63 using std::istringstream;
64 using std::ostream;
65 using std::swap;
66 using std::string;
67 using std::vector;
68
69 static size_t const npos = lyx::docstring::npos;
70
71 // define a function for tests
72 typedef bool TestItemFunc(MathAtom const &);
73
74 // define a function for replacing subexpressions
75 typedef MathAtom ReplaceArgumentFunc(const MathArray & ar);
76
77
78
79 // try to extract a super/subscript
80 // modify iterator position to point behind the thing
81 bool extractScript(MathArray & ar,
82         MathArray::iterator & pos, MathArray::iterator last, bool superscript)
83 {
84         // nothing to get here
85         if (pos == last)
86                 return false;
87
88         // is this a scriptinset?
89         if (!(*pos)->asScriptInset())
90                 return false;
91
92         // do we want superscripts only?
93         if (superscript && !(*pos)->asScriptInset()->hasUp())
94                 return false;
95
96         // it is a scriptinset, use it.
97         ar.push_back(*pos);
98         ++pos;
99         return true;
100 }
101
102
103 // try to extract an "argument" to some function.
104 // returns position behind the argument
105 MathArray::iterator extractArgument(MathArray & ar,
106         MathArray::iterator pos, MathArray::iterator last, bool function = false)
107 {
108         // nothing to get here
109         if (pos == last)
110                 return pos;
111
112         // something delimited _is_ an argument
113         if ((*pos)->asDelimInset()) {
114                 // leave out delimiters if this is a function argument
115                 if (function) {
116                         MathArray const & arg = (*pos)->asDelimInset()->cell(0);
117                         MathArray::const_iterator cur = arg.begin();
118                         MathArray::const_iterator end = arg.end();
119                         while (cur != end)
120                                 ar.push_back(*cur++);
121                 } else
122                         ar.push_back(*pos);
123                 ++pos;
124                 if (pos == last)
125                         return pos;
126                 // if there's one, get following superscript only if this
127                 // isn't a function argument
128                 if (!function)
129                         extractScript(ar, pos, last, true);
130                 return pos;
131         }
132
133         // always take the first thing, no matter what it is
134         ar.push_back(*pos);
135
136         // go ahead if possible
137         ++pos;
138         if (pos == last)
139                 return pos;
140
141         // if the next item is a super/subscript, it most certainly belongs
142         // to the thing we have
143         extractScript(ar, pos, last, false);
144         if (pos == last)
145                 return pos;
146
147         // but it might be more than that.
148         // FIXME: not implemented
149         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
150         //      // always take the first thing, no matter
151         //      if (it == pos) {
152         //              ar.push_back(*it);
153         //              continue;
154         //      }
155         //}
156         return pos;
157 }
158
159
160 // returns sequence of char with same code starting at it up to end
161 // it might be less, though...
162 docstring charSequence
163         (MathArray::const_iterator it, MathArray::const_iterator end)
164 {
165         docstring s;
166         for (; it != end && (*it)->asCharInset(); ++it)
167                 s += (*it)->getChar();
168         return s;
169 }
170
171
172 void extractStrings(MathArray & ar)
173 {
174         //lyxerr << "\nStrings from: " << ar << endl;
175         for (size_t i = 0; i < ar.size(); ++i) {
176                 if (!ar[i]->asCharInset())
177                         continue;
178                 docstring s = charSequence(ar.begin() + i, ar.end());
179                 ar[i] = MathAtom(new InsetMathString(s));
180                 ar.erase(i + 1, i + s.size());
181         }
182         //lyxerr << "\nStrings to: " << ar << endl;
183 }
184
185
186 void extractMatrices(MathArray & ar)
187 {
188         //lyxerr << "\nMatrices from: " << ar << endl;
189         // first pass for explicitly delimited stuff
190         for (size_t i = 0; i < ar.size(); ++i) {
191                 if (!ar[i]->asDelimInset())
192                         continue;
193                 MathArray const & arr = ar[i]->asDelimInset()->cell(0);
194                 if (arr.size() != 1)
195                         continue;
196                 if (!arr.front()->asGridInset())
197                         continue;
198                 ar[i] = MathAtom(new InsetMathMatrix(*(arr.front()->asGridInset())));
199         }
200
201         // second pass for AMS "pmatrix" etc
202         for (size_t i = 0; i < ar.size(); ++i)
203                 if (ar[i]->asAMSArrayInset())
204                         ar[i] = MathAtom(new InsetMathMatrix(*(ar[i]->asGridInset())));
205         //lyxerr << "\nMatrices to: " << ar << endl;
206 }
207
208
209 // convert this inset somehow to a string
210 bool extractString(MathAtom const & at, docstring & str)
211 {
212         if (at->getChar()) {
213                 str = docstring(1, at->getChar());
214                 return true;
215         }
216         if (at->asStringInset()) {
217                 str = at->asStringInset()->str();
218                 return true;
219         }
220         return false;
221 }
222
223
224 // is this a known function?
225 bool isKnownFunction(docstring const & str)
226 {
227         for (int i = 0; *function_names[i]; ++i) {
228                 if (str == function_names[i])
229                         return true;
230         }
231         return false;
232 }
233
234
235 // extract a function name from this inset
236 bool extractFunctionName(MathAtom const & at, docstring & str)
237 {
238         if (at->asSymbolInset()) {
239                 str = at->asSymbolInset()->name();
240                 return isKnownFunction(str);
241         }
242         if (at->asUnknownInset()) {
243                 // assume it is well known...
244                 str = at->name();
245                 return true;
246         }
247         if (at->asFontInset() && at->name() == "mathrm") {
248                 // assume it is well known...
249                 MathArray const & ar = at->asFontInset()->cell(0);
250                 str = charSequence(ar.begin(), ar.end());
251                 return ar.size() == str.size();
252         }
253         return false;
254 }
255
256
257 // convert this inset somehow to a number
258 bool extractNumber(MathArray const & ar, int & i)
259 {
260         idocstringstream is(charSequence(ar.begin(), ar.end()));
261         is >> i;
262         return is;
263 }
264
265
266 bool extractNumber(MathArray const & ar, double & d)
267 {
268         idocstringstream is(charSequence(ar.begin(), ar.end()));
269         is >> d;
270         return is;
271 }
272
273
274 bool testString(MathAtom const & at, docstring const & str)
275 {
276         docstring s;
277         return extractString(at, s) && str == s;
278 }
279
280
281 bool testString(MathAtom const & at, char const * const str)
282 {
283         return testString(at, from_ascii(str));
284 }
285
286 // search end of nested sequence
287 MathArray::iterator endNestSearch(
288         MathArray::iterator it,
289         MathArray::iterator last,
290         TestItemFunc testOpen,
291         TestItemFunc testClose
292 )
293 {
294         for (int level = 0; it != last; ++it) {
295                 if (testOpen(*it))
296                         ++level;
297                 if (testClose(*it))
298                         --level;
299                 if (level == 0)
300                         break;
301         }
302         return it;
303 }
304
305
306 // replace nested sequences by a real Insets
307 void replaceNested(
308         MathArray & ar,
309         TestItemFunc testOpen,
310         TestItemFunc testClose,
311         ReplaceArgumentFunc replaceArg
312 )
313 {
314         // use indices rather than iterators for the loop  because we are going
315         // to modify the array.
316         for (size_t i = 0; i < ar.size(); ++i) {
317                 // check whether this is the begin of the sequence
318                 if (!testOpen(ar[i]))
319                         continue;
320
321                 // search end of sequence
322                 MathArray::iterator it = ar.begin() + i;
323                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
324                 if (jt == ar.end())
325                         continue;
326
327                 // replace the original stuff by the new inset
328                 ar[i] = replaceArg(MathArray(it + 1, jt));
329                 ar.erase(it + 1, jt + 1);
330         }
331 }
332
333
334
335 //
336 // split scripts into seperate super- and subscript insets. sub goes in
337 // front of super...
338 //
339
340 void splitScripts(MathArray & ar)
341 {
342         //lyxerr << "\nScripts from: " << ar << endl;
343         for (size_t i = 0; i < ar.size(); ++i) {
344                 InsetMathScript const * script = ar[i]->asScriptInset();
345
346                 // is this a script inset and do we also have a superscript?
347                 if (!script || !script->hasUp())
348                         continue;
349
350                 // we must have a nucleus if we only have a superscript
351                 if (!script->hasDown() && script->nuc().size() == 0)
352                         continue;
353
354                 if (script->nuc().size() == 1) {
355                         // leave alone sums and integrals
356                         InsetMathSymbol const * sym =
357                                 script->nuc().front()->asSymbolInset();
358                         if (sym && (sym->name() == "sum" || sym->name() == "int"))
359                                 continue;
360                 }
361
362                 // create extra script inset and move superscript over
363                 InsetMathScript * p = ar[i].nucleus()->asScriptInset();
364                 auto_ptr<InsetMathScript> q(new InsetMathScript(true));
365                 swap(q->up(), p->up());
366                 p->removeScript(true);
367
368                 // if we don't have a subscript, get rid of the ScriptInset
369                 if (!script->hasDown()) {
370                         MathArray arg(p->nuc());
371                         MathArray::const_iterator it = arg.begin();
372                         MathArray::const_iterator et = arg.end();
373                         ar.erase(i);
374                         while (it != et)
375                                 ar.insert(i++, *it++);
376                 } else
377                         ++i;
378
379                 // insert new inset behind
380                 ar.insert(i, MathAtom(q.release()));
381         }
382         //lyxerr << "\nScripts to: " << ar << endl;
383 }
384
385
386 //
387 // extract exp(...)
388 //
389
390 void extractExps(MathArray & ar)
391 {
392         //lyxerr << "\nExps from: " << ar << endl;
393         for (size_t i = 0; i + 1 < ar.size(); ++i) {
394                 // is this 'e'?
395                 if (ar[i]->getChar() != 'e')
396                         continue;
397
398                 // we need an exponent but no subscript
399                 InsetMathScript const * sup = ar[i + 1]->asScriptInset();
400                 if (!sup || sup->hasDown())
401                         continue;
402
403                 // create a proper exp-inset as replacement
404                 ar[i] = MathAtom(new InsetMathExFunc(from_ascii("exp"), sup->cell(1)));
405                 ar.erase(i + 1);
406         }
407         //lyxerr << "\nExps to: " << ar << endl;
408 }
409
410
411 //
412 // extract det(...)  from |matrix|
413 //
414 void extractDets(MathArray & ar)
415 {
416         //lyxerr << "\ndet from: " << ar << endl;
417         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
418                 InsetMathDelim const * del = (*it)->asDelimInset();
419                 if (!del)
420                         continue;
421                 if (!del->isAbs())
422                         continue;
423                 *it = MathAtom(new InsetMathExFunc(from_ascii("det"), del->cell(0)));
424         }
425         //lyxerr << "\ndet to: " << ar << endl;
426 }
427
428
429 //
430 // search numbers
431 //
432
433 bool isDigitOrSimilar(char_type c)
434 {
435         return ('0' <= c && c <= '9') || c == '.';
436 }
437
438
439 // returns sequence of digits
440 docstring digitSequence
441         (MathArray::const_iterator it, MathArray::const_iterator end)
442 {
443         docstring s;
444         for (; it != end && (*it)->asCharInset(); ++it) {
445                 if (!isDigitOrSimilar((*it)->getChar()))
446                         break;
447                 s += (*it)->getChar();
448         }
449         return s;
450 }
451
452
453 void extractNumbers(MathArray & ar)
454 {
455         //lyxerr << "\nNumbers from: " << ar << endl;
456         for (size_t i = 0; i < ar.size(); ++i) {
457                 if (!ar[i]->asCharInset())
458                         continue;
459                 if (!isDigitOrSimilar(ar[i]->asCharInset()->getChar()))
460                         continue;
461
462                 docstring s = digitSequence(ar.begin() + i, ar.end());
463
464                 ar[i] = MathAtom(new InsetMathNumber(s));
465                 ar.erase(i + 1, i + s.size());
466         }
467         //lyxerr << "\nNumbers to: " << ar << endl;
468 }
469
470
471
472 //
473 // search delimiters
474 //
475
476 bool testOpenParen(MathAtom const & at)
477 {
478         return testString(at, "(");
479 }
480
481
482 bool testCloseParen(MathAtom const & at)
483 {
484         return testString(at, ")");
485 }
486
487
488 MathAtom replaceParenDelims(const MathArray & ar)
489 {
490         return MathAtom(new InsetMathDelim(from_ascii("("), from_ascii(")"), ar));
491 }
492
493
494 bool testOpenBracket(MathAtom const & at)
495 {
496         return testString(at, "[");
497 }
498
499
500 bool testCloseBracket(MathAtom const & at)
501 {
502         return testString(at, "]");
503 }
504
505
506 MathAtom replaceBracketDelims(const MathArray & ar)
507 {
508         return MathAtom(new InsetMathDelim(from_ascii("["), from_ascii("]"), ar));
509 }
510
511
512 // replace '('...')' and '['...']' sequences by a real InsetMathDelim
513 void extractDelims(MathArray & ar)
514 {
515         //lyxerr << "\nDelims from: " << ar << endl;
516         replaceNested(ar, testOpenParen, testCloseParen, replaceParenDelims);
517         replaceNested(ar, testOpenBracket, testCloseBracket, replaceBracketDelims);
518         //lyxerr << "\nDelims to: " << ar << endl;
519 }
520
521
522
523 //
524 // search well-known functions
525 //
526
527
528 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real InsetMathExFunc
529 // assume 'extractDelims' ran before
530 void extractFunctions(MathArray & ar)
531 {
532         // we need at least two items...
533         if (ar.size() < 2)
534                 return;
535
536         //lyxerr << "\nFunctions from: " << ar << endl;
537         for (size_t i = 0; i + 1 < ar.size(); ++i) {
538                 MathArray::iterator it = ar.begin() + i;
539                 MathArray::iterator jt = it + 1;
540
541                 docstring name;
542                 // is it a function?
543                 // it certainly is if it is well known...
544                 if (!extractFunctionName(*it, name)) {
545                         // is this a user defined function?
546                         // it it probably not, if it doesn't have a name.
547                         if (!extractString(*it, name))
548                                 continue;
549                         // it is not if it has no argument
550                         if (jt == ar.end())
551                                 continue;
552                         // guess so, if this is followed by
553                         // a DelimInset with a single item in the cell
554                         InsetMathDelim const * del = (*jt)->asDelimInset();
555                         if (!del || del->cell(0).size() != 1)
556                                 continue;
557                         // fall trough into main branch
558                 }
559
560                 // do we have an exponent like in
561                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
562                 MathArray exp;
563                 extractScript(exp, jt, ar.end(), true);
564
565                 // create a proper inset as replacement
566                 auto_ptr<InsetMathExFunc> p(new InsetMathExFunc(name));
567
568                 // jt points to the "argument". Get hold of this.
569                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end(), true);
570
571                 // replace the function name by a real function inset
572                 *it = MathAtom(p.release());
573
574                 // remove the source of the argument from the array
575                 ar.erase(it + 1, st);
576
577                 // re-insert exponent
578                 ar.insert(i + 1, exp);
579                 //lyxerr << "\nFunctions to: " << ar << endl;
580         }
581 }
582
583
584 //
585 // search integrals
586 //
587
588 bool testSymbol(MathAtom const & at, docstring const & name)
589 {
590         return at->asSymbolInset() && at->asSymbolInset()->name() == name;
591 }
592
593
594 bool testSymbol(MathAtom const & at, char const * const name)
595 {
596         return at->asSymbolInset() && at->asSymbolInset()->name() == from_ascii(name);
597 }
598
599
600 bool testIntSymbol(MathAtom const & at)
601 {
602         return testSymbol(at, from_ascii("int"));
603 }
604
605
606 bool testIntegral(MathAtom const & at)
607 {
608         return
609          testIntSymbol(at) ||
610                 ( at->asScriptInset()
611                   && at->asScriptInset()->nuc().size()
612                         && testIntSymbol(at->asScriptInset()->nuc().back()) );
613 }
614
615
616
617 bool testIntDiff(MathAtom const & at)
618 {
619         return testString(at, "d");
620 }
621
622
623 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real InsetMathExInt
624 // assume 'extractDelims' ran before
625 void extractIntegrals(MathArray & ar)
626 {
627         // we need at least three items...
628         if (ar.size() < 3)
629                 return;
630
631         //lyxerr << "\nIntegrals from: " << ar << endl;
632         for (size_t i = 0; i + 1 < ar.size(); ++i) {
633                 MathArray::iterator it = ar.begin() + i;
634
635                 // search 'd'
636                 MathArray::iterator jt =
637                         endNestSearch(it, ar.end(), testIntegral, testIntDiff);
638
639                 // something sensible found?
640                 if (jt == ar.end())
641                         continue;
642
643                 // is this a integral name?
644                 if (!testIntegral(*it))
645                         continue;
646
647                 // core ist part from behind the scripts to the 'd'
648                 auto_ptr<InsetMathExInt> p(new InsetMathExInt(from_ascii("int")));
649
650                 // handle scripts if available
651                 if (!testIntSymbol(*it)) {
652                         p->cell(2) = (*it)->asScriptInset()->down();
653                         p->cell(3) = (*it)->asScriptInset()->up();
654                 }
655                 p->cell(0) = MathArray(it + 1, jt);
656
657                 // use the "thing" behind the 'd' as differential
658                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
659
660                 // remove used parts
661                 ar.erase(it + 1, tt);
662                 *it = MathAtom(p.release());
663         }
664         //lyxerr << "\nIntegrals to: " << ar << endl;
665 }
666
667
668 bool testTermDelimiter(MathAtom const & at)
669 {
670         return testString(at, "+") || testString(at, "-");
671 }
672
673
674 // try to extract a "term", i.e., something delimited by '+' or '-'.
675 // returns position behind the term
676 MathArray::iterator extractTerm(MathArray & ar,
677         MathArray::iterator pos, MathArray::iterator last)
678 {
679         while (pos != last && !testTermDelimiter(*pos)) {
680                 ar.push_back(*pos);
681                 ++pos;
682         }
683         return pos;
684 }
685
686
687 //
688 // search sums
689 //
690
691
692 bool testEqualSign(MathAtom const & at)
693 {
694         return testString(at, "=");
695 }
696
697
698 bool testSumSymbol(MathAtom const & p)
699 {
700         return testSymbol(p, from_ascii("sum"));
701 }
702
703
704 bool testSum(MathAtom const & at)
705 {
706         return
707          testSumSymbol(at) ||
708                 ( at->asScriptInset()
709                   && at->asScriptInset()->nuc().size()
710                         && testSumSymbol(at->asScriptInset()->nuc().back()) );
711 }
712
713
714 // replace '\sum' ['_^'] f(x) sequences by a real InsetMathExInt
715 // assume 'extractDelims' ran before
716 void extractSums(MathArray & ar)
717 {
718         // we need at least two items...
719         if (ar.size() < 2)
720                 return;
721
722         //lyxerr << "\nSums from: " << ar << endl;
723         for (size_t i = 0; i + 1 < ar.size(); ++i) {
724                 MathArray::iterator it = ar.begin() + i;
725
726                 // is this a sum name?
727                 if (!testSum(ar[i]))
728                         continue;
729
730                 // create a proper inset as replacement
731                 auto_ptr<InsetMathExInt> p(new InsetMathExInt(from_ascii("sum")));
732
733                 // collect lower bound and summation index
734                 InsetMathScript const * sub = ar[i]->asScriptInset();
735                 if (sub && sub->hasDown()) {
736                         // try to figure out the summation index from the subscript
737                         MathArray const & ar = sub->down();
738                         MathArray::const_iterator xt =
739                                 find_if(ar.begin(), ar.end(), &testEqualSign);
740                         if (xt != ar.end()) {
741                                 // we found a '=', use everything in front of that as index,
742                                 // and everything behind as lower index
743                                 p->cell(1) = MathArray(ar.begin(), xt);
744                                 p->cell(2) = MathArray(xt + 1, ar.end());
745                         } else {
746                                 // use everything as summation index, don't use scripts.
747                                 p->cell(1) = ar;
748                         }
749                 }
750
751                 // collect upper bound
752                 if (sub && sub->hasUp())
753                         p->cell(3) = sub->up();
754
755                 // use something  behind the script as core
756                 MathArray::iterator tt = extractTerm(p->cell(0), it + 1, ar.end());
757
758                 // cleanup
759                 ar.erase(it + 1, tt);
760                 *it = MathAtom(p.release());
761         }
762         //lyxerr << "\nSums to: " << ar << endl;
763 }
764
765
766 //
767 // search differential stuff
768 //
769
770 // tests for 'd' or '\partial'
771 bool testDiffItem(MathAtom const & at)
772 {
773         if (testString(at, "d") || testSymbol(at, "partial"))
774                 return true;
775
776         // we may have d^n .../d and splitScripts() has not yet seen it
777         InsetMathScript const * sup = at->asScriptInset();
778         if (sup && !sup->hasDown() && sup->hasUp() && sup->nuc().size() == 1) {
779                 MathAtom const & ma = sup->nuc().front();
780                 return testString(ma, "d") || testSymbol(ma, "partial");
781         }
782         return false;
783 }
784
785
786 bool testDiffArray(MathArray const & ar)
787 {
788         return ar.size() && testDiffItem(ar.front());
789 }
790
791
792 bool testDiffFrac(MathAtom const & at)
793 {
794         return
795                 at->asFracInset()
796                         && testDiffArray(at->asFracInset()->cell(0))
797                         && testDiffArray(at->asFracInset()->cell(1));
798 }
799
800
801 void extractDiff(MathArray & ar)
802 {
803         //lyxerr << "\nDiffs from: " << ar << endl;
804         for (size_t i = 0; i < ar.size(); ++i) {
805                 MathArray::iterator it = ar.begin() + i;
806
807                 // is this a "differential fraction"?
808                 if (!testDiffFrac(*it))
809                         continue;
810
811                 InsetMathFrac const * f = (*it)->asFracInset();
812                 if (!f) {
813                         lyxerr << "should not happen" << endl;
814                         continue;
815                 }
816
817                 // create a proper diff inset
818                 auto_ptr<InsetMathDiff> diff(new InsetMathDiff);
819
820                 // collect function, let jt point behind last used item
821                 MathArray::iterator jt = it + 1;
822                 //int n = 1;
823                 MathArray numer(f->cell(0));
824                 splitScripts(numer);
825                 if (numer.size() > 1 && numer[1]->asScriptInset()) {
826                         // this is something like  d^n f(x) / d... or  d^n / d...
827                         // FIXME
828                         //n = 1;
829                         if (numer.size() > 2)
830                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
831                         else
832                                 jt = extractTerm(diff->cell(0), jt, ar.end());
833                 } else {
834                         // simply d f(x) / d... or  d/d...
835                         if (numer.size() > 1)
836                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
837                         else
838                                 jt = extractTerm(diff->cell(0), jt, ar.end());
839                 }
840
841                 // collect denominator parts
842                 MathArray denom(f->cell(1));
843                 splitScripts(denom);
844                 for (MathArray::iterator dt = denom.begin(); dt != denom.end();) {
845                         // find the next 'd'
846                         MathArray::iterator et
847                                 = find_if(dt + 1, denom.end(), &testDiffItem);
848
849                         // point before this
850                         MathArray::iterator st = et - 1;
851                         InsetMathScript const * script = (*st)->asScriptInset();
852                         if (script && script->hasUp()) {
853                                 // things like   d.../dx^n
854                                 int mult = 1;
855                                 if (extractNumber(script->up(), mult)) {
856                                         //lyxerr << "mult: " << mult << endl;
857                                         for (int i = 0; i < mult; ++i)
858                                                 diff->addDer(MathArray(dt + 1, st));
859                                 }
860                         } else {
861                                 // just  d.../dx
862                                 diff->addDer(MathArray(dt + 1, et));
863                         }
864                         dt = et;
865                 }
866
867                 // cleanup
868                 ar.erase(it + 1, jt);
869                 *it = MathAtom(diff.release());
870         }
871         //lyxerr << "\nDiffs to: " << ar << endl;
872 }
873
874
875 //
876 // search limits
877 //
878
879
880 bool testRightArrow(MathAtom const & at)
881 {
882         return testSymbol(at, "to") || testSymbol(at, "rightarrow");
883 }
884
885
886
887 // replace '\lim_{x->x0} f(x)' sequences by a real InsetMathLim
888 // assume 'extractDelims' ran before
889 void extractLims(MathArray & ar)
890 {
891         //lyxerr << "\nLimits from: " << ar << endl;
892         for (size_t i = 0; i < ar.size(); ++i) {
893                 MathArray::iterator it = ar.begin() + i;
894
895                 // must be a script inset with a subscript (without superscript)
896                 InsetMathScript const * sub = (*it)->asScriptInset();
897                 if (!sub || !sub->hasDown() || sub->hasUp() || sub->nuc().size() != 1)
898                         continue;
899
900                 // is this a limit function?
901                 if (!testSymbol(sub->nuc().front(), "lim"))
902                         continue;
903
904                 // subscript must contain a -> symbol
905                 MathArray const & s = sub->down();
906                 MathArray::const_iterator st = find_if(s.begin(), s.end(), &testRightArrow);
907                 if (st == s.end())
908                         continue;
909
910                 // the -> splits the subscript int x and x0
911                 MathArray x  = MathArray(s.begin(), st);
912                 MathArray x0 = MathArray(st + 1, s.end());
913
914                 // use something behind the script as core
915                 MathArray f;
916                 MathArray::iterator tt = extractTerm(f, it + 1, ar.end());
917
918                 // cleanup
919                 ar.erase(it + 1, tt);
920
921                 // create a proper inset as replacement
922                 *it = MathAtom(new InsetMathLim(f, x, x0));
923         }
924         //lyxerr << "\nLimits to: " << ar << endl;
925 }
926
927
928 //
929 // combine searches
930 //
931
932 void extractStructure(MathArray & ar)
933 {
934         //lyxerr << "\nStructure from: " << ar << endl;
935         splitScripts(ar);
936         extractDelims(ar);
937         extractIntegrals(ar);
938         extractSums(ar);
939         extractNumbers(ar);
940         extractMatrices(ar);
941         extractFunctions(ar);
942         extractDets(ar);
943         extractDiff(ar);
944         extractExps(ar);
945         extractLims(ar);
946         extractStrings(ar);
947         //lyxerr << "\nStructure to: " << ar << endl;
948 }
949
950
951 void write(MathArray const & dat, WriteStream & wi)
952 {
953         MathArray ar = dat;
954         extractStrings(ar);
955         wi.firstitem() = true;
956         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
957                 (*it)->write(wi);
958                 wi.firstitem() = false;
959         }
960 }
961
962
963 void normalize(MathArray const & ar, NormalStream & os)
964 {
965         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
966                 (*it)->normalize(os);
967 }
968
969
970 void octave(MathArray const & dat, OctaveStream & os)
971 {
972         MathArray ar = dat;
973         extractStructure(ar);
974         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
975                 (*it)->octave(os);
976 }
977
978
979 void maple(MathArray const & dat, MapleStream & os)
980 {
981         MathArray ar = dat;
982         extractStructure(ar);
983         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
984                 (*it)->maple(os);
985 }
986
987
988 void maxima(MathArray const & dat, MaximaStream & os)
989 {
990         MathArray ar = dat;
991         extractStructure(ar);
992         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
993                 (*it)->maxima(os);
994 }
995
996
997 void mathematica(MathArray const & dat, MathematicaStream & os)
998 {
999         MathArray ar = dat;
1000         extractStructure(ar);
1001         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
1002                 (*it)->mathematica(os);
1003 }
1004
1005
1006 void mathmlize(MathArray const & dat, MathStream & os)
1007 {
1008         MathArray ar = dat;
1009         extractStructure(ar);
1010         if (ar.size() == 0)
1011                 os << "<mrow/>";
1012         else if (ar.size() == 1)
1013                 os << ar.front();
1014         else {
1015                 os << MTag("mrow");
1016                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
1017                         (*it)->mathmlize(os);
1018                 os << ETag("mrow");
1019         }
1020 }
1021
1022
1023
1024
1025 namespace {
1026
1027         std::string captureOutput(std::string const & cmd, std::string const & data)
1028         {
1029                 // In order to avoid parsing problems with command interpreters
1030                 // we pass input data through a file
1031                 FileName const cas_tmpfile(tempName(FileName(), "casinput"));
1032                 if (cas_tmpfile.empty()) {
1033                         lyxerr << "Warning: cannot create temporary file."
1034                                << endl;
1035                         return std::string();
1036                 }
1037                 std::ofstream os(cas_tmpfile.toFilesystemEncoding().c_str());
1038                 os << data << endl;
1039                 os.close();
1040                 std::string command =  cmd + " < "
1041                         + quoteName(cas_tmpfile.toFilesystemEncoding());
1042                 lyxerr << "calling: " << cmd
1043                        << "\ninput: '" << data << "'" << endl;
1044                 cmd_ret const ret = runCommand(command);
1045                 unlink(cas_tmpfile);
1046                 return ret.second;
1047         }
1048
1049         size_t get_matching_brace(std::string const & str, size_t i)
1050         {
1051                 int count = 1;
1052                 size_t n = str.size();
1053                 while (i < n) {
1054                         i = str.find_first_of("{}", i+1);
1055                         if (i == npos)
1056                                 return i;
1057                         if (str[i] == '{')
1058                                 ++count;
1059                         else
1060                                 --count;
1061                         if (count == 0)
1062                                 return i;
1063                 }
1064                 return npos;
1065         }
1066
1067         size_t get_matching_brace_back(std::string const & str, size_t i)
1068         {
1069                 int count = 1;
1070                 while (i > 0) {
1071                         i = str.find_last_of("{}", i-1);
1072                         if (i == npos)
1073                                 return i;
1074                         if (str[i] == '}')
1075                                 ++count;
1076                         else
1077                                 --count;
1078                         if (count == 0)
1079                                 return i;
1080                 }
1081                 return npos;
1082         }
1083
1084         MathArray pipeThroughMaxima(docstring const &, MathArray const & ar)
1085         {
1086                 odocstringstream os;
1087                 MaximaStream ms(os);
1088                 ms << ar;
1089                 docstring expr = os.str();
1090                 docstring const header = from_ascii("simpsum:true;");
1091
1092                 std::string out;
1093                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
1094                         // try to fix missing '*' the hard way
1095                         //
1096                         // > echo "2x;" | maxima
1097                         // ...
1098                         // (C1) Incorrect syntax: x is not an infix operator
1099                         // 2x;
1100                         //  ^
1101                         //
1102                         lyxerr << "checking expr: '" << to_utf8(expr) << "'" << endl;
1103                         docstring full = header + "tex(" + expr + ");";
1104                         out = captureOutput("maxima", to_utf8(full));
1105
1106                         // leave loop if expression syntax is probably ok
1107                         if (out.find("Incorrect syntax") == npos)
1108                                 break;
1109
1110                         // search line with "Incorrect syntax"
1111                         istringstream is(out);
1112                         std::string line;
1113                         while (is) {
1114                                 getline(is, line);
1115                                 if (line.find("Incorrect syntax") != npos)
1116                                         break;
1117                         }
1118
1119                         // 2nd next line is the one with caret
1120                         getline(is, line);
1121                         getline(is, line);
1122                         size_t pos = line.find('^');
1123                         lyxerr << "found caret at pos: '" << pos << "'" << endl;
1124                         if (pos == npos || pos < 4)
1125                                 break; // caret position not found
1126                         pos -= 4; // skip the "tex(" part
1127                         if (expr[pos] == '*')
1128                                 break; // two '*' in a row are definitely bad
1129                         expr.insert(pos, from_ascii("*"));
1130                 }
1131
1132                 vector<std::string> tmp = getVectorFromString(out, "$$");
1133                 if (tmp.size() < 2)
1134                         return MathArray();
1135
1136                 out = subst(tmp[1], "\\>", std::string());
1137                 lyxerr << "output: '" << out << "'" << endl;
1138
1139                 // Ugly code that tries to make the result prettier
1140                 size_t i = out.find("\\mathchoice");
1141                 while (i != npos) {
1142                         size_t j = get_matching_brace(out, i + 12);
1143                         size_t k = get_matching_brace(out, j + 1);
1144                         k = get_matching_brace(out, k + 1);
1145                         k = get_matching_brace(out, k + 1);
1146                         std::string mid = out.substr(i + 13, j - i - 13);
1147                         if (mid.find("\\over") != npos)
1148                                 mid = '{' + mid + '}';
1149                         out = out.substr(0,i)
1150                                 + mid
1151                                 + out.substr(k + 1);
1152                         //lyxerr << "output: " << out << endl;
1153                         i = out.find("\\mathchoice", i);
1154                         break;
1155                 }
1156
1157                 i = out.find("\\over");
1158                 while (i != npos) {
1159                         size_t j = get_matching_brace_back(out, i - 1);
1160                         if (j == npos || j == 0)
1161                                 break;
1162                         size_t k = get_matching_brace(out, i + 5);
1163                         if (k == npos || k + 1 == out.size())
1164                                 break;
1165                         out = out.substr(0,j - 1)
1166                                 + "\\frac"
1167                                 + out.substr(j,i - j)
1168                                 + out.substr(i + 5,k - i - 4)
1169                                 + out.substr(k + 2);
1170                         //lyxerr << "output: " << out << endl;
1171                         i = out.find("\\over", i + 4);
1172                 }
1173                 MathArray res;
1174                 mathed_parse_cell(res, from_utf8(out));
1175                 return res;
1176         }
1177
1178
1179         MathArray pipeThroughMaple(docstring const & extra, MathArray const & ar)
1180         {
1181                 std::string header = "readlib(latex):\n";
1182
1183                 // remove the \\it for variable names
1184                 //"#`latex/csname_font` := `\\it `:"
1185                 header +=
1186                         "`latex/csname_font` := ``:\n";
1187
1188                 // export matrices in (...) instead of [...]
1189                 header +=
1190                         "`latex/latex/matrix` := "
1191                                 "subs(`[`=`(`, `]`=`)`,"
1192                                         "eval(`latex/latex/matrix`)):\n";
1193
1194                 // replace \\cdots with proper '*'
1195                 header +=
1196                         "`latex/latex/*` := "
1197                                 "subs(`\\,`=`\\cdot `,"
1198                                         "eval(`latex/latex/*`)):\n";
1199
1200                 // remove spurious \\noalign{\\medskip} in matrix output
1201                 header +=
1202                         "`latex/latex/matrix`:= "
1203                                 "subs(`\\\\\\\\\\\\noalign{\\\\medskip}` = `\\\\\\\\`,"
1204                                         "eval(`latex/latex/matrix`)):\n";
1205
1206                 //"#`latex/latex/symbol` "
1207                 //      " := subs((\\'_\\' = \\'`\\_`\\',eval(`latex/latex/symbol`)): ";
1208
1209                 std::string trailer = "quit;";
1210                 odocstringstream os;
1211                 MapleStream ms(os);
1212                 ms << ar;
1213                 std::string expr = to_utf8(os.str());
1214                 lyxerr << "ar: '" << ar << "'\n"
1215                        << "ms: '" << expr << "'" << endl;
1216
1217                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
1218                         // try to fix missing '*' the hard way by using mint
1219                         //
1220                         // ... > echo "1A;" | mint -i 1 -S -s -q
1221                         // on line     1: 1A;
1222                         //                 ^ syntax error -
1223                         //                   Probably missing an operator such as * p
1224                         //
1225                         lyxerr << "checking expr: '" << expr << "'" << endl;
1226                         string out = captureOutput("mint -i 1 -S -s -q -q", expr + ';');
1227                         if (out.empty())
1228                                 break; // expression syntax is ok
1229                         istringstream is(out);
1230                         string line;
1231                         getline(is, line);
1232                         if (line.find("on line") != 0)
1233                                 break; // error message not identified
1234                         getline(is, line);
1235                         size_t pos = line.find('^');
1236                         if (pos == string::npos || pos < 15)
1237                                 break; // caret position not found
1238                         pos -= 15; // skip the "on line ..." part
1239                         if (expr[pos] == '*' || (pos > 0 && expr[pos - 1] == '*'))
1240                                 break; // two '*' in a row are definitely bad
1241                         expr.insert(pos, 1, '*');
1242                 }
1243
1244                 // FIXME UNICODE Is utf8 encoding correct?
1245                 string full = "latex(" + to_utf8(extra) + '(' + expr + "));";
1246                 string out = captureOutput("maple -q", header + full + trailer);
1247
1248                 // change \_ into _
1249
1250                 //
1251                 MathArray res;
1252                 mathed_parse_cell(res, from_utf8(out));
1253                 return res;
1254         }
1255
1256
1257         MathArray pipeThroughOctave(docstring const &, MathArray const & ar)
1258         {
1259                 odocstringstream os;
1260                 OctaveStream vs(os);
1261                 vs << ar;
1262                 string expr = to_utf8(os.str());
1263                 string out;
1264
1265                 lyxerr << "pipe: ar: '" << ar << "'\n"
1266                        << "pipe: expr: '" << expr << "'" << endl;
1267
1268                 for (int i = 0; i < 100; ++i) { // at most 100 attempts
1269                         //
1270                         // try to fix missing '*' the hard way
1271                         // parse error:
1272                         // >>> ([[1 2 3 ];[2 3 1 ];[3 1 2 ]])([[1 2 3 ];[2 3 1 ];[3 1 2 ]])
1273                         //                                   ^
1274                         //
1275                         lyxerr << "checking expr: '" << expr << "'" << endl;
1276                         out = captureOutput("octave -q 2>&1", expr);
1277                         lyxerr << "output: '" << out << "'" << endl;
1278
1279                         // leave loop if expression syntax is probably ok
1280                         if (out.find("parse error:") == string::npos)
1281                                 break;
1282
1283                         // search line with single caret
1284                         istringstream is(out);
1285                         string line;
1286                         while (is) {
1287                                 getline(is, line);
1288                                 lyxerr << "skipping line: '" << line << "'" << endl;
1289                                 if (line.find(">>> ") != string::npos)
1290                                         break;
1291                         }
1292
1293                         // found line with error, next line is the one with caret
1294                         getline(is, line);
1295                         size_t pos = line.find('^');
1296                         lyxerr << "caret line: '" << line << "'" << endl;
1297                         lyxerr << "found caret at pos: '" << pos << "'" << endl;
1298                         if (pos == string::npos || pos < 4)
1299                                 break; // caret position not found
1300                         pos -= 4; // skip the ">>> " part
1301                         if (expr[pos] == '*')
1302                                 break; // two '*' in a row are definitely bad
1303                         expr.insert(pos, 1, '*');
1304                 }
1305
1306                 // remove 'ans = ' taking into account that there may be an
1307                 // ansi control sequence before, such as '\033[?1034hans = '
1308                 size_t i = out.find("ans = ");
1309                 if (i == string::npos)
1310                         return MathArray();
1311                 out = out.substr(i + 6);
1312
1313                 // parse output as matrix or single number
1314                 MathAtom at(new InsetMathArray(from_ascii("array"), from_utf8(out)));
1315                 InsetMathArray const * mat = at->asArrayInset();
1316                 MathArray res;
1317                 if (mat->ncols() == 1 && mat->nrows() == 1)
1318                         res.append(mat->cell(0));
1319                 else {
1320                         res.push_back(MathAtom(new InsetMathDelim(from_ascii("("), from_ascii(")"))));
1321                         res.back().nucleus()->cell(0).push_back(at);
1322                 }
1323                 return res;
1324         }
1325
1326
1327         string fromMathematicaName(string const & name)
1328         {
1329                 if (name == "Sin")    return "sin";
1330                 if (name == "Sinh")   return "sinh";
1331                 if (name == "ArcSin") return "arcsin";
1332                 if (name == "Cos")    return "cos";
1333                 if (name == "Cosh")   return "cosh";
1334                 if (name == "ArcCos") return "arccos";
1335                 if (name == "Tan")    return "tan";
1336                 if (name == "Tanh")   return "tanh";
1337                 if (name == "ArcTan") return "arctan";
1338                 if (name == "Cot")    return "cot";
1339                 if (name == "Coth")   return "coth";
1340                 if (name == "Csc")    return "csc";
1341                 if (name == "Sec")    return "sec";
1342                 if (name == "Exp")    return "exp";
1343                 if (name == "Log")    return "log";
1344                 if (name == "Arg" )   return "arg";
1345                 if (name == "Det" )   return "det";
1346                 if (name == "GCD" )   return "gcd";
1347                 if (name == "Max" )   return "max";
1348                 if (name == "Min" )   return "min";
1349                 if (name == "Erf" )   return "erf";
1350                 if (name == "Erfc" )  return "erfc";
1351                 return name;
1352         }
1353
1354
1355         void prettifyMathematicaOutput(string & out, string const & macroName,
1356                         bool roman, bool translate)
1357         {
1358                 string const macro = "\\" + macroName + "{";
1359                 size_t const len = macro.length();
1360                 size_t i = out.find(macro);
1361
1362                 while (i != npos) {
1363                         size_t const j = get_matching_brace(out, i + len);
1364                         string const name = out.substr(i + len, j - i - len);
1365                         out = out.substr(0, i)
1366                                 + (roman ? "\\mathrm{" : "")
1367                                 + (translate ? fromMathematicaName(name) : name)
1368                                 + out.substr(roman ? j : j + 1);
1369                         //lyxerr << "output: " << out << endl;
1370                         i = out.find(macro, i);
1371                 }
1372         }
1373
1374
1375         MathArray pipeThroughMathematica(docstring const &, MathArray const & ar)
1376         {
1377                 odocstringstream os;
1378                 MathematicaStream ms(os);
1379                 ms << ar;
1380                 // FIXME UNICODE Is utf8 encoding correct?
1381                 string const expr = to_utf8(os.str());
1382                 string out;
1383
1384                 lyxerr << "expr: '" << expr << "'" << endl;
1385
1386                 string const full = "TeXForm[" + expr + "]";
1387                 out = captureOutput("math", full);
1388                 lyxerr << "output: '" << out << "'" << endl;
1389
1390                 size_t pos1 = out.find("Out[1]//TeXForm= ");
1391                 size_t pos2 = out.find("In[2]:=");
1392
1393                 if (pos1 == string::npos || pos2 == string::npos)
1394                         return MathArray();
1395
1396                 // get everything from pos1+17 to pos2
1397                 out = out.substr(pos1 + 17, pos2 - pos1 - 17);
1398                 out = subst(subst(out, '\r', ' '), '\n', ' ');
1399
1400                 // tries to make the result prettier
1401                 prettifyMathematicaOutput(out, "Mfunction", true, true);
1402                 prettifyMathematicaOutput(out, "Muserfunction", true, false);
1403                 prettifyMathematicaOutput(out, "Mvariable", false, false);
1404
1405                 MathArray res;
1406                 mathed_parse_cell(res, from_utf8(out));
1407                 return res;
1408         }
1409
1410 }
1411
1412
1413 MathArray pipeThroughExtern(string const & lang, docstring const & extra,
1414         MathArray const & ar)
1415 {
1416         if (lang == "octave")
1417                 return pipeThroughOctave(extra, ar);
1418
1419         if (lang == "maxima")
1420                 return pipeThroughMaxima(extra, ar);
1421
1422         if (lang == "maple")
1423                 return pipeThroughMaple(extra, ar);
1424
1425         if (lang == "mathematica")
1426                 return pipeThroughMathematica(extra, ar);
1427
1428         // create normalized expression
1429         odocstringstream os;
1430         NormalStream ns(os);
1431         os << '[' << extra << ' ';
1432         ns << ar;
1433         os << ']';
1434         // FIXME UNICODE Is utf8 encoding correct?
1435         string data = to_utf8(os.str());
1436
1437         // search external script
1438         support::FileName const file = libFileSearch("mathed", "extern_" + lang);
1439         if (file.empty()) {
1440                 lyxerr << "converter to '" << lang << "' not found" << endl;
1441                 return MathArray();
1442         }
1443
1444         // run external sript
1445         string out = captureOutput(file.absFilename(), data);
1446         MathArray res;
1447         mathed_parse_cell(res, from_utf8(out));
1448         return res;
1449 }
1450
1451
1452 } // namespace lyx