]> git.lyx.org Git - lyx.git/blob - src/mathed/math_extern.C
fix typo that put too many include paths for most people
[lyx.git] / src / mathed / math_extern.C
1 // This file contains most of the magic that extracts "context
2 // information" from the unstructered layout-oriented stuff in an
3 // MathArray.
4
5 #include <algorithm>
6
7 #include "math_charinset.h"
8 #include "math_deliminset.h"
9 #include "math_diffinset.h"
10 #include "math_exfuncinset.h"
11 #include "math_exintinset.h"
12 #include "math_funcinset.h"
13 #include "math_fracinset.h"
14 #include "math_matrixinset.h"
15 #include "math_mathmlstream.h"
16 #include "math_scriptinset.h"
17 #include "math_stringinset.h"
18 #include "math_symbolinset.h"
19 #include "Lsstream.h"
20 #include "debug.h"
21
22
23 using std::ostream;
24 using std::istringstream;
25 using std::find_if;
26
27
28 ostream & operator<<(ostream & os, MathArray const & ar)
29 {
30         NormalStream ns(os);
31         ns << ar;
32         return os;
33 }
34
35
36 // define a function for tests
37 typedef bool TestItemFunc(MathInset *);
38
39 // define a function for replacing subexpressions
40 typedef MathInset * ReplaceArgumentFunc(const MathArray & ar);
41
42
43
44 // try to extract a super/subscript
45 // modify iterator position to point behind the thing
46 bool extractScript(MathArray & ar,
47         MathArray::iterator & pos, MathArray::iterator last)
48 {
49         // nothing to get here
50         if (pos == last)
51                 return false;
52
53         // is this a scriptinset?
54         if (!(*pos)->asScriptInset())
55                 return false;
56
57         // it is a scriptinset, use it.
58         ar.push_back(*pos);
59         ++pos;
60         return true;
61 }
62
63
64 // try to extract an "argument" to some function.
65 // returns position behind the argument
66 MathArray::iterator extractArgument(MathArray & ar,
67         MathArray::iterator pos, MathArray::iterator last, string const & = "")
68 {
69         // nothing to get here
70         if (pos == last)
71                 return pos;
72
73         // something deliminited _is_ an argument
74         if ((*pos)->asDelimInset()) {
75                 ar.push_back(*pos);
76                 return pos + 1;
77         }
78
79         // always take the first thing, no matter what it is
80         ar.push_back(*pos);
81
82         // go ahead if possible
83         ++pos;
84         if (pos == last)
85                 return pos;
86
87         // if the next item is a subscript, it most certainly belongs to the
88         // thing we have
89         extractScript(ar, pos, last);
90         if (pos == last)
91                 return pos;
92
93         // but it might be more than that.
94         // FIXME: not implemented
95         //for (MathArray::iterator it = pos + 1; it != last; ++it) {
96         //      // always take the first thing, no matter
97         //      if (it == pos) {
98         //              ar.push_back(*it);
99         //              continue;
100         //      }
101         //}
102         return pos;
103 }
104
105
106 MathScriptInset const * asScript(MathArray::const_iterator it)
107 {
108         if (it->nucleus()->asScriptInset())
109                 return 0;
110         ++it;
111         if (!it->nucleus())
112                 return 0;
113         return it->nucleus()->asScriptInset();
114 }
115
116
117
118 // returns sequence of char with same code starting at it up to end
119 // it might be less, though...
120 MathArray::const_iterator charSequence(MathArray::const_iterator it,
121         MathArray::const_iterator end, string & s, MathTextCodes & c)
122 {
123         MathCharInset const * p = (*it)->asCharInset();
124         c = p->code();
125         for (; it != end; ++it) {
126                 p = (*it)->asCharInset();
127                 if (!p || p->code() != c)
128                         break;
129                 s += p->getChar();
130         }
131         return it;
132 }
133
134
135 void extractStrings(MathArray & ar)
136 {
137         //lyxerr << "\nStrings from: " << ar << "\n";
138         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
139                 MathArray::iterator it = ar.begin() + i;
140                 if (!(*it)->asCharInset())
141                         continue;
142
143                 // create proper string inset
144                 MathStringInset * p = new MathStringInset;
145                 MathArray::const_iterator
146                         jt = charSequence(it, ar.end(), p->str_, p->code_);
147
148                 // clean up
149                 (*it).reset(p);
150                 ar.erase(i + 1, jt - ar.begin());
151         }
152         //lyxerr << "\nStrings to: " << ar << "\n";
153 }
154
155
156 MathInset * singleItem(MathArray & ar)
157 {
158         return ar.size() == 1 ? ar.begin()->nucleus() : 0;
159 }
160
161
162 void extractMatrices(MathArray & ar)
163 {
164         //lyxerr << "\nMatrices from: " << ar << "\n";
165         for (MathArray::iterator it = ar.begin(); it != ar.end(); ++it) {
166                 MathDelimInset * del = (*it)->asDelimInset();
167                 if (!del)
168                         continue;
169                 MathInset * arr = singleItem(del->cell(0));
170                 if (!arr || !arr->asArrayInset())
171                         continue;
172                 *it = MathAtom(new MathMatrixInset(*(arr->asArrayInset())));
173         }
174         //lyxerr << "\nMatrices to: " << ar << "\n";
175 }
176
177
178 // convert this inset somehow to a string
179 bool extractString(MathInset * p, string & str)
180 {
181         if (!p)
182                 return false;
183         if (p->getChar()) {
184                 str = string(1, p->getChar());
185                 return true;
186         }
187         if (p->asStringInset()) {
188                 str = p->asStringInset()->str();
189                 return true;
190         }
191         return false;
192 }
193
194
195 // convert this inset somehow to a number
196 bool extractNumber(MathArray const & ar, int & i)
197 {
198         string s;
199         MathTextCodes c;
200         charSequence(ar.begin(), ar.end(), s, c);
201         istringstream is(s.c_str());
202         is >> i;
203         return is;
204 }
205
206
207 bool extractNumber(MathArray const & ar, double & i)
208 {
209         string s;
210         MathTextCodes c;
211         charSequence(ar.begin(), ar.end(), s, c);
212         istringstream is(s.c_str());
213         is >> i;
214         return is;
215 }
216
217
218 bool testString(MathInset * p, const string & str)
219 {
220         string s;
221         return extractString(p, s) && str == s;
222 }
223
224
225 // search end of nested sequence
226 MathArray::iterator endNestSearch(
227         MathArray::iterator it,
228         MathArray::iterator last,
229         TestItemFunc testOpen,
230         TestItemFunc testClose
231 )
232 {
233         for (int level = 0; it != last; ++it) {
234                 if (testOpen(it->nucleus()))
235                         ++level;
236                 if (testClose(it->nucleus()))
237                         --level;
238                 if (level == 0)
239                         break;
240         }
241         return it;
242 }
243
244
245 // replace nested sequences by a real Insets
246 void replaceNested(
247         MathArray & ar,
248         TestItemFunc testOpen,
249         TestItemFunc testClose,
250         ReplaceArgumentFunc replaceArg
251 )
252 {
253         // use indices rather than iterators for the loop  because we are going
254         // to modify the array.
255         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
256                 // check whether this is the begin of the sequence
257                 MathArray::iterator it = ar.begin() + i;
258                 if (!testOpen(it->nucleus()))
259                         continue;
260
261                 // search end of sequence
262                 MathArray::iterator jt = endNestSearch(it, ar.end(), testOpen, testClose);
263                 if (jt == ar.end())
264                         continue;
265
266                 // create a proper inset as replacement
267                 MathInset * p = replaceArg(MathArray(it + 1, jt));
268
269                 // replace the original stuff by the new inset
270                 ar.erase(it + 1, jt + 1);
271                 (*it).reset(p);
272         }
273 }
274
275
276
277 //
278 // split scripts into seperate super- and subscript insets. sub goes in
279 // front of super...
280 //
281
282 void splitScripts(MathArray & ar)
283 {
284         //lyxerr << "\nScripts from: " << ar << "\n";
285         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
286                 MathArray::iterator it = ar.begin() + i;
287
288                 // is this script inset?
289                 MathScriptInset * p = (*it)->asScriptInset();
290                 if (!p)
291                         continue;
292
293                 // no problem if we don't have both...
294                 if (!p->hasUp() || !p->hasDown())
295                         continue;
296
297                 // create extra script inset and move superscript over
298                 MathScriptInset * q = new MathScriptInset;
299                 q->ensure(true);
300                 q->up().data_.swap(p->up().data_);
301                 p->removeScript(true);
302
303                 // insert new inset behind
304                 ++i;
305                 ar.insert(i, MathAtom(q));
306         }
307         //lyxerr << "\nScripts to: " << ar << "\n";
308 }
309
310
311 //
312 // extract exp(...)
313 //
314
315 void extractExps(MathArray & ar)
316 {
317         //lyxerr << "\nExps from: " << ar << "\n";
318
319         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
320                 MathArray::iterator it = ar.begin() + i;
321
322                 // is this 'e'?
323                 MathCharInset const * p = (*it)->asCharInset();
324                 if (!p || p->getChar() != 'e')
325                         continue;
326
327                 // we need an exponent but no subscript
328                 MathScriptInset * sup = (*(it + 1))->asScriptInset();
329                 if (!sup || sup->hasDown())
330                         continue;
331
332                 // create a proper exp-inset as replacement
333                 MathExFuncInset * func = new MathExFuncInset("exp");
334                 func->cell(0) = sup->cell(1);
335
336                 // clean up
337                 (*it).reset(func);
338                 ar.erase(it + 1);
339         }
340         //lyxerr << "\nExps to: " << ar << "\n";
341 }
342
343
344 //
345 // search deliminiters
346 //
347
348 bool testOpenParan(MathInset * p)
349 {
350         return testString(p, "(");
351 }
352
353
354 bool testCloseParan(MathInset * p)
355 {
356         return testString(p, ")");
357 }
358
359
360 MathInset * replaceDelims(const MathArray & ar)
361 {
362         MathDelimInset * del = new MathDelimInset("(", ")");
363         del->cell(0) = ar;
364         return del;
365 }
366
367
368 // replace '('...')' sequences by a real MathDelimInset
369 void extractDelims(MathArray & ar)
370 {
371         //lyxerr << "\nDelims from: " << ar << "\n";
372         replaceNested(ar, testOpenParan, testCloseParan, replaceDelims);
373         //lyxerr << "\nDelims to: " << ar << "\n";
374 }
375
376
377
378 //
379 // search well-known functions
380 //
381
382
383 // replace 'f' '(...)' and 'f' '^n' '(...)' sequences by a real MathExFuncInset
384 // assume 'extractDelims' ran before
385 void extractFunctions(MathArray & ar)
386 {
387         // we need at least two items...
388         if (ar.size() <= 1)
389                 return;
390
391         //lyxerr << "\nFunctions from: " << ar << "\n";
392         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
393                 MathArray::iterator it = ar.begin() + i;
394                 MathArray::iterator jt = it + 1;
395
396                 string name;
397                 // is it a function?
398                 if ((*it)->asFuncInset()) {
399                         // it certainly is if it is well known...
400                         name = (*it)->asFuncInset()->name();
401                 } else {
402                         // is this a user defined function?
403                         // it it probably not, if it doesn't have a name.
404                         if (!extractString((*it).nucleus(), name))
405                                 continue;
406                         // it is not if it has no argument
407                         if (jt == ar.end())
408                                 continue;
409                         // guess so, if this is followed by
410                         // a DelimInset with a single item in the cell
411                         MathDelimInset * del = (*jt)->asDelimInset();
412                         if (!del || del->cell(0).size() != 1)
413                                 continue;
414                         // fall trough into main branch
415                 }
416
417                 // do we have an exponent like in
418                 // 'sin' '^2' 'x' -> 'sin(x)' '^2'
419                 MathArray exp;
420                 extractScript(exp, jt, ar.end());
421
422                 // create a proper inset as replacement
423                 MathExFuncInset * p = new MathExFuncInset(name);
424
425                 // jt points to the "argument". Get hold of this.
426                 MathArray::iterator st = extractArgument(p->cell(0), jt, ar.end());
427
428                 // replace the function name by a real function inset
429                 (*it).reset(p);
430
431                 // remove the source of the argument from the array
432                 ar.erase(it + 1, st);
433
434                 // re-insert exponent
435                 ar.insert(i + 1, exp);
436                 //lyxerr << "\nFunctions to: " << ar << "\n";
437         }
438 }
439
440
441 //
442 // search integrals
443 //
444
445 bool testSymbol(MathInset * p, string const & name)
446 {
447         return p->asSymbolInset() && p->asSymbolInset()->name() == name;
448 }
449
450
451 bool testIntSymbol(MathInset * p)
452 {
453         return testSymbol(p, "int");
454 }
455
456
457 bool testIntDiff(MathInset * p)
458 {
459         return testString(p, "d");
460 }
461
462
463 // replace '\int' ['_^'] x 'd''x'(...)' sequences by a real MathExIntInset
464 // assume 'extractDelims' ran before
465 void extractIntegrals(MathArray & ar)
466 {
467         // we need at least three items...
468         if (ar.size() <= 2)
469                 return;
470
471         //lyxerr << "\nIntegrals from: " << ar << "\n";
472         for (MathArray::size_type i = 0; i + 1 < ar.size(); ++i) {
473                 MathArray::iterator it = ar.begin() + i;
474
475                 // is this a integral name?
476                 if (!testIntSymbol(it->nucleus()))
477                         continue;
478
479                 // search 'd'
480                 MathArray::iterator jt =
481                         endNestSearch(it, ar.end(), testIntSymbol, testIntDiff);
482
483                 // something sensible found?
484                 if (jt == ar.end())
485                         continue;
486
487                 // create a proper inset as replacement
488                 MathExIntInset * p = new MathExIntInset("int");
489
490                 // collect subscript if any
491                 MathArray::iterator st = it + 1;
492                 if (st != ar.end())
493                         if (MathScriptInset * sub = (*st)->asScriptInset())
494                                 if (sub->hasDown()) {
495                                         p->cell(2) = sub->down().data_;
496                                         ++st;
497                                 }
498
499                 // collect superscript if any
500                 if (st != ar.end())
501                         if (MathScriptInset * sup = (*st)->asScriptInset())
502                                 if (sup->hasUp()) {
503                                         p->cell(3) = sup->up().data_;
504                                         ++st;
505                                 }
506
507                 // core ist part from behind the scripts to the 'd'
508                 p->cell(0) = MathArray(st, jt);
509
510                 // use the "thing" behind the 'd' as differential
511                 MathArray::iterator tt = extractArgument(p->cell(1), jt + 1, ar.end());
512
513                 // remove used parts
514                 ar.erase(it + 1, tt);
515                 (*it).reset(p);
516         }
517         //lyxerr << "\nIntegrals to: " << ar << "\n";
518 }
519
520
521 //
522 // search sums
523 //
524
525 bool testSumSymbol(MathInset * p)
526 {
527         return testSymbol(p, "sum");
528 }
529
530
531 bool testEqualSign(MathAtom const & at)
532 {
533         return testString(at.nucleus(), "=");
534 }
535
536
537
538 // replace '\sum' ['_^'] f(x) sequences by a real MathExIntInset
539 // assume 'extractDelims' ran before
540 void extractSums(MathArray & ar)
541 {
542         // we need at least two items...
543         if (ar.size() <= 1)
544                 return;
545
546         //lyxerr << "\nSums from: " << ar << "\n";
547         for (MathArray::size_type i = 0; i + 1< ar.size(); ++i) {
548                 MathArray::iterator it = ar.begin() + i;
549
550                 // is this a sum name?
551                 if (!testSumSymbol(it->nucleus()))
552                         continue;
553
554                 // create a proper inset as replacement
555                 MathExIntInset * p = new MathExIntInset("sum");
556
557                 // collect lower bound and summation index
558                 MathArray::iterator st = it + 1;
559                 if (st != ar.end())
560                         if (MathScriptInset * sub = (*st)->asScriptInset())
561                                 if (sub->hasDown()) {
562                                         // try to figure out the summation index from the subscript
563                                         MathArray & ar = sub->down().data_;
564                                         MathArray::iterator it =
565                                                 find_if(ar.begin(), ar.end(), &testEqualSign);
566                                         if (it != ar.end()) {
567                                                 // we found a '=', use everything in front of that as index,
568                                                 // and everything behind as lower index
569                                                 p->cell(1) = MathArray(ar.begin(), it);
570                                                 p->cell(2) = MathArray(it + 1, ar.end());
571                                         } else {
572                                                 // use everything as summation index, don't use scripts.
573                                                 p->cell(1) = ar;
574                                         }
575                                         ++st;
576                                 }
577
578                 // collect upper bound
579                 if (st != ar.end())
580                         if (MathScriptInset * sup = (*st)->asScriptInset())
581                                 if (sup->hasUp()) {
582                                         p->cell(3) = sup->up().data_;
583                                         ++st;
584                                 }
585
586                 // use some  behind the script as core
587                 MathArray::iterator tt = extractArgument(p->cell(0), st, ar.end());
588
589                 // cleanup
590                 ar.erase(it + 1, tt);
591                 (*it).reset(p);
592         }
593         //lyxerr << "\nSums to: " << ar << "\n";
594 }
595
596
597 //
598 // search differential stuff
599 //
600
601 // tests for 'd' or '\partial'
602 bool testDiffItem(MathAtom const & at)
603 {
604         return testString(at.nucleus(), "d");
605 }
606
607
608 bool testDiffArray(MathArray const & ar)
609 {
610         return ar.size() && testDiffItem(ar.front());
611 }
612
613
614 bool testDiffFrac(MathInset * p)
615 {
616         MathFracInset * f = p->asFracInset();
617         return f && testDiffArray(f->cell(0)) && testDiffArray(f->cell(1));
618 }
619
620
621 // is this something like ^number?
622 bool extractDiffExponent(MathArray::iterator it, int & i)
623 {
624         if (!(*it)->asScriptInset())
625                 return false;
626
627         string s;
628         if (!extractString((*it).nucleus(), s))
629                 return false;
630         istringstream is(s.c_str());
631         is >> i;
632         return is;
633 }
634
635
636 void extractDiff(MathArray & ar)
637 {
638         //lyxerr << "\nDiffs from: " << ar << "\n";
639         for (MathArray::size_type i = 0; i < ar.size(); ++i) {
640                 MathArray::iterator it = ar.begin() + i;
641
642                 // is this a "differential fraction"?
643                 if (!testDiffFrac(it->nucleus()))
644                         continue;
645
646                 MathFracInset * f = (*it)->asFracInset();
647                 if (!f) {
648                         lyxerr << "should not happen\n";
649                         continue;
650                 }
651
652                 // create a proper diff inset
653                 MathDiffInset * diff = new MathDiffInset;
654
655                 // collect function, let jt point behind last used item
656                 MathArray::iterator jt = it + 1;
657                 //int n = 1;
658                 MathArray & numer = f->cell(0);
659                 if (numer.size() > 1 && numer.at(1)->asScriptInset()) {
660                         // this is something like  d^n f(x) / d... or  d^n / d...
661                         // FIXME
662                         //n = 1;
663                         if (numer.size() > 2)
664                                 diff->cell(0) = MathArray(numer.begin() + 2, numer.end());
665                         else
666                                 jt = extractArgument(diff->cell(0), jt, ar.end());
667                 } else {
668                         // simply d f(x) / d... or  d/d...
669                         if (numer.size() > 1)
670                                 diff->cell(0) = MathArray(numer.begin() + 1, numer.end());
671                         else
672                                 jt = extractArgument(diff->cell(0), jt, ar.end());
673                 }
674
675                 // collect denominator parts
676                 MathArray & denom = f->cell(1);
677                 for (MathArray::iterator dt = denom.begin(); dt != denom.end();) {
678                         // find the next 'd'
679                         MathArray::iterator et = find_if(dt + 1, denom.end(), &testDiffItem);
680
681                         // point before this
682                         MathArray::iterator st = et - 1;
683                         MathScriptInset * script = (*st)->asScriptInset();
684                         if (script && script->hasUp()) {
685                                 // things like   d.../dx^n
686                                 int mult = 1;
687                                 if (extractNumber(script->up().data_, mult)) {
688                                         //lyxerr << "mult: " << mult << endl;
689                                         for (int i = 0; i < mult; ++i)
690                                                 diff->addDer(MathArray(dt + 1, st));
691                                 }
692                         } else {
693                                 // just  d.../dx
694                                 diff->addDer(MathArray(dt + 1, et));
695                         }
696                         dt = et;
697                 }
698
699                 // cleanup
700                 ar.erase(it + 1, jt);
701                 (*it).reset(diff);
702         }
703         //lyxerr << "\nDiffs to: " << ar << "\n";
704 }
705
706
707
708 //
709 // combine searches
710 //
711
712 void extractStructure(MathArray & ar)
713 {
714         splitScripts(ar);
715         extractMatrices(ar);
716         extractDelims(ar);
717         extractFunctions(ar);
718         extractIntegrals(ar);
719         extractSums(ar);
720         extractDiff(ar);
721         extractExps(ar);
722         extractStrings(ar);
723 }
724
725
726 void write(MathArray const & dat, WriteStream & wi)
727 {
728         MathArray ar = dat;
729         extractStrings(ar);
730         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
731                 wi.firstitem() = (it == ar.begin());
732                 MathInset const * p = it->nucleus();
733                 if (it + 1 != ar.end()) {
734                         if (MathScriptInset const * q = asScript(it)) {
735                                 q->write2(p, wi);
736                                 ++it;
737                                 continue;
738                         }
739                 }
740                 p->write(wi);
741         }
742 }
743
744
745 void normalize(MathArray const & ar, NormalStream & os)
746 {
747         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it)
748                 (*it)->normalize(os);
749 }
750
751
752 void octavize(MathArray const & dat, OctaveStream & os)
753 {
754         MathArray ar = dat;
755         extractStructure(ar);
756         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
757                 MathInset const * p = it->nucleus();
758                 if (it + 1 != ar.end()) {
759                         if (MathScriptInset const * q = asScript(it)) {
760                                 q->octavize2(p, os);
761                                 ++it;
762                                 continue;
763                         }
764                 }
765                 p->octavize(os);
766         }
767 }
768
769
770 void maplize(MathArray const & dat, MapleStream & os)
771 {
772         MathArray ar = dat;
773         extractStructure(ar);
774         for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
775                 MathInset const * p = it->nucleus();
776                 if (it + 1 != ar.end()) {
777                         if (MathScriptInset const * q = asScript(it)) {
778                                 q->maplize2(p, os);
779                                 ++it;
780                                 continue;
781                         }
782                 }
783                 p->maplize(os);
784         }
785 }
786
787
788 void mathmlize(MathArray const & dat, MathMLStream & os)
789 {
790         MathArray ar = dat;
791         extractStructure(ar);
792         if (ar.size() == 0)
793                 os << "<mrow/>";
794         else if (ar.size() == 1)
795                 os << ar.begin()->nucleus();
796         else {
797                 os << MTag("mrow");
798                 for (MathArray::const_iterator it = ar.begin(); it != ar.end(); ++it) {
799                         MathInset const * p = it->nucleus();
800                         if (it + 1 != ar.end()) {
801                                 if (MathScriptInset const * q = asScript(it)) {
802                                         q->mathmlize2(p, os);
803                                         ++it;
804                                         continue;
805                                 }
806                         }
807                         p->mathmlize(os);
808                 }
809                 os << ETag("mrow");
810         }
811 }