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