X-Git-Url: http://matita.cs.unibo.it/gitweb/?a=blobdiff_plain;ds=sidebyside;f=helm%2FDEVEL%2Fmathml_editor%2Fsrc%2FTPushParser.cc;h=906e8f414497e54254bd6ebc3dd4b0a9a596d05f;hb=387aeebf96181c051b7f527a0901b173cfcdf194;hp=325b9aa3c129f9a9bd31500aeec01ed6850184f1;hpb=7bea7bddf7ca13260f4d3965182dc4fb58d035e7;p=helm.git diff --git a/helm/DEVEL/mathml_editor/src/TPushParser.cc b/helm/DEVEL/mathml_editor/src/TPushParser.cc index 325b9aa3c..906e8f414 100644 --- a/helm/DEVEL/mathml_editor/src/TPushParser.cc +++ b/helm/DEVEL/mathml_editor/src/TPushParser.cc @@ -5,17 +5,22 @@ TPushParser::TPushParser(ALogger& l, const TDictionary& d) : APushParser(l), dictionary(d) { - init(); + reset(); } TPushParser::TPushParser(ALogger& l, AMathMLFactory& f, const TDictionary& d) : APushParser(l, f), dictionary(d) { - init(); + reset(); +} + +TPushParser::~TPushParser() +{ } void -TPushParser::init() +TPushParser::reset() { + APushParser::reset(); nextId = 1; cursor = doc.create("cursor"); cursor["id"] = "I0"; @@ -23,10 +28,6 @@ TPushParser::init() doc.root().append(cursor); } -TPushParser::~TPushParser() -{ -} - std::string TPushParser::PRIME() const { @@ -75,22 +76,23 @@ TPushParser::do_end() assert(row && row.is("row")); TNode table = row.parent(); assert(table); - advance(table); - - // now we have to pop the associated frame in the stack - frames.pop(); + advance(row); } - else if (parent && parent.isG() && !parent.hasId() && parent.parent()) + else if (parent && parent.isG() && !parent.hasId() && parent.parent() && !parent.parent().is("math")) { // closing brace for a right-open macro (like \over) + // It's not sure that we want to exit from a phantom group with a + // '}', because, to enter in the phantom group the user didn't insert a + // '{'. cursor.remove(); - advance(parent.parent()); - frames.pop(); + advance(parent); } else { - // ??? - assert(0); + // In this case, there is a redundant '}', so I think we can ignore it and + // emit an error + logger.warning("There is so no corresponding'{'"); + //assert(0); } } @@ -121,6 +123,7 @@ TPushParser::do_shift() else { // we need two closing math shifts + //cursor.remove(); ?? parent.parent().append(cursor); } } @@ -141,7 +144,13 @@ TPushParser::do_shift() } else { - cerr << "ERROR: math shift" << endl; + // In TeX, the user can type + // $a-b\over2a+b$ + // In this case, '$' character is correct. + // I don't think so, if user types + // $12+{a-b\over2a+b$ + // because the group is not closed with a '}' + logger.error("parser: math shift"); } } @@ -175,7 +184,7 @@ TPushParser::do_align() } else { - cerr << "alignment tab used outside matrix" << endl; + logger.error("alignment tab used outside matrix"); } } @@ -216,7 +225,7 @@ TPushParser::do_subscript() } else if (parent.isSb() && cursor == parent[1]) { - if (parent["under"] == "1") cerr << "already under" << endl; + if (parent["under"] == "1") logger.error("already under"); else parent["under"] = "1"; } } @@ -246,7 +255,7 @@ TPushParser::do_superscript() } else if (parent.isSp() && cursor == parent[1]) { - if (parent["over"] == "1") cerr << "already over" << endl; + if (parent["over"] == "1") logger.error("already over"); else parent["over"] = "1"; } } @@ -316,7 +325,8 @@ TPushParser::do_apostrophe() } else { - // error ??? + // is it an error? + logger.error("parser: you have to type one identifier before the ''"); } } else @@ -334,8 +344,8 @@ TPushParser::do_other(const std::string& s) do_apostrophe(); break; default: - cout << "TPushParser::do_other " << s << endl; - cout << "DOCUMENT: " << static_cast(cursor.element().get_ownerDocument()) << endl; + /*cout << "TPushParser::do_other " << s << endl; + cout << "DOCUMENT: " << static_cast(cursor.element().get_ownerDocument()) << endl;*/ TNode elem = doc.createT("o", s, nextId++); cursor.replace(elem); advance(elem); @@ -417,10 +427,6 @@ TPushParser::do_control(const std::string& name) TNode m = doc.createC(name, nextId++); cursor.replace(m); - cout << "ecco tutti i token del pattern della entry inserita" << endl; - for (unsigned i = 0; i < entry.pattern.size(); i++) - cout << entry.pattern[i].value << endl; - if (entry.leftOpen && entry.rightOpen) { assert(entry.pattern.empty()); @@ -458,12 +464,8 @@ TPushParser::do_control(const std::string& name) { frames.push(Frame(entry)); - if (entry.table) - cout << "do_control: we have a table" << endl; - if (entry.paramDelimited(0)) { - cout << "do_control: it'a MACRO with delimited first argument" << endl; TNode g = doc.createG(); m.append(g); g.append(cursor); @@ -474,7 +476,7 @@ TPushParser::do_control(const std::string& name) else { // error, but we could handle this very easily - cerr << "error, but we could handle this easily" << endl; + logger.error(" parser:but we could handle this easily"); } } else advance(m); @@ -482,7 +484,7 @@ TPushParser::do_control(const std::string& name) break; case TDictionary::UNDEFINED: { - cerr << "ERROR: using undefined macro `" << name << "'" << endl; + logger.error("parser: using undefined macro " + name); TNode m = doc.createC(name, nextId++); cursor.replace(m); advance(m); @@ -495,156 +497,242 @@ TPushParser::do_control(const std::string& name) } void -TPushParser::gdelete_prev() +TPushParser::gdelete_prev_token() { - // if in this function, the prev of cursor does exist, also the parent and we want a graphical deleting. - assert(cursor.prev()); assert(cursor.parent()); - TNode prev = cursor.prev(); - if (prev.is("i") || prev.is("o") || prev.is("n")) + assert(prev.is("i") || prev.is("o") || prev.is("n")); + + // the control below is designed to handle the case in which val have more than one unicode character + DOM::UCS4String ucs4val(prev.element().getAttribute("val")); + if ((ucs4val.length() <= 1) || prev.element().hasAttribute("name")) { - // the control below is designed to handle the case in which val have more than one unicode character - DOM::UCS4String ucs4val(prev.element().getAttribute("val")); - if ((ucs4val.length() <= 1) || prev.element().hasAttribute("name")) + cursor.remove(); + prev.replace(cursor); + + if (cursor.parent().isC()) { - cursor.remove(); - prev.replace(cursor); - - if (cursor.parent().isC()) - { - // in this case we have removed an element of a MACRO. - // we can assert that this element was a non delimited argument - assert(!frames.empty()); - Frame& frame = frames.top(); - assert(frame.pos > 0); + // in this case we have removed an element of a MACRO. + // we can assert that this element was a non delimited argument + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.pos > 0); - frame.pos--; - } - } - else - { - ucs4val.erase(ucs4val.length() - 1, 1); - prev.element().setAttribute(DOM::GdomeString("val"), DOM::GdomeString(ucs4val)); + frame.pos--; } - } // end of if (prev.is("i") || ...) - else if (prev.is("sp") || prev.is("sb")) + } + else { - cursor.remove(); - prev.append(cursor); - gdelete_prev(); - } // end of if (prev.is("sp") || prev.is("sb")) - else if (prev.isG()) + ucs4val.erase(ucs4val.length() - 1, 1); + prev.element().setAttribute(DOM::GdomeString("val"), DOM::GdomeString(ucs4val)); + } + +} + +void +TPushParser::gdelete_prev_script() +{ + // this method delete an sp or an sb preceding the cursor + assert(cursor.prev()); + assert(cursor.parent()); + TNode prev = cursor.prev(); + assert(prev.is("sp") || prev.is("sb")); + cursor.remove(); + prev.append(cursor); + // i can invoke the gdelet_prev, because a sp (sb) MUST have two children + gdelete_prev(); +} + +void +TPushParser::gdelete_prev_group() +{ + assert(cursor.prev() && cursor.prev().isG()); + TNode prev = cursor.prev(); + cursor.remove(); + prev.append(cursor); + + // a group may have no children, so the gdelete_prev is not appropriate + // so this method is not equivalent to the one above + do_gdelete(); +} + +void +TPushParser::gdelete_prev_macro() +{ + assert(cursor.parent()); + assert(cursor.prev()); + TNode prev = cursor.prev(); + assert(prev.isC()); + + const TDictionary::Entry& entry = dictionary.find(prev["name"]); + + if (!entry.defined()) { + // We can assume tha the user want to completely delete the undefined macro cursor.remove(); - prev.append(cursor); - do_gdelete(); + prev.replace(cursor); } - else if (prev.isC() && dictionary.find(prev["name"]).cls != TDictionary::UNDEFINED) - { - const TDictionary::Entry& entry = dictionary.find(prev["name"]); - - // i start to remove a MACRO. The frame associated to this MACRO was - // popped from the stack (i think). So, i re-push the frame in the stack, - // but the pos member should be set correctly - cout << "gdelete_prev: i have to start to delete a MACRO" << endl; + else + { + // we start to remove a MACRO. Different actions must be taken, based on the nature + // of the MACRO. In some cases, we can't remove the MACRO immediately, in other + // cases it's correct. In the first set of cases, we have to update the stack, pushing + // a frame in it with a correct value of pos, in the + // second one, we must not push a frame in the stack - // if the control element is leftOpen and rightOpen, the cursor should be placed after - // the last child of the control element's last child if (entry.rightOpen) { - cout << "gdelte_prev(): i have to delete a control rightOpen, so i push an element in the stack" << endl; - Frame frame(entry); - frames.push(frame); + // In this fragment of code we also handle the leftOpen && rightOpen MACRO. + // if the control element is rightOpen, the cursor should be placed after + // the last child of the control element's last child, and than, we try to remove something. + // A frame MUST be pushed in the stack, because we dont' know if the following actions + // will completely remove the MACRO. + frames.push(Frame(entry)); + + // Since the MACRO is rightOpen, the last child of the MACRO must be a phantom group + assert(prev.last().isG() && !prev.last().hasId()); cursor.remove(); prev.last().append(cursor); - do_gdelete(); + + // the gdelete_prev is not appropriate, because the last child of the MACRO may have no children + do_gdelete_phantom_group(); } else if (entry.leftOpen) { - cout << "gdelete_prev: i have to delete a control element with leftOpen" << endl; + // the leftOpen MACRO MUST have one and only one child, which MUST be a phantom group + // In this case, we do not have to push a frame in the stack, because we remove the + // MACRO immediately, substituting it with the content of the phantom group. + // At the moment, we don't remove the last child of the phantom group, but + // i don't know if it's the correct behavior of the graphical deleting. + // To delete it, just remove the comment of the last instruction of this block + // of code. assert(prev.first()); assert(prev.first().isG()); assert(prev.first() == prev.last()); + TNode g = prev.first(); g.remove(); prev.replace(g.first(), TNode()); + //do_gdelete(); } else if (!entry.pattern.empty()) - { - // we have to start removing a MACRO which accepts arguments. - // a MACRO without child does not exist - - cout << "gdelete_prev: i have to remove a MACRO with argument" << endl; - + { + // we have to start to remove a MACRO which accepts arguments. + // If the MACRO accepts arguments, the MACRO has one or more children assert(prev.size() >= 1); + // Differnt actions must be taken, based on the nature of the last child + // of the MACRO. We have to distinguish the case in which it's a delimited argument + // frome the one in which it's a not delimited argument. if (prev.last().isG() && !prev.last().hasId()) { - // this means that the last child of the MACRO is a phantom group, - // which in turn means that it is a delimited argument - // so we have to ideally remove this delimiter + // the last argument of the MACRO is a delimited argumet. We ideally remove + // the sequence of delimiters cursor.remove(); prev.last().append(cursor); - // i have to push a frame with a correct value of pos - Frame frame(entry); + // we have to push a frame with a correct value of pos assert(entry.previousParam(entry.pattern.size()) != entry.pattern.size()); + unsigned sequence_length = entry.pattern.size() - entry.previousParam(entry.pattern.size()) - 1; - frame.pos = entry.pattern.size() - sequence_length - 1; - frames.push(frame); - cout << "gdelete_prev: i have inserted a frame, it's pos is: " << frames.top().pos << endl; + unsigned p = entry.pattern.size() - sequence_length - 1; + // now, p is the correct value of pos, and we can push the frame. + frames.push(Frame(entry, p)); } else { - // in this case, the last child of the MACRO is - // an argument which is NOT delimited, so i try to - // remove it - cout << "gdelete_prev: i try to remove the last argumet of the MACRO" << endl; + // in this case, the last child of the MACRO is not a delimited argument, so we try + // to remove it, but we have to take differnt actions if the MACRO is a table with rows or not. cursor.remove(); if (entry.table == 1 && prev.last().is("row")) { - // in this case should be appended to the group associated to + // in this case the cursor should be appended to the group associated to // the last cell of the last row of the table assert(prev.last().last().is("cell") && prev.last().last().first().isG()); prev.last().last().first().append(cursor); } - else prev.append(cursor); + else + { + prev.append(cursor); + } - Frame frame(entry); - frame.pos = entry.pattern.size(); - frames.push(frame); - - if (cursor.prev()) gdelete_prev(); - else do_gdelete(); + // we push a frame in the stack with a correct value of member pos. + // This correct value is the size of the pattern, because we have to START to delete + // a MACRO. It means that all of the MACRO's arguments have been inserted. + frames.push(Frame(entry, entry.pattern.size())); - } + if (cursor.prev()) + { + // in this case we try to remove the last child of the MACRO. + gdelete_prev(); + } + else + { + // this block of code will never be executed, because the MACRO accepts arguments, + // so a MACRO's child MUST exist. But, we can handle this case, emitting a warning + logger.warning("Parser: the TML tree is in a strange state, but a good state will be generated"); + do_gdelete(); + } + } // end of the else of the if (prev.last().isG() && !prev.last().hasId()) + + } // end of if (!entry.pattern.empty()) + else + { + // if we are here, the MACRO preceding the cursor, is !(rightOpen ||leftOpen), + // and has no pattern. It means that it has no children. + // We can replace it with the cursor + assert(prev.size() == 0); + cursor.remove(); + prev.replace(cursor); } + } // end of defined MACRO + +} + +void +TPushParser::gdelete_prev() +{ + // if in this function, the prev of cursor does exist, also the parent and we want a graphical deleting. + + assert(cursor.prev()); + assert(cursor.parent()); + + TNode prev = cursor.prev(); + + if (prev.is("i") || prev.is("o") || prev.is("n")) + { + gdelete_prev_token(); } - else if (dictionary.find(prev["name"]).cls == TDictionary::UNDEFINED) + else if (prev.isSp() || prev.isSb()) { - // The user wants to delete an undefined element. - // I suppose that he (or she) wants to delete it all - cursor.remove(); - prev.replace(cursor); + gdelete_prev_script(); + } + else if (prev.isG()) + { + gdelete_prev_group(); + } + else if (prev.isC()) + { + // here, we also treat the case in which the MACRO is a table + gdelete_prev_macro(); } else { - // not handled + // not handled. Future cases... } - + } // end of method void -TPushParser::rgreplace_futher(void) +TPushParser::rgreplace_father(void) { - // this function MUST only be invoked, when the cursor + // this method MUST only be invoked, when the cursor // is the only child of a group with id. This function - // replace the group with the cursor. But if the new parent + // replaces the group with the cursor. But if the new parent // is a group with id and the cursor is the only child of the // group, the new parent is replaced...and so on. - // r stands for recursive, g stands for graphical + // r stands for recursive, g stands for graphical. assert(cursor.parent()); assert(cursor.parent().isG() && cursor.parent().hasId()); @@ -659,6 +747,7 @@ TPushParser::rgreplace_futher(void) if (parent.isC()) { // in this case we have removed a MACRO's child. + // So, we have to update the member pos of the frame in the stack // I can assert that this MACRO accepts arguments. assert(!frames.empty()); Frame& frame = frames.top(); @@ -668,340 +757,461 @@ TPushParser::rgreplace_futher(void) } void -TPushParser::do_gdelete() +TPushParser::do_gdelete_script() { - // this function operates a graphical deleting + // If we are here, the cursor is child of a script (sp or sb) and + // this means that a prev MUST exist and that there is one and only one + // element preceding the cursor. The sp's (or sb's) parent + // MUST NOT be a MACRO. + // The element preceding the cursor is the base of the script. + + assert(cursor.parent() && (cursor.parent().isSp() || cursor.parent().isSb())); + TNode parent = cursor.parent(); + + assert(parent.size() == 2); + assert(parent.parent() && !parent.parent().isC()); - //if (!frames.empty()) - // cout << "do_gdelete: c'e' un frame aperto e il suo pos vale: " << frames.top().pos << endl; + TNode prev = cursor.prev(); + cursor.remove(); + if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0)) + { + // in this case, the script's base is a group with no elements, so + // we have to remove the entire MACRO, replacing it with the cursor. + // This situation occurs when the user type something like this + // $....{}^ + // or this + // $^ + // or this + // $...{^ + prev.remove(); + parent.replace(cursor); + + // if the new parent is a group with Id and the cursor is the only + // element of this group, we have to remove the group. This controls are made + // in the method rgreplace_father(). + if (cursor.parent().isG() && cursor.parent().hasId()) rgreplace_father(); + } + else + { + // in this case, the prev has to replace the script, + // and the cursor has to be placed after the prev. + assert(prev.hasId()); + parent.replace(prev); + prev.parent().append(cursor); + // now prev have a preceding element + assert(cursor.parent().size() > 1); + } +} // end of method do_gdelet_script + +void +TPushParser::do_gdelete_macro() +{ + // If we are here, the cursor is a child of a MACRO and this means + // that there is an open frame for the control element + // and this element is closed at either side (no leftOpen no rightOpen) + // and the MACRO is waiting for a not delimited argument, so + // we can assert that frame.entry.pattern.size() >= 1 + assert(cursor.parent() && cursor.parent().isC()); TNode parent = cursor.parent(); + + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.entry.pattern.size() >= 1); - // if no parent, do nothing - if (parent) + // we have to take different actions, based on if a preceding element exists + // or not + TNode prev = cursor.prev(); + if (!prev) { - assert(parent); - if (parent.isG()) - { - TNode prev = cursor.prev(); - if (prev) - { - // i try to delete the preceding element - gdelete_prev(); - - if ((parent.first() == cursor) && parent.isG() && parent.hasId()) - rgreplace_futher(); - - } - else // no previous node is present - { - // if here, we are in a gruop whose only child is the cursor. - - if (!parent.hasId()) - { - // the parent is a phantom group - assert(parent.parent()); - if (!parent.parent().is("math")) - { - TNode gfuther = parent.parent(); - - // if the grand futher is a group with Id, it should be removed, - // but i don't know if it will never occur... - if (gfuther.isG() && gfuther.hasId()) - { - cursor.remove(); - parent.replace(cursor); - - // re-start the process - do_gdelete(); - } - else if (gfuther.isC()) - { - // the grand futher is a control element: since the parent is a phantom group, - // the TML tree should be in a inconsistent state (once removed the parent). - - // being here means that there is a frame in the stack - assert(!frames.empty()); - cout << "do_gdelete: i have to remove a phantom group whuch is a child of a MACRO" << endl; - Frame& frame = frames.top(); - if (frame.entry.leftOpen && frame.entry.rightOpen) - { - // in this case, the cursor is in the second and last child - // of the MACRO. We can assert that the grand futher has two - // children. which are both phantom group - cout << "do_gdelete: the MACRO is leftOpen and rigthOpen" << endl; - assert(gfuther.size() == 2); - assert((gfuther.last() == parent) && (gfuther.first().isG() && !gfuther.first().hasId())); - assert(frame.pos == 0); - - TNode ggfuther = gfuther.parent(); - assert(ggfuther); - cursor.remove(); - parent.remove(); - // i have to replace the gfuther with the elements of its first child - gfuther.replace(gfuther.first().first(), TNode()); - cout << "do_gdelete: i have removed the control element, and replaced it with its first child" << endl; - ggfuther.append(cursor); - cout << "do_gdelete: cursor appended to the grand grand futher" << endl; - - // now we have the situation preceding the insertion of the MACRO leftOpen and rightOpen - // this MACRO no longer exists. - frames.pop(); - } - else if (frame.entry.rightOpen) - { - // the user has inserted a MACRO rightOpen. Since the cursor is the - // only child of the MACRO, the user want to remove it. - // We can assert that cursor's parent is the only child of the MACRO - cout << "do_gdelete: the MACRO is rightOpen only" << endl; - assert(gfuther.size() == 1); - assert(frame.pos == 0); // i think this assert has no sense - - cursor.remove(); - parent.remove(); - gfuther.replace(cursor); - - // now we have the situation preceding the MACRO rightOpen, so i have to pop the frame - frames.pop(); - } - else if (frame.entry.leftOpen) - { - // it' s an unpredicted situation - cout << "it's a bad situation, maybe handlable, but unpredicted" << endl; - } - else if (!frame.entry.pattern.empty()) - { - // the MACRO (the cursor's grand futher) accepts arguments. - // we have to control if the cursor's uncle does exist. - - if (parent.prev()) - { - // in this case, we can assert that frame in the stack has - // pos greater than 0 - assert(frame.pos > 0); - - // cursor's uncle does exist. we have to control - // its nature (is it a phantom group?) - TNode uncle = parent.prev(); - if (uncle.isG() && !uncle.hasId()) - { - // the cursor's uncle is a phantom group, so it was a - // delimited argument of the MACRO and the corrisponding sequence of - // delimeters is inserted. So, the action of deleting means - // removing this sequence - assert(frame.pos > 1); - unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1; - assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size()); - assert(sequence_length); - // sequence_length is the length of the delimiters sequence which separates - // the current parameter and the previous parameter - frame.pos = frame.pos - sequence_length - 1; - cursor.remove(); - parent.remove(); - uncle.append(cursor); - } - else - { - // the uncle was a NOT delimited argument. So i try to - // remove it - cursor.remove(); - parent.replace(cursor); - - parent = cursor.parent(); // i update the parent (it should be the MACRO) - assert(parent.isC()); - - gdelete_prev(); - - } - } - else - { - // cursor's parent is the only child of the MACRO, which accepts arguments - // i can assert that frame.pos == 0. - // In this case i can replace the MACRO with the cursor - assert(frame.pos == 0); - - cursor.remove(); - parent.remove(); - gfuther.replace(cursor); - - frames.pop(); - } - - } - } - else if (gfuther.is("cell")) - { - // being here means that there is a frame in the stack - // associated to the "table" - cout << "do_gdelete: i have to delete a cell" << endl; - assert(!frames.empty()); - assert(frames.top().pos == 1); - assert(frames.top().entry.table == 1); - // a cell MUST be child of row element, which in turn MUST be child of an element - // havin attribute table. - assert(gfuther.parent() && gfuther.parent().is("row") && gfuther.parent().parent()); - TNode row = gfuther.parent(); - - // in this case the cell has no element, so the user wants to delete this cell. - TNode prev_cell = gfuther.prev(); - - cursor.remove(); - parent.remove(); - gfuther.remove(); - // now the cell no longer exists - - if (!prev_cell) - { - // i this case, the cell is the only cell in the row. - // So, i assume that the user wants to delete the entire row. - TNode table = row.parent(); - TNode prev_row = row.prev(); - row.remove(); - if (!prev_row) - { - // the row was the only child of the table. - // I think have to delete the entire table - assert(table.parent()); - TNode parent_table = table.parent(); - table.remove(); - frames.pop(); - parent_table.append(cursor); - } - else - { - // there are other rows (one at least) - assert(prev_row.is("row")); - assert(prev_row.last()); - TNode last_cell = prev_row.last(); - assert(last_cell.is("cell")); - assert(last_cell.size() == 1); - assert(last_cell.first().isG() && !last_cell.first().hasId()); - last_cell.first().append(cursor); - } - } - else - { - // being here means that there is a previous cell, - // so we append the cursor to group. - assert(prev_cell.size() == 1); - assert(prev_cell.first().isG() && !prev_cell.first().hasId()); - prev_cell.first().append(cursor); - } - } - } - else // the grand futher is math - { - // nothing to do...i think - assert(frames.empty()); - } - } - else - { - // the parent is a group with id and has no elements other than cursor - // so we replace it with the cursor. - rgreplace_futher(); + // in this case, a prev does not exist, so the actions of deleting means + // that we have to remove the MACRO. So we have to pop the stack. + // Being here also means that the MACRO is waiting for the first argument + // (which is not delimited), but we don't mind about it. + assert(frame.pos == 0); + parent.replace(cursor); + frames.pop(); - // i have to re-start the process, because it' a graphical delete - do_gdelete(); - } - } - } - else if (parent.isC()) + // if the new parent is a group with Id, and has no elements other than the + // cursor, we can remove it, because it' a graphical deleting + if (cursor.parent() && cursor.parent().isG() && cursor.parent().hasId()) + rgreplace_father(); + else if (cursor.parent().isC()) { - // being here means that there is an open frame for the control element - // and this element is closed at either side (no leftOpen no rightOpen) - // and the MACRO was waiting for a non delimited argument, so - // we can assert that frame.entry.pattern.size() >= 1 + // We have assumed that a MACRO cannot be a MACRO's child. + // At the moment, this assumption is valid, but in a future + // it might be false. assert(!frames.empty()); Frame& frame = frames.top(); + assert(frame.pos > 0); + frame.pos--; + } + } + else + { + // a prev does exist, we have to control if it's a delimited argument or not. + if (prev.isG() && !prev.hasId()) + { + // in this case, prev is a delimited argument, so we have + // to ideally remove the sequence of delimiters + Frame& frame = frames.top(); + assert(frame.pos > 1); + cursor.remove(); + prev.append(cursor); + assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size()); + + // these 3 lines of code update the member pos. + unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1; + assert(sequence_length); + frame.pos = frame.pos - sequence_length - 1; + } + else + { + // the prev is not a delimited argument, so we have to try to remove it. + // We "try", because the prev might be a something that + // a simple delete cannot remove completely + gdelete_prev(); + } + } - assert(frame.entry.pattern.size() >= 1); +} - cout << "do_gdelete: frames.top().pos = " << frames.top().pos << endl; +void +TPushParser::do_gdelete_groupId() +{ + // if we are here, the cursor's parent is a group with Id + assert(cursor.parent() && cursor.parent().isG() && cursor.parent().hasId()); + TNode parent = cursor.parent(); - TNode prev = cursor.prev(); + // we have to take different actions based on if the cursor has a preceding + // element or not + TNode prev = cursor.prev(); + if (prev) + { + // the cursor has a preceding element, so we try to remove it + gdelete_prev(); + + // We control if the group has to be removed, because the cursor + // might be the only element of the group. + if ((parent.first() == cursor) && parent.isG() && parent.hasId()) + rgreplace_father(); + + } + else + { + // the cursor has no preceding elements, so we have to remove the + // group. + rgreplace_father(); + + // we have to re-start the process, because it' a graphical delete + do_gdelete(); + } + +} // end of method do_gdelete_groupId() + +void +TPushParser::do_gdelete_phantom_group() +{ + // if we are here, the cursor MUST be a child of a + // phantom group. + assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId()); - if (!prev) + TNode parent = cursor.parent(); + + // now we have to control if the cursor has a preceding element or not + TNode prev = cursor.prev(); + if (prev) + { + // the cursor has a preceding element, so we try to remove it + gdelete_prev(); + + if (parent.size() == 1 && parent.parent().isSp()) + { + // in this case the gdelete_prev has removed the only element preceding the cursor. + // If the phantom group is an sp's child, it means that the user has removed all \' in the + // phantom group. + // We can remove the phamtom group and the sp element. But we also can only remove the + // phantom group, giving to the user the possibility of inserting an exponent. + // At the moment, we remove the sp element (and the phantom group), because it may be the correct + // behavior of a graphical deleting. + cursor.remove(); + parent.replace(cursor); + // now we have an sp element with two children: the first child (we don't know nothing about it) + // and the cursror. + assert(cursor.parent().size() == 2); + + // to delete the script we can invoke the do_gdelete_script(), which will do all controls we need. + // To give the possibility of insering an exponent + do_gdelete_script(); + } + else if (parent.parent().isSp()) + { + // in this case we have to place the cursor after the sp element + cursor.remove(); + assert(parent.parent().parent()); + parent.parent().parent().append(cursor); + } + } + else + { + // in this case the cursor is the only element of the phantom group, + // so we have to remove it. But, a phantom group has a special role, + // so we have to control the grand father of the cursor. + TNode gfather = parent.parent(); + if (!gfather) + { + // If here, the TML tree is in an inconsistent state + logger.error("do_gdelete_phantom: TML tree in a inconsistent state"); + } + else if (gfather.isC()) + { + // in this case the phantom group is child of a MACRO. + // We have to control the nature of this MACRO. + assert(!frames.empty()); + Frame& frame = frames.top(); + + if (frame.entry.leftOpen && frame.entry.rightOpen) + { + // in this case, the cursor'parent is in the second and last child + // of the MACRO. We can assert that the grand father has two + // children, which are both phantom group + assert(gfather.size() == 2); + assert((gfather.last() == parent) && (gfather.first().isG() && !gfather.first().hasId())); + assert(frame.pos == 0); + + TNode ggfather = gfather.parent(); + assert(ggfather); + cursor.remove(); + parent.remove(); + // i have to replace the gfather with the elements of its first child + gfather.replace(gfather.first().first(), TNode()); + ggfather.append(cursor); + // now we have the situation preceding the insertion of the leftOpen and rightOpen MACRO. + // this MACRO no longer exists. + frames.pop(); + } + else if (frame.entry.rightOpen) { - // in this case we can replace the MACRO with the cursor - // and pop the stack and we can assert that frame.pos == 0 + // the user has inserted a rightOpen MACRO, and now, this MACRO has no children (excluding the + // phantom group), so we remove the MACRO. + // We can assert that cursor's parent is the only child of the MACRO + assert(gfather.size() == 1); assert(frame.pos == 0); - cursor.remove(); // it should not be necessary, but i'm not shure - parent.replace(cursor); + cursor.remove(); + parent.remove(); + gfather.replace(cursor); + + // now we have the situation preceding the rightOpen MACRO, so we have to pop the frame frames.pop(); } + else if (frame.entry.leftOpen) + { + // this situation will never occur. + logger.error("the parser has generated a wrong TML tree"); + } + else if (!frame.entry.pattern.empty()) + { + // the MACRO accepts arguments, and the phantom group in which + // the cursor is, rappresents a delimited argument + // We have to control if the cursor's parent has a preceding element, + // or not. + TNode uncle = parent.prev(); + if (!uncle) + { + // the parent is the only element of the MACRO. + // we can assert that frame.pos == 0. + // In this case we can replace the MACRO with the cursor + assert(frame.pos == 0); + cursor.remove(); + parent.remove(); + gfather.replace(cursor); + frames.pop(); + } + else + { + // the parent has a preceding element. Now we have + // to control if the uncle is a delimited argument or not. + if (uncle.isG() && !uncle.hasId()) + { + // the uncle is a delimited argument. So we have to ideally + // remove the sequence of delimiters. + assert(frame.pos > 1); + unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1; + assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size()); + assert(sequence_length); + // sequence_length is the length of the delimiters sequence which separates + // the current parameter and the previous parameter + frame.pos = frame.pos - sequence_length - 1; + cursor.remove(); + parent.remove(); + uncle.append(cursor); + } + else + { + // the uncle is a not delimited argument, so we try to remove it. + cursor.remove(); + parent.replace(cursor); + parent = cursor.parent(); // i update the parent (it should be the MACRO) + assert(parent.isC()); + + // now we try to remove the uncle (now it' the preceding element) + gdelete_prev(); + } + } // this is the else's end, that handles the case in which an uncle exists + } // end of if (!frame.entry.pattern.empty()) else { - // in this case the cursor has a preceding element - // and there are differnt things based on the nature - // of the prev: if it's a phantom group do something, - // else do something else - if (prev.isG() && !prev.hasId()) + // the entry has no arguments, is not rightOpen and is not leftOpen. + logger.warning("parser: TML tree in a strange state, but we try to recover from it"); + if (gfather.size() == 1) { - // in this case we have to append the cursor - // to the prev and decrement frame.pos of the length of - // delimiters sequence that delimitates the preceding argument. - // So we ideally remove this sequence - assert(frame.pos > 1); cursor.remove(); - prev.append(cursor); - assert(frame.entry.previousParam(frame.pos) != frame.entry.pattern.size()); - unsigned sequence_length = frame.pos - frame.entry.previousParam(frame.pos) - 1; - assert(sequence_length); - frame.pos = frame.pos - sequence_length - 1; + parent.remove(); + gfather.replace(cursor); } else { - // the prev is an non delimited argument, so we try to - // remove it. - gdelete_prev(); + logger.warning("parser: TML tree in a very strange state, but we try to recover from it"); + cursor.remove(); + gfather.replace(cursor); } } - } - else if (parent.is("sp") || parent.is("sb")) + } // end of if (gfather.isC()) + else if (gfather.is("cell")) { - // being here means that a prev MUST exist - // and that there is only an element preceding the cursor. - // The sp's (or sb's) parent MUST NOT be a MACRO - - assert(parent.size() == 2); - assert(parent.parent() && !parent.parent().isC()); + // A table is a control sequence, so there is a frame in the stack + assert(!frames.empty()); + assert(frames.top().pos == 1); + assert(frames.top().entry.table == 1); - TNode prev = cursor.prev(); + // a cell MUST be a row's child, which in turn is a table's child + assert(gfather.parent() && gfather.parent().is("row") && gfather.parent().parent()); + TNode row = gfather.parent(); + + // in this case the cell has no element, so the user wants to delete this cell. + TNode prev_cell = gfather.prev(); cursor.remove(); - if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0)) - { - prev.remove(); - parent.replace(cursor); - - // now, cursor should be the only parent's child - assert(cursor.parent().size() == 1); + parent.remove(); + gfather.remove(); + // now the cell no longer exists - if (cursor.parent().isG() && cursor.parent().hasId()) rgreplace_futher(); - } + if (!prev_cell) + { + // in this case, the cell is the only cell in the row. + // So, we assume that the user wants to delete the entire row. + TNode table = row.parent(); + TNode prev_row = row.prev(); + row.remove(); + + if (!prev_row) + { + // the row was the only child of the table. + // so we have to delete the entire table + assert(table.parent()); + TNode parent_table = table.parent(); + table.remove(); + frames.pop(); + parent_table.append(cursor); + } + else + { + // there are other rows (one or more) + assert(prev_row.is("row")); + assert(prev_row.last()); + TNode last_cell = prev_row.last(); + assert(last_cell.is("cell")); + assert(last_cell.size() == 1); + assert(last_cell.first().isG() && !last_cell.first().hasId()); + last_cell.first().append(cursor); + } + } // end of if (!prev_cell) else { - assert(prev.hasId()); - parent.replace(prev); - prev.parent().append(cursor); - // now prev should have a preceding element - assert(cursor.parent().size() > 1); - } + // being here means that there is a previous cell, + // so we append the cursor to group. + assert(prev_cell.size() == 1); + assert(prev_cell.first().isG() && !prev_cell.first().hasId()); + prev_cell.first().append(cursor); + } + } // end of if (gfather.is("cell")) + else if (gfather.isSp()) + { + // in this case, the user typed a \'. So this phantom group + // contained a sequence of \'. + // Maybe in this part will never be used, because, if we delete last \' in the + // phantom group, we remove the phantom group also + // + // In any case, if we are here we have two possibilities: + // we can delete the phantom group; + // we can delete the superscript. + // At the moment we implement the first solution. To implement the second one, just remove + // delete the line code after the logger.warning and remove comment from the remaining lines + logger.warning("parser: TML tree in a strange state, we try to recover from it"); + parent.replace(cursor); + + //cursor.remove(); + //parent.remove(); + //gfather.replace(cursor); + } + else if (gfather.is("math")) + { + // in this case we ignore the user's will of deleting + // but we can decide to remove the math mode. + logger.info("Parser: nothing to delete"); } else { - // not handled: no control for tables, ... + // cursor' grand father is undefined + logger.error("parser: TML tree is in a unknown state"); } + } // end of the else of the if (prev) + +} + + +void +TPushParser::do_gdelete() +{ + // we have to handle the case in wich the cursor has a parent or not + if (!cursor.parent()) + { + // it's not a good situation...at the moment we do not take actions + logger.error("TML tree not well structured"); } else { - // the cursor has no parent!!! - // do nothing? - // emit an error? and if we want to emit an error, in which way? - } - -} // end of method + // a parent exists. We have to take differnt actions, based on the nature of + // the parent + TNode parent = cursor.parent(); + if (parent.is("math")) + { + // we ca do two thing...we can remove the math mode (it implies controlling the display attribute), we can do nothing + // At the moment, the user's will of deleting is simply ignored + logger.error("TML tree not well structured"); + } + else if (parent.isG()) + { + // the cursor's parent is a group. We have to control if it's a phantom group or not + if (parent.hasId()) + { + do_gdelete_groupId(); + } + else + { + do_gdelete_phantom_group(); + } + } // end of parent is group + else if (parent.isC()) + { + do_gdelete_macro(); + } // end of parent is a MACRO + else if (parent.isSp() || parent.isSb()) + { + do_gdelete_script(); + } // end of parent is sp or sb + } // end of the else which consider the case in which parent exists + +} // end of method do_gdelete void TPushParser::process(const TToken& token) @@ -1023,176 +1233,156 @@ TPushParser::process(const TToken& token) case TToken::ACTIVE: do_active(token.value); break; case TToken::COMMENT: do_comment(); break; case TToken::CONTROL: do_control(token.value); break; - case TToken::GDELETE: do_gdelete(); break; } } void TPushParser::push(const TToken& token) { - cerr << "TPushParser::push " << token.value << " (cat: " << token.category << ")" << endl; - - if (token.category == TToken::GDELETE) - { - cout << "push: i have to process a token with category member = GDELETE" << endl; - process(token); - } - else if ((doc.root().first() && doc.root().first().is("math")) || token.category == TToken::SHIFT) - { + TNode parent = cursor.parent(); + // If the cursor has no parent then it is detached from the editing + // tree, which means this token will be ignored - TNode parent = cursor.parent(); - // If the cursor has no parent then it is detached from the editing - // tree, which means this token will be ignored - - if (parent) - // If the parent is a phantom group and the grand-parent is a - // control sequence, there are two cases: - // a. we are parsing a delimited argument of a entry - // b. we are parsing a side of a right- or left-open entry - if (parent.isG() && !parent.hasId() && parent.parent().isC()) - { - // There must be an open frame, for the grand-parent is a control sequence - assert(!frames.empty()); - Frame& frame = frames.top(); - if (!frame.entry.pattern.empty()) + if (parent) + // If the parent is a phantom group and the grand-parent is a + // control sequence, there are two cases: + // a. we are parsing a delimited argument of a entry + // b. we are parsing a side of a right- or left-open entry + if (parent.isG() && !parent.hasId() && parent.parent().isC()) + { + // There must be an open frame, for the grand-parent is a control sequence + assert(!frames.empty()); + Frame& frame = frames.top(); + if (!frame.entry.pattern.empty()) + { + // The entry pattern is not empty. By our conventions this means + // the entry cannot be open at either end, hence we are parsing + // a delimited argument + assert(frame.pos + 1 < frame.entry.pattern.size()); + assert(frame.entry.pattern[frame.pos + 1].category != TToken::PARAMETER); + if (frame.entry.pattern[frame.pos + 1] == token) { - // The entry pattern is not empty. By our conventions this means - // the entry cannot be open at either end, hence we are parsing - // a delimited argument - assert(frame.pos + 1 < frame.entry.pattern.size()); - assert(frame.entry.pattern[frame.pos + 1].category != TToken::PARAMETER); - if (frame.entry.pattern[frame.pos + 1] == token) - { - // The token matches with a delimiter of the argument, - // so we increment the frame.pos + // The token matches with a delimiter of the argument, + // so we increment the frame.pos + frame.pos++; + + if (frame.entry.lastDelimiter(frame.pos)) + { + // this delimiter is the last one for the argumet, + // so the argument is completed + cursor.remove(); frame.pos++; - if (frame.entry.lastDelimiter(frame.pos)) + if (frame.pos == frame.entry.pattern.size()) { - // this delimiter is the last one for the argumet, - // so the argument is completed - cursor.remove(); - frame.pos++; - - if (frame.pos == frame.entry.pattern.size()) - { - // This token has completed the entry - frames.pop(); - advance(parent.parent()); - } - else if (frame.entry.paramDelimited(frame.pos)) - { - // For the next is a delimited argument we have to place - // a suitable phantom group with the cursor inside - TNode g = doc.createG(); - parent.parent().append(g); - g.append(cursor); - } - else - parent.parent().append(cursor); + // This token has completed the entry + advance(parent); } - } - else - { - // Delimiter mismatch. - if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) + else if (frame.entry.paramDelimited(frame.pos)) { - // in this case, there is a sequence of delimiters that delimitates - // the argument, and the user correctly inserted a portion of this - // sequence, but has inserted a wrong delimiter. - // Here, there are some possibilities: - // - ignore the token, and wait for the correct delimiter - // - ignore the token, wait for the correct delimiter and emit an error - // If we want to emit an error, we shlould implement a class, that handle - // the error. - // At the moment, the error is printed to screen - cout << "push: it's not the correct delimiter...you have to type " << frame.entry.pattern[frame.pos + 1].value << endl; + // For the next is a delimited argument we have to place + // a suitable phantom group with the cursor inside + TNode g = doc.createG(); + parent.parent().append(g); + g.append(cursor); } else - { - // in this case, the sequence of delimiters is composed of one - // delimiter. It means that we have to process the token - process(token); - } - } + parent.parent().append(cursor); + } } else { - // The entry pattern is empty, hence we are parsing a right-open - // entry. What happens if we actually are in the left side? - // This could happen only when re-editing an entered expression - // We'll see... - assert(frame.entry.rightOpen); - process(token); - } - } - else if (parent.isC()) - { - // We are parsing a non-delimited argument entry - // or a fixed token - Frame& frame = frames.top(); - assert(frame.pos < frame.entry.pattern.size()); - - cout << "push: there is a frame with pos " << frame.pos << endl; - if (frame.entry.pattern[frame.pos].category == TToken::PARAMETER) - { - // As by the TeX parsing rules of undelimited parameters, - // empty spaces are ignored - if (token.category != TToken::SPACE) - { - // We need to increase the frame position here, becase inside - // process the function advance will be called. At that point - // it will be important for the parser to know that the entry - // has been completed in order to place the cursor correctly - // in the next position - frame.pos++; + // Delimiter mismatch. + if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) + { + // in this case, there is a sequence of delimiters that delimitates + // the argument, and the user correctly inserted a portion of this + // sequence, but now has inserted a wrong delimiter. + // Here, there are some possibilities: + // - ignore the token, and wait for the correct delimiter + // - ignore the token, wait for the correct delimiter and emit an error + // At the moment, we implement the second possibily + logger.error("parser: it's not the correct delimiter...you have to type " + frame.entry.pattern[frame.pos + 1].value); + } + else + { + // in this case, the sequence of delimiters is composed of one + // delimiter. It means that we have to process the token process(token); - } + } } - else if (frame.entry.pattern[frame.pos] == token) + } + else + { + // The entry pattern is empty, hence we are parsing a right-open + // entry. What happens if we actually are in the left side? + // This could happen only when re-editing an entered expression + // We'll see... + assert(frame.entry.rightOpen); + process(token); + } + } + else if (parent.isC()) + { + // We are parsing a non-delimited argument entry + // or a fixed token + Frame& frame = frames.top(); + assert(frame.pos < frame.entry.pattern.size()); + + if (frame.entry.pattern[frame.pos].category == TToken::PARAMETER) + { + // As by the TeX parsing rules of undelimited parameters, + // empty spaces are ignored + if (token.category != TToken::SPACE) { - // The token has been accepted - frame.pos++; - if (frame.pos < frame.entry.pattern.size() && - frame.entry.paramDelimited(frame.pos)) - { - // If the next is a delimited argument we have to place - // the phantom group with the cursor inside - TNode g = doc.createG(); - cursor.replace(g); - g.append(cursor); - } - else - advance(parent); + // We need to increase the frame position here, becase inside + // process the function advance will be called. At that point + // it will be important for the parser to know that the entry + // has been completed in order to place the cursor correctly + // in the next position + frame.pos++; + process(token); } - else + } + else if (frame.entry.pattern[frame.pos] == token) + { + // The token has been accepted + frame.pos++; + if (frame.pos < frame.entry.pattern.size() && + frame.entry.paramDelimited(frame.pos)) { - // There is a mismatch. Emit an error and ignore the token? - cerr << "ERROR: token ignored: " << token.value << " (cat: " << token.category << ")" << endl; + // If the next is a delimited argument we have to place + // the phantom group with the cursor inside + TNode g = doc.createG(); + cursor.replace(g); + g.append(cursor); } + else + advance(parent); + } + else + { + // There is a mismatch. Emit an error and ignore the token? + logger.debug("parser: token ignored: " + token.value); + } - } - else - process(token); - else - { - cout << "ignored token" << endl; - } - - //if (listener) listener->callback(doc); //it shoul be repristened if you remove the comment in the else above - - } // this end corresponds to the if ((doc.root().first() && doc.root().first().is("math")) || token.category == TToken::SHIFT) + } + else + process(token); else { - // there is no math element - // In this case, i think we have to emit an error - cout << "push: ignored token...you have to enter in math mode...insert $" << endl; + logger.warning("ignored token"); } - if (factory) factory->documentModified(doc); + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); +} - if (frames.empty()) cout << "stack vuoto" << endl; - else cout << "stack non vuoto" << endl; +std::string +TPushParser::drop() +{ + do_gdelete(); + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return ""; } void @@ -1233,6 +1423,21 @@ TPushParser::advance(const TNode& node) void TPushParser::setCursorHint(const std::string& c) { - cursor["val"] = c; - if (factory) factory->documentModified(doc); + if (cursor["val"] != c) + { + cursor["val"] = c; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + } +} + +bool +TPushParser::thaw() +{ + if (APushParser::thaw() && factory && doc.dirtyNode()) + { + factory->documentModified(doc); + return true; + } + else + return false; }