X-Git-Url: http://matita.cs.unibo.it/gitweb/?a=blobdiff_plain;f=helm%2FDEVEL%2Fmathml_editor%2Fsrc%2FTPushParser.cc;h=0c96fe43968c94be52a1fdcffac385012d683ebb;hb=97c2d258a5c524eb5c4b85208899d80751a2c82f;hp=870e9f0a707a6b86d05c24ef9b7a4677729d3b40;hpb=12274c7d0f49801a75e1f18ab181bed99ae66256;p=helm.git diff --git a/helm/DEVEL/mathml_editor/src/TPushParser.cc b/helm/DEVEL/mathml_editor/src/TPushParser.cc index 870e9f0a7..0c96fe439 100644 --- a/helm/DEVEL/mathml_editor/src/TPushParser.cc +++ b/helm/DEVEL/mathml_editor/src/TPushParser.cc @@ -1,3 +1,29 @@ +/* This file is part of EdiTeX, an editor of mathematical + * expressions based on TeX syntax. + * + * Copyright (C) 2002-2003 Luca Padovani , + * 2003 Paolo Marinelli . + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For more information, please visit the project's home page + * http://helm.cs.unibo.it/editex/ + * or send an email to + */ + +#include #include "ALogger.hh" #include "TPushParser.hh" @@ -13,30 +39,48 @@ TPushParser::TPushParser(ALogger& l, AMathMLFactory& f, const TDictionary& d) : init(); } +TPushParser::~TPushParser() +{ +} + void TPushParser::init() { - nextId = 1; cursor = doc.create("cursor"); - cursor["id"] = "I0"; - doc.clearDirty(); - doc.root().append(cursor); - logger.verbosity(ALogger::Debug); + cursor["visible"] = "1"; + hiddenCursor = 0; + reset(); } -TPushParser::~TPushParser() +void +TPushParser::reset() { + nextId = 1; + if (cursor.parent()) cursor.remove(); + cursor["val"] = ""; + doc.reset(); + doc.root().append(cursor); + if (factory && !frozen()) factory->documentModified(doc); } -std::string -TPushParser::PRIME() const +TNode +TPushParser::PRIME() { const TDictionary::Entry entry = dictionary.find("prime"); - if (entry.cls == TDictionary::OPERATOR) return entry.value; - else return "?"; + if (entry.cls == TDictionary::OPERATOR) + { + TNode op = doc.createO(entry.value, nextId++); + op["name"] = "prime"; + return op; + } + else + { + TNode op = doc.createO("?", nextId++); + return op; + } } -void +bool TPushParser::do_begin() { TNode parent = cursor.parent(); @@ -56,9 +100,69 @@ TPushParser::do_begin() cursor.replace(g); g.append(cursor); } + return true; } -void +bool +TPushParser::correctBrace() +{ + // this method MUST be invoked when the cursor is child of a + // phantom group, which in turn is the last rightOpen MACRO's child. + // The only way to exit from a rightOpen MACRO is opening a group before + // inserting the MACRO and, once the MACRO is completely inserted, closing + // the group. + // This method return true if the condition above is true. False, otherwise. + assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId()); + TNode parent = cursor.parent(); + assert(parent.parent() && parent.parent().isC()); + assert(!frames.empty()); + Frame& frame = frames.top(); + assert(frame.entry.rightOpen); + assert(parent.parent().last() == parent); + + TNode c = parent.parent(); + bool stop = false; + bool ok = false; + TNode node = c.parent(); + do + { + if (node.isG() && node.hasId()) + { + // in this case, the rightOpen MACRO is a child of a group with id. + // So, the '}' is correct + ok = true; + stop = true; + } + else if (node.isG()) + { + // the MACRO is a phantom group's child. We have to control why we + // have this phantom group + TNode nodeParent = node.parent(); + if (nodeParent && nodeParent.isC()) + { + // we have to control the nature of this MACRO + const TDictionary::Entry& entry = dictionary.find(nodeParent.nameC()); + if (entry.rightOpen && node == nodeParent.last()) + { + // in this case we have to re-iterate the process + node = nodeParent.parent(); + } + else stop = true; + } + else stop = true; + } + else + { + // at the moment we assume that a MACRO cannot be child of an element other than a group + stop = true; + } + } + while (!stop); + + return ok; +} + +bool TPushParser::do_end() { TNode parent = cursor.parent(); @@ -67,6 +171,7 @@ TPushParser::do_end() // normal closing brace for an explicitly open group cursor.remove(); advance(parent); + return true; } else if (parent && parent.isG() && parent.parent() && parent.parent().is("cell")) { @@ -74,29 +179,67 @@ TPushParser::do_end() // closing brace for a structure in which & or \cr have been used TNode row = parent.parent().parent(); assert(row && row.is("row")); - TNode table = row.parent(); - assert(table); + assert(row.parent()); advance(row); + return true; } - 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); + // In this case, we have to control the cursor's grand parent. + TNode gparent = parent.parent(); + + if (gparent.isC() && gparent.last() == parent) + { + // a frame MUST be in the stack + assert(!frames.empty()); + + // we have to control the nature of this macro + if (frames.top().entry.rightOpen) + { + // in this case, the '}' character is the proper way to exit from the phantom group, and + // in particular, this character means that the user wants to exit from the MACRO. + // A rightOpen MACRO MUST be descendant of a group with Id. This '}' is the closing brace of this + // group. So, we have to control if this group exists. This groyp could exist, but this MACRO could + // be another MACRO's child, so we have to control this last MACRO recursively. This recurive control + // is done by the correctBrace method. + if (!correctBrace()) + { + // the '}' is not correct + logger.warning("nothing to close"); + return false; + } + else + { + cursor.remove(); + advance(parent); + return true; + } + } + else + { + logger.warning("ignored closing brace"); + return false; + } + } + else + { + // at the moment, a phantom group with the cursor inside can be a MACRO's child or a cell's child, and these cases + // are handled in other blocks of code. + logger.error("do_end: strange TML tree"); + return false; + } } else { - // In this case, there is a redundant '}', so I think we can ignore it and + // In this case, there is a redundant '}', so we can ignore it and // emit an error - logger.error("There so no corresponding'{'"); + logger.warning("There is so no corresponding'{'"); + return false; //assert(0); } } -void +bool TPushParser::do_shift() { TNode parent = cursor.parent(); @@ -108,6 +251,7 @@ TPushParser::do_shift() cursor.replace(math); math.append(g); g.append(cursor); + return true; } else if (parent.isG() && !parent.hasId() && parent.parent() && parent.parent().is("math")) { @@ -119,12 +263,14 @@ TPushParser::do_shift() { // one math shift is enough to close it cursor.remove(); + return true; } else { // we need two closing math shifts //cursor.remove(); ?? parent.parent().append(cursor); + return true; // ??? } } else if (parent.parent()["display"] != "1") @@ -132,29 +278,27 @@ TPushParser::do_shift() // there is nothing before the cursor, and the math is not // in display mode, so this must be a double math shift parent.parent()["display"] = "1"; + return true; } else { parent.parent().append(cursor); + return true; } } else if (parent.is("math")) { cursor.remove(); + return true; } else { - // 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("ERROR: math shift"); + logger.warning("not allowed here"); + return false; } } -void +bool TPushParser::do_align() { TNode parent = cursor.parent(); @@ -167,6 +311,7 @@ TPushParser::do_align() row.append(cell); cell.append(g); g.append(parent.first(), cursor); + return true; } else if (parent && parent.isG() && parent.parent().is("cell")) { @@ -181,26 +326,31 @@ TPushParser::do_align() TNode g = doc.createG(); cell.append(g); g.append(cursor); + return true; } else { - logger.error("alignment tab used outside matrix"); + logger.warning("alignment tab used outside matrix"); + return false; } } -void +bool TPushParser::do_eol() { //if (cursor.parent()) cursor.remove(); + logger.warning("ignored token"); + return false; } -void +bool TPushParser::do_parameter(const std::string& p) { - // ??? + logger.warning("ignored token"); + return false; } -void +bool TPushParser::do_subscript() { TNode parent = cursor.parent(); @@ -214,6 +364,7 @@ TPushParser::do_subscript() cursor.replace(elem); elem.append(g); elem.append(cursor); + return true; } else { @@ -221,16 +372,30 @@ TPushParser::do_subscript() prev.replace(elem); elem.append(prev); elem.append(cursor); + return true; } } else if (parent.isSb() && cursor == parent[1]) { - if (parent["under"] == "1") logger.error("already under"); - else parent["under"] = "1"; + if (parent["under"] == "1") + { + logger.warning("already under"); + return false; + } + else + { + parent["under"] = "1"; + return true; + } + } + else + { + logger.warning("ignored token"); + return false; } } -void +bool TPushParser::do_superscript() { TNode parent = cursor.parent(); @@ -244,6 +409,7 @@ TPushParser::do_superscript() cursor.replace(elem); elem.append(g); elem.append(cursor); + return true; } else { @@ -251,56 +417,71 @@ TPushParser::do_superscript() prev.replace(elem); elem.append(prev); elem.append(cursor); + return true; } } else if (parent.isSp() && cursor == parent[1]) { - if (parent["over"] == "1") logger.error("already over"); - else parent["over"] = "1"; + if (parent["over"] == "1") + { + logger.warning("already over"); + return false; + } + else + { + parent["over"] = "1"; + return true; + } + } + else + { + logger.warning("ignored token"); + return false; } } -void +bool +TPushParser::do_ignorablespace(const std::string& s) +{ + // At the moment, do nothing +} + +bool TPushParser::do_space(const std::string&) { - // ? may be used to distinguish tokens in some mode? + TNode elem = doc.createS(nextId++); + cursor.replace(elem); + advance(elem); + return true; } -void +bool TPushParser::do_letter(const std::string& s) { //TNode parent = cursor.parent(); TNode elem = doc.createI(s, nextId++); cursor.replace(elem); advance(elem); + return true; } -void +bool TPushParser::do_digit(const std::string& s) { - TNode parent = cursor.parent(); - TNode prev = cursor.prev(); - if (prev && parent.isG() && prev.is("n")) - { - TNode elem = doc.createN(prev.value() + s, nextId++); - prev.replace(elem); - } - else - { - TNode elem = doc.createN(s, nextId++); - cursor.replace(elem); - advance(elem); - } + TNode elem = doc.createN(s, nextId++); + cursor.replace(elem); + advance(elem); + return true; } bool TPushParser::isPrimes(const TNode& node) const { assert(node); - return node.isG() && node.last() && node.last().is("o") && node.last()["val"] == PRIME(); + return node.isG() && node.last() && node.last().is("o") && node.last()["name"] == "prime"; } -void +bool TPushParser::do_apostrophe() { if (cursor.parent() && cursor.parent().isG()) @@ -308,11 +489,17 @@ TPushParser::do_apostrophe() if (TNode prev = cursor.prev()) { if (prev.isSp() && prev[1] && isPrimes(prev[1])) - prev[1].append(doc.createO(PRIME(), nextId++)); + { + prev[1].append(PRIME()); + return true; + } else if (prev.isSb() && prev[0] && prev[0].isSp() && prev[0][1] && isPrimes(prev[0][1])) - prev[0][1].append(doc.createO(PRIME(), nextId++)); + { + prev[0][1].append(PRIME()); + return true; + } else { TNode elem = doc.create("sp"); @@ -320,52 +507,59 @@ TPushParser::do_apostrophe() prev.replace(elem); elem.append(prev); elem.append(g); - g.append(doc.createO(PRIME(), nextId++)); + g.append(PRIME()); + return true; } } else { // is it an error? - logger.error("you have to type one identifier before the ''"); + logger.warning("you have to insert an identifier before a ''"); + return false; } } else { - // error ?? + logger.warning("cursor has to be in a group"); + return false; } } -void +bool TPushParser::do_other(const std::string& s) { switch (s[0]) { case '\'': - do_apostrophe(); + return 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); + advance(elem); + return true; break; } } -void +bool TPushParser::do_active(const std::string&) { // ??? space? + logger.warning("ignored token"); + return false; } -void +bool TPushParser::do_comment() { // ??? + return false; } -void +bool TPushParser::do_cr() { TNode parent = cursor.parent(); @@ -385,13 +579,20 @@ TPushParser::do_cr() row.append(cell); cell.append(g); g.append(cursor); + return true; + } + else + { + // at the moment, \cr can only be used inside a table + logger.warning("cr used outside a table"); + return false; } } -void +bool TPushParser::do_control(const std::string& name) { - if (name == "cr") do_cr(); + if (name == "cr") return do_cr(); else { TNode parent = cursor.parent(); @@ -404,6 +605,7 @@ TPushParser::do_control(const std::string& name) t["name"] = name; cursor.replace(t); advance(t); + return true; } break; case TDictionary::OPERATOR: @@ -412,6 +614,7 @@ TPushParser::do_control(const std::string& name) t["name"] = name; cursor.replace(t); advance(t); + return true; } break; case TDictionary::NUMBER: @@ -420,54 +623,49 @@ TPushParser::do_control(const std::string& name) t["name"] = name; cursor.replace(t); advance(t); + return true; } break; case TDictionary::MACRO: { - TNode m = doc.createC(name, nextId++); - cursor.replace(m); - - logger.debug("do_control: i have pushed a frame in the stack. Here is the pattern of the associated MACRO"); - for (unsigned i = 0; i < entry.pattern.size(); i++) - logger.debug(entry.pattern[i].value); - - if (entry.leftOpen && entry.rightOpen) - { - assert(entry.pattern.empty()); - assert(parent.isG()); - TNode g1 = doc.createG(); - g1["left-open"] = "1"; - g1.append(parent.first(), m); - m.append(g1); - TNode g2 = doc.createG(); - g2.append(cursor); - m.append(g2); - frames.push(Frame(entry)); - } - else if (entry.leftOpen) - { - assert(parent.isG()); - TNode g = doc.createG(); - g["left-open"] = "1"; - g.append(parent.first(), m); - m.append(g); - advance(m); - } - else if (entry.rightOpen) - { - assert(entry.pattern.empty()); - assert(parent.isG()); - TNode g = doc.createG(); - g.append(cursor); - m.append(g); - frames.push(Frame(entry)); - } - else if (!entry.pattern.empty()) + if (parent.isG()) { - if (parent.isG()) - { + TNode m = doc.createC(name, nextId++); + cursor.replace(m); + if (entry.leftOpen && entry.rightOpen) + { + assert(entry.pattern.empty()); + assert(parent.isG()); + TNode g1 = doc.createG(); + g1["left-open"] = "1"; + g1.append(parent.first(), m); + m.append(g1); + TNode g2 = doc.createG(); + g2.append(cursor); + m.append(g2); + frames.push(Frame(entry)); + } + else if (entry.leftOpen) + { + assert(parent.isG()); + TNode g = doc.createG(); + g["left-open"] = "1"; + g.append(parent.first(), m); + m.append(g); + advance(m); + } + else if (entry.rightOpen) + { + assert(entry.pattern.empty()); + assert(parent.isG()); + TNode g = doc.createG(); + g.append(cursor); + m.append(g); + frames.push(Frame(entry)); + } + else if (!entry.pattern.empty()) + { frames.push(Frame(entry)); - if (entry.paramDelimited(0)) { TNode g = doc.createG(); @@ -479,311 +677,403 @@ TPushParser::do_control(const std::string& name) } else { - // error, but we could handle this very easily - logger.error("error, but we could handle this easily"); - } + // it's an empty macro + advance(m); + } + return true; + } + else if (!entry.pattern.size() && !entry.rightOpen && !entry.leftOpen) + { + // a macro with no arguments and no right open and no left open, can be child of anything + TNode m = doc.createC(name, nextId++); + cursor.replace(m); + advance(m); + return true; + } + else + { + // a macro with arguments or a rightOpen or leftOpen macro must be a group's child + logger.warning("ignored token: this macro should be in a group"); + return false; } - else advance(m); } break; case TDictionary::UNDEFINED: { - logger.error("ERROR: using undefined macro ` + name '"); + logger.warning("using undefined macro " + name); TNode m = doc.createC(name, nextId++); cursor.replace(m); advance(m); + return true; } break; default: - assert(0); + { + //assert(0); + logger.warning("ignored token"); + return false; + } } } } -void -TPushParser::gdelete_prev_token() +std::string +TPushParser::drop_prev_token(bool special) { assert(cursor.prev()); assert(cursor.parent()); TNode prev = cursor.prev(); - assert(prev.is("i") || prev.is("o") || prev.is("n")); - - logger.debug("gdelete_prev_token: start"); + assert(prev.isT()); + + DOM::UCS4String ucs4val = prev.element().getAttribute("val"); + bool macro = prev.element().hasAttribute("name"); + std::string utf8name; + if (macro) utf8name = prev.element().getAttribute("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()) { - logger.debug("gdelete_prev_token: i have removed an element"); - 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); - - frame.pos--; - logger.debug("gdelete_prev_token: it was a MACRO's child so i have decremented the member pos of the associated frame"); - } + // 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 + + if ((ucs4val.length() > 1)) { - ucs4val.erase(ucs4val.length() - 1, 1); - prev.element().setAttribute(DOM::GdomeString("val"), DOM::GdomeString(ucs4val)); + if (!macro) + { + // in this case we can return the content of ucs4val, but we have + // to convert it in a utf8 + DOM::GdomeString gdsval(ucs4val); + std::string utf8val(gdsval); + switch (utf8val[utf8val.length() - 1]) + { + case '-': + case '_': + return (special) ? std::string(utf8val, 0, utf8val.length() - 1) + "\\" : std::string(utf8val, 0, utf8val.length() - 1); + default: return (special) ? "" : std::string(utf8val, 0, utf8val.length() - 1); + } + } + else + { + // in this case, the content of val could be in unicode, + // but we have the attribute name, which doesn't contain character not representable + // with a byte. + return (special) ? "\\" + utf8name : ""; + } } - - logger.debug("gdelete_prev_token: bye..."); + else if (macro && special) return "\\" + utf8name; + else return ""; } -void -TPushParser::gdelete_prev_script() +std::string +TPushParser::drop_prev_script(bool special) { - logger.debug("gdelete_prev_script: start..."); - // this method delete a sp or an sb preceding the cursor + // this method deletes 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 child - gdelete_prev(); - logger.debug("gdelete_prev_script: bye..."); + // we can invoke the drop_prev, because a sp (sb) MUST have two children + // but we cannot invoke do_drop_script because it assumes when called, the first + // child has been removed yet. + if (cursor.prev().isG() && !prev.hasId()) + { + // in this case, the user has inserted a sequence of '. + // Hence, we force a normal deletion, because the behavior must be the same + // for the two kind of deletion + return drop_prev(false); + } + else return drop_prev(special); } -void -TPushParser::gdelete_prev_group() +std::string +TPushParser::drop_prev_group(bool special) { - logger.debug("gdelete_prev_group: start"); assert(cursor.prev() && cursor.prev().isG()); + TNode parent = cursor.parent(); TNode prev = cursor.prev(); cursor.remove(); prev.append(cursor); - logger.debug("gdelete_prev_group: i have to call the do_gdelete()"); - - // a group can have no child, so the gdelete_prev is not appropriate - // so this method is not equivalent to the one above - do_gdelete(); - - logger.debug("gdelete_prev_group: do_gdelete terminated"); - logger.debug("gdelete_prev_group: bye..."); + + if (parent.isC() && prev.hasId()) + { + // this previous group is a macro's argument. Entering inside it means that + // this argument becomes incomplete. Hence, we have to decrement the member pos. + assert(!frames.empty()); + frames.top().pos--; + } + + if (special) return ""; + else + { + // a group could have no children, so the drop_prev is not appropriate + // so, this method is not equivalent to the one above + return do_drop(special); + } } -void -TPushParser::gdelete_prev_macro() +std::string +TPushParser::drop_prev_macro(bool special) { - logger.debug("gdelete_prev_macro: start"); assert(cursor.parent()); assert(cursor.prev()); TNode prev = cursor.prev(); assert(prev.isC()); + + std::string macro_name = prev.nameC(); + + TNode parent = cursor.parent(); const TDictionary::Entry& entry = dictionary.find(prev["name"]); if (!entry.defined()) { - // I assume tha the user want to completely delete the undefined macro - logger.debug("gdelete_prev_macro: i have to remove an undefined macro"); + // In this case, with a normal deletion, we completely remove the macro. + // With a special deletion, we remove the last character of the macro's name. cursor.remove(); prev.replace(cursor); + if (cursor.parent().isC()) + { + // we have removed a macro's child + assert(!frames.empty()); + frames.top().pos--; + } + if (special) return "\\" + macro_name.erase(macro_name.length() - 1, 1); // we remove the last char, because an undefined macro's name is visible + return ""; } else - { - logger.debug("gdelete_prev_macro: i have to start to delete a defined MACRO"); - - // i start to remove a MACRO. Different actions must be taken, based on the nature - // of the MACRO. In some case, we can't remove the MACRO immediately, in other - // case it's correct. In the first case, we have to update the stack, pushing - // a frame in the stack with a correct value of pos, in the - // second one, we do not have to push a frame in the stack + { + // 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 (entry.rightOpen) { - // if the control element is ightOpen, the cursor should be placed after - // the last child of the control element's last child, and try to remove something + // In this fragment of code we also handle the leftOpen && rightOpen MACRO. + // since 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 remove the MACRO. - logger.debug("gdelte_prev_macro: i have to delete a control rightOpen, so i push an element in the stack"); - + // will completely remove the MACRO. frames.push(Frame(entry)); - logger.debug("gdelete_prev_macro: i control the values of the pushed frame"); - logger.debug("gdelete_prev_macro: rightOpen"); - cout << frames.top().entry.rightOpen << endl; - logger.debug("gdelete_prev_macro: leftOpen"); - cout << frames.top().entry.leftOpen << endl; - logger.debug("gdelete_prev_macro: pattern.empty()"); - cout << frames.top().entry.pattern.empty() << endl; - // 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); - - // the gdelete_prev is not appropriate, because the last child of the MACRO could have no child - logger.debug("gdelete_prev_macro: i have to call do_gdelete_phantom_group"); - do_gdelete_phantom_group(); - - if (!frames.empty()) + + if (special) return ""; + else { - logger.debug("gdelete_prev_macro: after the do_gdelete_phantom_group, the frame in the stack has to be rightOpen"); - cout << frames.top().entry.rightOpen << endl; - logger.debug("gdelte_prev_macro: after the do_gdelete_phantom_group, the top element's pattern MUST be empty"); - cout << frames.top().entry.pattern.empty() << endl; + // the drop_prev is not appropriate, because the last child of the MACRO could have no children + return do_drop_phantom_group(special); } - else - logger.debug("gdelete_prev_macro: the do_gdelete_phantom_group has removed all frames in the stack"); - } else if (entry.leftOpen) { - logger.debug("gdelete_prev_macro: i have to delete a control element with leftOpen"); // 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, i 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 + // We could remove the last child of the phantom group, but + // it's not clear if it's the correct behavior of the graphical deletion. + // At the moment, to give a standard behavior, we remove the last element. + // With a special deletion, we do not remove it. 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(); + if (g.size()) + { + // in this case, the phantom group has at least one child, so we can call the + // TNode::replace. + g.remove(); + prev.replace(g.first(), TNode()); + parent.append(cursor); + if (special) return "\\" + macro_name; + else return do_drop(special); + } + else + { + // otherwise, the phantom group has no children, so we remove it, also the MACRO. + cursor.remove(); + g.remove(); + prev.replace(cursor); + if (special) return "\\" + macro_name; + else + { + // Once removed this empty macro, we could try to remove something else. + // This would be justified by the fact that, generally, an empty macro gives no visual information + // about it. + return do_drop(special); // special is false + } + } } else if (!entry.pattern.empty()) { - // we have to start removing a MACRO which accepts arguments. - // a MACRO without child does not exist - - logger.debug("gdelete_prev_macro: i have to remove a MACRO with argument"); + // we have to start to remove a MACRO which accepts arguments. + // If the MACRO accepts arguments, the MACRO has at least one child assert(prev.size() >= 1); - // Differnt actions should 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 + // 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()) { - // the last argument of the MACRO is a delimited argumet. We ideally remove - // the sequence of delimiters - logger.debug("gdelete_prev_macro: the last argument of the MACRO is delimited"); - cursor.remove(); - prev.last().append(cursor); - // i 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; - unsigned p = entry.pattern.size() - sequence_length - 1; - frames.push(Frame(entry, p)); - logger.debug("gdelete_prev_macro: i have inserted a frame, it's pos is: "); - cout << frames.top().pos << endl; - - } - else - { - // 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. - logger.debug("gdelete_prev_macro: the last argumet of the MACRO is not delimited, so itry to remove it"); - cursor.remove(); - if (entry.table == 1 && prev.last().is("row")) + if (special) { - // in this case the cursor should be appended to the group associated to - // the last cell of the last row of the table - logger.debug("gdelete_prev_macro: but it is a table with rows, so i append the cursor at the end of the table"); - assert(prev.last().last().is("cell") && prev.last().last().first().isG()); - prev.last().last().first().append(cursor); + // in this case, we have to start removing the last delimiter + frames.push(Frame(entry, entry.pattern.size() - 2)); + + cursor.remove(); + prev.last().append(cursor); + + std::string last_del = entry.pattern[entry.pattern.size() - 1].value; + + return "\\" + last_del; } else { - logger.debug("gdelete_prev_macro: i append the cursor to the MACRO"); - prev.append(cursor); + // the last argument of the MACRO is a delimited argumet. We ideally remove + // the sequence of delimiters + cursor.remove(); + prev.last().append(cursor); + // 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; + 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)); + + // To give a standard behavior to the graphical deletion, we remove the last + // element of the macro. Since we are in a phantom group, we can invoke the + // do_drop_phantom_group(special). + return do_drop_phantom_group(special); } - - frames.push(Frame(entry, entry.pattern.size())); - - logger.debug("gdelete_prev_macro: i've pushed a frame in the stack, and it's value of pos is "); - cout << frames.top().pos << endl; - - if (cursor.prev()) + } + else + { + // 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")) { - logger.debug("gdelete_prev_macro: i invoke the gdelete_prev"); - gdelete_prev(); + // in this case the cursor has to 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); + + // we have to push a frame in the stack. Since tables has a pattern size = 1, we have to + // set pos at 0, because appending the cursor to the last cell means that this argument + // is not whole inserted. + // We don't call frames.push(Frame(entry)), because it incoditionaly set pos at 0. The + // following line is more general + frames.push(Frame(entry, entry.pattern.size() - 1)); + if (special) + { + // to type a table with rows and cells, the user had typed a + // "{", and to exit from it, the user had inserted a "}". + // Since we are in a special deletion, we just idealy remove the "}" + return ""; + } + else return do_drop_phantom_group(special); } else { - logger.debug("gdelete_prev_macro: i invoke the do_gdelete"); - 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 - 1, because we have been started to delete + // a MACRO. It means that all of the MACRO's arguments have been inserted, but + frames.push(Frame(entry, entry.pattern.size())); + prev.append(cursor); + return drop_prev(special); } + } // 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 not rightOpen, nor leftOpen, - // and has no pattern. It means that it has no childs. + // 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 - logger.debug("gdelete_prev_macro: the MACRO is empty, i remove it"); assert(prev.size() == 0); cursor.remove(); prev.replace(cursor); + if (cursor.parent().isC()) + { + // we have removed an empty macro, which was a non delimited argument of a macro. + // We have to decrement pos + assert(!frames.empty()); + frames.top().pos--; + } + + if (special) return "\\" + macro_name; + else return ""; + + // now we could start to remove something else. This behavior would be justified by the + // fact that, generally, an empty MACRO gives no visual information about it. + // To adopt this behavior, just remove the comment to the following instruction + // return do_drop(special); } } // end of defined MACRO - logger.debug("gdelete_prev_macro: bye..."); } -void -TPushParser::gdelete_prev() +std::string +TPushParser::drop_prev(bool special) { - // if in this function, the prev of cursor does exist, also the parent and we want a graphical deleting. + // if in this function, the prev of cursor does exist, also the parent and we want a graphical deletion. - logger.debug("gdelete_prev: start..."); - assert(cursor.prev()); assert(cursor.parent()); TNode prev = cursor.prev(); - if (prev.is("i") || prev.is("o") || prev.is("n")) + if (prev.isT()) { - gdelete_prev_token(); + return drop_prev_token(special); } else if (prev.isSp() || prev.isSb()) { - gdelete_prev_script(); + return drop_prev_script(special); } else if (prev.isG()) { - gdelete_prev_group(); + return drop_prev_group(special); } else if (prev.isC()) { - // here we also treat the case in which the MACRO is a table - gdelete_prev_macro(); + // here, we also treat the case in which the MACRO is a table + return drop_prev_macro(special); } else { // not handled. Future cases... + return ""; } - logger.debug("gdelete_prev: bye..."); } // end of method void -TPushParser::rgreplace_futher(void) +TPushParser::rgreplace_father() { - // 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()); @@ -794,29 +1084,17 @@ TPushParser::rgreplace_futher(void) parent.replace(cursor); parent = cursor.parent(); } - - if (parent.isC()) - { - // in this case we have removed a MACRO's child. - // I can assert that this MACRO accepts arguments. - assert(!frames.empty()); - Frame& frame = frames.top(); - assert(frame.pos > 0); - frame.pos--; - } } -void -TPushParser::do_gdelete_script() +std::string +TPushParser::do_drop_script(bool special) { - // If we are here, the cursor is child of a script and - // means that a prev MUST exist and that there is only an + // If we are here, the cursor is child of a script (sp or sb) and + // this means that a prev does 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. - logger.debug("do_gdelete_script: start..."); - assert(cursor.parent() && (cursor.parent().isSp() || cursor.parent().isSb())); TNode parent = cursor.parent(); @@ -827,41 +1105,74 @@ TPushParser::do_gdelete_script() cursor.remove(); if (prev.isG() /*&& !prev.hasId()*/ && (prev.size() == 0)) { - // in this case, the script's base is a group with no elements, so i think - // we have to remove the entire MACRO, replacing it with the cursor - 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 it. This controls are made - // in the method rgreplace_futher(). - if (cursor.parent().isG() && cursor.parent().hasId()) rgreplace_futher(); + // 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 had typed something like this + // $....{}^ + // or this + // $^ + // or this + // $...{^ + // + if (special && prev.hasId()) + { + // in this case, the user has typed: ...{}^ + // hence we idealy remove the ^ + parent.replace(prev); + prev.parent().append(cursor); + return ""; + } + else if (!prev.hasId()) + { + // we idealy remove the ^, but the phantom group + // has to be removed, also + prev.remove(); + parent.replace(cursor); + return ""; + } + else + { + prev.remove(); + parent.replace(cursor); + + // since the script had no children, we can try to remove something else. + // Since we don't know who is cursor's parent, and who is cursor's preceding + // element, we invoke the do_drop() + return do_drop(special); + } } else { - // in this case, the prev has to replace the script, - // and the curosor has to be placed after the prev. - assert(prev.hasId()); + // in this case, the prev has to replace the script. parent.replace(prev); prev.parent().append(cursor); - // now prev should have a preceding element + // now prev have a preceding element assert(cursor.parent().size() > 1); + + if (special) return ""; + else + { + // to give a standard behavior, we try to remove the element, which was + // the script's base. + return do_drop(special); + } } - logger.debug("do_gdelete_script: bye..."); - -} // end of method do_gdelet_script +} // end of method do_drop_script -void -TPushParser::do_gdelete_macro() +std::string +TPushParser::do_drop_macro(bool special) { // 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 was waiting for a non delimited argument, so + // 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(); + + // this string is useful iff we have a special deletion. + std::string macro_name = parent.nameC(); assert(!frames.empty()); Frame& frame = frames.top(); @@ -869,69 +1180,72 @@ TPushParser::do_gdelete_macro() // we have to take different actions, based on if a preceding element exists // or not - logger.debug("do_gdelete_macro:"); TNode prev = cursor.prev(); if (!prev) { // 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) assert(frame.pos == 0); + parent.replace(cursor); frames.pop(); - // if the new parent is a group with Id, and has no elements other than the - // cursor, we could remove it...but i'm not sure - if (cursor.parent() && cursor.parent().isG() && cursor.parent().hasId()) - rgreplace_futher(); - else if (cursor.parent().isC()) + if (special) return "\\" + macro_name; + else { - // We have assumed that a MACRO cannot be a MACRO's child. - // At the moment, this assumption is valid, but in a future - // it could be false. - assert(!frames.empty()); - Frame& frame = frames.top(); - assert(frame.pos > 0); - frame.pos--; + // Since the macro had no children and this is a graphical deletion, we try + // to remove something else + return do_drop(special); } } else { - // we have to control if prev is a delimited argument or not. + // 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); - logger.debug("do_gdelete_macro: the pos of the frame is: "); - cout << frame.pos << endl; - logger.debug("do_gdelete_macro: the pattern size of the entry is"); - cout << frame.entry.pattern.size() << endl; - logger.debug("do_gdelete_macro: the prev param is at"); - cout << frame.entry.previousParam(frame.pos) << endl; 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; + + if (special) + { + // in this case we have to start removing the last delimimeter. + // It means that we return in a situation where the user has not entirely + // inserted the delimited argument. So, we have to decrement frame.pos of + // two units: the delimiter and the actual argument + std::string last_del = frame.entry.pattern[frame.pos - 1].value; + frame.pos = frame.pos - 2; + return "\\" + last_del; + } + else + { + // 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; + + // since it's a graphical deletion, we have to remove the current preceding element. + // We don't invoke the drop_prev(), because a do_drop_phantom_group is more general. + return do_drop_phantom_group(special); + } } else { // the prev is not a delimited argument, so we have to try to remove it. - // I told "try" because the prev can be a group or something that - // a simple delete cannot remove completely - gdelete_prev(); + // We "try", because the prev might be something that + // a simple deletion cannot remove completely + return drop_prev(special); } } - logger.debug("do_gdelete_macro: bye..."); } -void -TPushParser::do_gdelete_groupId() +std::string +TPushParser::do_drop_groupId(bool special) { // if we are here, the cursor's parent is a group with Id assert(cursor.parent() && cursor.parent().isG() && cursor.parent().hasId()); @@ -942,35 +1256,47 @@ TPushParser::do_gdelete_groupId() TNode prev = cursor.prev(); if (prev) { - // the cursor has a preceding element, so we try to remoev it - gdelete_prev(); - - // We control if the group has to be removed, because the cursor - // could be the only element of the group. - if ((parent.first() == cursor) && parent.isG() && parent.hasId()) - rgreplace_futher(); + // the cursor has a preceding element, so we try to remove it + if (special) return drop_prev(special); + else + { + std::string str = drop_prev(special); + + // We control if the group has to be removed, because the cursor + // might be the only element of the group. + // But we have to be careful, because drop_prev could change the TML tree + // more than we think...parent could no longer exist! + parent = cursor.parent(); + if ((parent.first() == cursor) && parent.isG() && parent.hasId()) + rgreplace_father(); + return str; + } } else { // the cursor has no preceding elements, so we have to remove the // group. - rgreplace_futher(); - - // i have to re-start the process, because it' a graphical delete - do_gdelete(); + if (special) + { + parent.replace(cursor); + return ""; + } + else + { + rgreplace_father(); + // we have to re-start the process, because it' a graphical deletion + return do_drop(special); + } } - logger.debug("do_gdelete_groupId: bye..."); - -} // end of method do_gdelete_groupId() +} // end of method do_drop_groupId() -void -TPushParser::do_gdelete_phantom_group() +std::string +TPushParser::do_drop_phantom_group(bool special) { // if we are here, the cursor MUST be a child of a // phantom group. - logger.debug("do_gdelete_phantom_group: start"); assert(cursor.parent() && cursor.parent().isG() && !cursor.parent().hasId()); TNode parent = cursor.parent(); @@ -979,107 +1305,170 @@ TPushParser::do_gdelete_phantom_group() TNode prev = cursor.prev(); if (prev) { + if (parent.parent() && parent.parent().isC()) + { + // there is a frame in the stack + assert(!frames.empty()); + if (frames.top().entry.pattern.size()) + { + Frame& frame = frames.top(); + if (special) + { + // we are in a delimited argument. If the user has inserted a proper subset of the + // delimiters'sequence, we start to remove the previous delimiter. Start to remove + // a delimiter means that that delimiter must be removed from the count of inserted delimiters. + // It means that we have to decrement the member pos. + if (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) + { + std::string del = frame.entry.pattern[frame.pos].value; + frame.pos--; + return "\\" + del; + } + } + else + { + // we are in a delimited argument. If the user has inserted a proper subset of the delimiters'sequence, + // we have to remove the portion the user has inserted. + while (frame.entry.pattern[frame.pos].category != TToken::PARAMETER) frame.pos--; + } + } + } + // the cursor has a preceding element, so we try to remove it - logger.debug("do_gdelete_phantom_group: i have to call the gdelete_prev()"); - gdelete_prev(); - logger.debug("do_gdelete_phantom_group: gdelete_prev terminated"); + std::string str = drop_prev(special); - if (parent.size() == 1 && parent.parent().isSp()) + if (special) return str; + else { - logger.debug("do_gdelete_phantom_group: i have to remove a phantom group which is a child of a sp"); - // in this case the gdelete_prev has removed the only element preceding the cursor. - // If the phantom group is a sp's child, it means that the user has removed all \' in the - // phantom group. I think, we can remove the phamtom group and the sp element. - cursor.remove(); - parent.replace(cursor); - // now we have a sp element with two children: the first child (we don't know nothing about it) - // and the cursror. - assert(cursor.parent().size() == 2); + // now we have to control the parent, to handle the case of primes. But we have returned from a drop_prev(), which + // could change the TML tree. So not asssuming that cursor's parent is unchanged is convenient. + parent = cursor.parent(); + if (parent.isG() && !parent.hasId() && (parent.size() == 1) && parent.parent().isSp()) + { + // in this case the drop_prev has removed the only element preceding the cursor. + // Since the phantom group is an sp's child, the user has removed all \' in the + // phantom group. + // Now we have some possibilities: + // - we can replace the phantom group with the cursor, giving the user the chance to insert a new + // exponent + // - we can remove the phantom group and the sp element, recreating the state before the user inserted the first + // prime. + // At the moment we implement the second one. + assert(parent.parent().size() == 2); + TNode gparent = parent.parent(); + TNode base = gparent.first(); + cursor.remove(); + parent.remove(); + gparent.replace(base); + // now base's parent is no more gparent + base.parent().append(cursor); - // to delete the script we can invoke the do_gdelete_script(), which will do all controls we need - do_gdelete_script(); - } - else if (parent.parent().isSp()) - { - // in this case we have to place the cursor after the sp element - logger.debug("do_gdelete_phantom_group: the sequence of \' is not terminated, so i place the cursor after the sp element"); - cursor.remove(); - assert(parent.parent().parent()); - parent.parent().parent().append(cursor); + return str; + } + else if (parent.isG() && !parent.hasId() && 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); + return str; + } + else return str; } } 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 futher of the cursor. - TNode gfuther = parent.parent(); - if (!gfuther) + // 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"); + logger.error("TML tree in a inconsistent state"); + return ""; } - else if (gfuther.isC()) + else if (gfather.isC()) { // in this case the phantom group is child of a MACRO. // We have to control the nature of this MACRO. - logger.debug("do_gdelete_phantom_group: i have to remove a phantom group which is a child of a MACRO"); assert(!frames.empty()); Frame& frame = frames.top(); + + // this variable is useful in a special deletion + std::string macro_name = gfather.nameC(); 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 - logger.debug("do_gdelete_phantom_group: the MACRO is leftOpen and rigthOpen"); - assert(gfuther.size() == 2); - assert((gfuther.last() == parent) && (gfuther.first().isG() && !gfuther.first().hasId())); + // 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 groups + assert(gfather.size() == 2); + assert((gfather.last() == parent) && (gfather.first().isG() && !gfather.first().hasId())); assert(frame.pos == 0); - TNode ggfuther = gfuther.parent(); - assert(ggfuther); + TNode ggfather = gfather.parent(); + assert(ggfather); cursor.remove(); parent.remove(); - // i have to replace the gfuther with the elements of its first child - gfuther.replace(gfuther.first().first(), TNode()); - logger.debug("do_gdelete_phantom_group: i have removed the control element, and replaced it with its first child"); - ggfuther.append(cursor); - logger.debug("do_gdelete_phantom_group: cursor appended to the grand grand futher"); - // now we have the situation preceding the insertion of the MACRO leftOpen and rightOpen + // we have to replace the gfather with the elements of its first child, but this group may have no + // children. + if (gfather.first().size()) + { + gfather.replace(gfather.first().first(), TNode()); + ggfather.append(cursor); + } + else + { + // in this case, the MACRO has to be replaced with the cursor + gfather.first().remove(); + gfather.replace(cursor); + } + // now we have the situation preceding the insertion of the leftOpen and rightOpen MACRO. // this MACRO no longer exists. frames.pop(); + + if (special) return "\\" + macro_name; + else + { + // to give a standard behavior to the graphical deletion, we call the do_drop. + return do_drop(special); + } } 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. + // 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 - logger.debug("do_gdelete_phantom_group: the MACRO is rightOpen only"); - assert(gfuther.size() == 1); + assert(gfather.size() == 1); assert(frame.pos == 0); cursor.remove(); parent.remove(); - gfuther.replace(cursor); + gfather.replace(cursor); - // now we have the situation preceding the MACRO rightOpen, so i have to pop the frame + // now we have the situation preceding the rightOpen MACRO, so we have to pop the frame frames.pop(); + + if (special) return "\\" + macro_name; + else + { + // to give a standard behavior to the graphical deletion, we call the do_drop. + return do_drop(special); + } + } else if (frame.entry.leftOpen) { - // i think this situation will never occur - // but it can be recovered in some way + // this situation will never occur. logger.error("the parser has generated a wrong TML tree"); + return ""; } else if (!frame.entry.pattern.empty()) { // the MACRO accepts arguments, and the phantom group in which - // the cursor is, rappresents a delimited argument + // the cursor is, rappresents a delimited argument. // We have to control if the cursor's parent has a preceding element, // or not. - logger.debug("do_gdelete_phantom_group: the MACRO accepts arguments"); - logger.debug("do_gdelete_phantom_group: entry pattern has size:"); - cout << frame.entry.pattern.size() << endl; TNode uncle = parent.prev(); if (!uncle) { @@ -1089,8 +1478,16 @@ TPushParser::do_gdelete_phantom_group() assert(frame.pos == 0); cursor.remove(); parent.remove(); - gfuther.replace(cursor); + gfather.replace(cursor); frames.pop(); + + if (special) return "\\" + macro_name; + else + { + // once we have replaced the empty macro with the cursor, we can remove + // something else + return do_drop(special); + } } else { @@ -1098,91 +1495,110 @@ TPushParser::do_gdelete_phantom_group() // 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 sequrnce 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's uncle is a delimited argument cursor.remove(); parent.remove(); uncle.append(cursor); + if (special) + { + // we have to start removing the last delimiter of the delimited + // argument. + std::string last_del = frame.entry.pattern[frame.pos - 1].value; + frame.pos = frame.pos - 2; + return "\\" + last_del; + } + else + { + // 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; + + // once removed the sequnce of delimiters, we can start to remove the actual + // parameter. We can call the do_drop_phantom_group() because a delimited argument + // is always a phantom group's child + return do_drop_phantom_group(special); + } } 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) + parent = cursor.parent(); // we update the parent (it should be the MACRO) assert(parent.isC()); - // now i try to remove the uncle (now it' the preceding element) - gdelete_prev(); + // now we try to remove the uncle (now it' the preceding element) + return drop_prev(special); } } // this is the else's end, that handles the case in which an uncle exists } // end of if (!frame.entry.pattern.empty()) else { // the entry has no arguments, is not rightOpen and is not leftOpen. - logger.warning("do_gdelete_phantom_group: TML tree in a strange state"); - if (gfuther.size() == 1) - { - cursor.remove(); - parent.remove(); - gfuther.replace(cursor); - } - else - { - logger.debug("do_gdelete_phantom_group: TML tree in a very strange state"); - cursor.remove(); - gfuther.replace(cursor); - } + logger.error("TML tree in a strange state"); + return ""; } - } // end of if (gfuther.isC()) - else if (gfuther.is("cell")) + } // end of if (gfather.isC()) + else if (gfather.is("cell")) { - // we have to handle the case where cursor'grand futher is a cell element. - // The tables are control sequece, so there is a frame in the stack + // 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().pos == 0); 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(); + // 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()); + + // this variable is useful to handle the special deletion + std::string table_name = gfather.parent().parent().nameC(); + + TNode row = gfather.parent(); // in this case the cell has no element, so the user wants to delete this cell. - TNode prev_cell = gfuther.prev(); + TNode prev_cell = gfather.prev(); cursor.remove(); parent.remove(); - gfuther.remove(); + gfather.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. + // in this case, the cell was 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. - // I think have to delete the entire table - assert(table.parent()); - TNode parent_table = table.parent(); - table.remove(); - frames.pop(); - parent_table.append(cursor); + if (special) + { + // Since there was a cell (and a row), the user has typed a "{" to + // We ideally remove this character. + table.append(cursor); + return ""; + } + else + { + // 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); + return ""; + } } else { - // there are other rows (one at least) + // there are other rows (one or more) assert(prev_row.is("row")); assert(prev_row.last()); TNode last_cell = prev_row.last(); @@ -1190,6 +1606,10 @@ TPushParser::do_gdelete_phantom_group() assert(last_cell.size() == 1); assert(last_cell.first().isG() && !last_cell.first().hasId()); last_cell.first().append(cursor); + // Since cells and rows are separated by spaces and CRs + // (and the user can see this spaces and CRs), a special deletion + // is equivalent to a normal deletion + return ""; } } // end of if (!prev_cell) else @@ -1199,47 +1619,50 @@ TPushParser::do_gdelete_phantom_group() assert(prev_cell.size() == 1); assert(prev_cell.first().isG() && !prev_cell.first().hasId()); prev_cell.first().append(cursor); + return ""; } - } // end of if (gfuther.is("cell")) - else if (gfuther.isSp()) + } // end of if (gfather.is("cell")) + else if (gfather.isSp()) { - // in this case, the user pushed 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 choice: - // delete the phantom group; - // delete the superscript. + // we cannot be here because a phantom group can be a Sp child only + // in two cases. If the user has typed somethong like: + // $^ + // the cursor is not phantom group's child. + // If the user has typed somethong like + // ..'' + // In this case the sequence of ' is placed in a phantom group, + // which becomes the exponent of the script. But, the cursor is + // always outside the phantom group + logger.error("TML tree in a strange state"); + return ""; + } + else if (gfather.is("math")) + { + // in this case we ignore the user's will of deleting + // but we could also decide to remove the math mode. + logger.warning("nothing to delete"); + return ""; } else { - // cursor' grand futher is undefined - logger.error("do_gdelete_phantom_group: TML tree in a unknown state"); + // cursor's grand father is undefined + logger.error("TML tree is in an unknown state"); + return ""; } } // end of the else of the if (prev) - if (!frames.empty()) - { - logger.debug("do_gdelete_phantom_group: the stack is not empty"); - logger.debug("do_gdelte_phanto_group: is the top element rightOpen?"); - cout << frames.top().entry.rightOpen << endl; - logger.debug("do_gdelte_phanto_group: is the top element'pattern empty?"); - cout << frames.top().entry.pattern.empty() << endl; - } - logger.debug("do_gdelete_phantom_group: bye..."); } -void -TPushParser::do_gdelete() +std::string +TPushParser::do_drop(bool special) { - logger.debug("do_gdelete: start"); // 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"); + return ""; } else { @@ -1248,633 +1671,282 @@ TPushParser::do_gdelete() TNode parent = cursor.parent(); if (parent.is("math")) { - // we ca do two thing...remove the math mode (it implies controlling the display attribute), do nothing + // 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"); + logger.warning("nothing to delete"); + return ""; } else if (parent.isG()) { // the cursor's parent is a group. We have to control if it's a phantom group or not - logger.debug("do_gdelete: the cursor's parent is a group"); if (parent.hasId()) { - logger.debug("do_gdelete: which has Id"); - do_gdelete_groupId(); + return do_drop_groupId(special); } else { - logger.debug("do_gdelete: which is a phantom group"); - do_gdelete_phantom_group(); + return do_drop_phantom_group(special); } } // end of parent is group else if (parent.isC()) { - cout << "the cursor's parent is a MACRO" << endl; - do_gdelete_macro(); + return do_drop_macro(special); } // end of parent is a MACRO else if (parent.isSp() || parent.isSb()) { - cout << "the cursor's parent is a script" << endl; - do_gdelete_script(); + return do_drop_script(special); } // end of parent is sp or sb } // end of the else which consider the case in which parent exists - if (!cursor.parent()) - logger.debug("do_gdelete: the cursro has no parent -> paolo is stupid"); +} // end of method do_drop - if (!frames.empty()) - { - logger.debug("do_gdelete: the stack is not empty"); - logger.debug("do_gdelete: is the top element rightOpen?"); - cout << frames.top().entry.rightOpen << endl; - logger.debug("do_gdelete: is the top element'pattern empty"); - cout << frames.top().entry.pattern.empty() << endl; - } - logger.debug("do_gdelete: bye..."); - -} // end of method do_gdelete - -/* -void -TPushParser::do_gdelete() -{ - // this function operates a graphical deleting - - //if (!frames.empty()) - // cout << "do_gdelete: c'e' un frame aperto e il suo pos vale: " << frames.top().pos << endl; - - TNode parent = cursor.parent(); - - // if no parent, do nothing - if (parent) - { - 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 which is a child of a MACRO" << endl; - Frame& frame = frames.top(); - cout << frame.entry.leftOpen << frame.entry.rightOpen << endl; - 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 - cout << "this is the assert that fails" << endl; - 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(); - - // i have to re-start the process, because it' a graphical delete - do_gdelete(); - } - } - } - else if (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 - assert(!frames.empty()); - Frame& frame = frames.top(); - - assert(frame.entry.pattern.size() >= 1); - - cout << "do_gdelete: frames.top().pos = " << frames.top().pos << endl; - - TNode prev = cursor.prev(); - - if (!prev) - { - // in this case we can replace the MACRO with the cursor - // and pop the stack and we can assert that frame.pos == 0 - assert(frame.pos == 0); - cursor.remove(); // it should not be necessary, but i'm not shure - parent.replace(cursor); - frames.pop(); - } - 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()) - { - // 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; - } - else - { - // the prev is an non delimited argument, so we try to - // remove it. - gdelete_prev(); - } - } - } - else if (parent.is("sp") || parent.is("sb")) - { - // 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()); - - TNode prev = cursor.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); - - if (cursor.parent().isG() && cursor.parent().hasId()) rgreplace_futher(); - } - else - { - assert(prev.hasId()); - parent.replace(prev); - prev.parent().append(cursor); - // now prev should have a preceding element - assert(cursor.parent().size() > 1); - } - } - else - { - // not handled: no control for tables, ... - } - } - 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 -*/ - -void +bool TPushParser::process(const TToken& token) { switch (token.category) { - case TToken::BEGIN: do_begin(); break; - case TToken::END: do_end(); break; - case TToken::SHIFT: do_shift(); break; - case TToken::ALIGN: do_align(); break; - case TToken::EOL: do_eol(); break; - case TToken::PARAMETER: do_parameter(token.value); break; - case TToken::SUPERSCRIPT: do_superscript(); break; - case TToken::SUBSCRIPT: do_subscript(); break; - case TToken::SPACE: do_space(token.value); break; - case TToken::LETTER: do_letter(token.value); break; - case TToken::DIGIT: do_digit(token.value); break; - case TToken::OTHER: do_other(token.value); break; - 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; + case TToken::BEGIN: return do_begin(); + case TToken::END: return do_end(); + case TToken::SHIFT: return do_shift(); + case TToken::ALIGN: return do_align(); + case TToken::EOL: return do_eol(); + case TToken::PARAMETER: return do_parameter(token.value); + case TToken::SUPERSCRIPT: return do_superscript(); + case TToken::SUBSCRIPT: return do_subscript(); + case TToken::IGNORABLE_SPACE: return do_ignorablespace(token.value); + case TToken::SPACE: return do_space(token.value); + case TToken::LETTER: return do_letter(token.value); + case TToken::DIGIT: return do_digit(token.value); + case TToken::OTHER: return do_other(token.value); + case TToken::ACTIVE: return do_active(token.value); + case TToken::COMMENT: return do_comment(); + case TToken::CONTROL: return do_control(token.value); } } void TPushParser::push(const TToken& token) { - logger.debug("TPushParser::push " + token.value + " (cat: "); - cout << token.category << ")" << endl; - - if (token.category == TToken::GDELETE) - { - logger.debug("push: i have to process a token with category member = GDELETE"); - process(token); - } - else - { + 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 - logger.debug("push: i get the cursor's parent"); - 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 - logger.debug("push: cursor'parent is a phantom group, and this group has a MACRO as parent"); - logger.debug("push: i assert that !frames.empty()"); - assert(!frames.empty()); - logger.debug("push: this assert is OK"); - logger.debug("push: now i take the top element of the stack"); - Frame& frame = frames.top(); - logger.debug("push: now i've got the top element"); - 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 - logger.debug("push: the frame.entry accepts arguments"); - 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 - frame.pos++; + // The token matches with a delimiter of the argument, + // hence 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.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); - } - } - else - { - // 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 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; - } - else - { - // in this case, the sequence of delimiters is composed of one - // delimiter. It means that we have to process the token - process(token); - } - } + if (frame.entry.lastDelimiter(frame.pos)) + { + // this delimiter is the last one for the argumet, + // so the argument is completed + cursor.remove(); + advance(parent); + } } 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... - logger.debug("push: the frame.entry has no arguments, so i assert that i's rightOpne"); - assert(frame.entry.rightOpen); - logger.debug("push: this assert is OK, now i process the token"); - process(token); - logger.debug("push: the token has been processed"); - } - } - 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 - logger.debug("TPushParser.push: we should be here"); - 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 has 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.warning("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) && (token.category != TToken::IGNORABLE_SPACE)) process(token); + } + 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)) { - // 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); + // 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 { - // There is a mismatch. Emit an error and ignore the token? - cerr << "ERROR: token ignored: " << token.value << " (cat: " << token.category << ")" << endl; + cursor.remove(); + advance(parent); } - - } - else - process(token); - else - { - cout << "ignored token" << endl; - } - - } // this end corresponds to the else of the if (token.category == TToken::GDELETE) - - if (factory) factory->documentModified(doc); - - if (frames.empty()) cout << "stack vuoto" << endl; + } + else + { + // There is a mismatch. Emit an error and ignore the token? + logger.warning("ignored token: " + token.value); + } + } + else + process(token); else { - logger.debug("push: the stack is not empty"); - logger.debug("push: is the top element rightOpen?"); - cout << frames.top().entry.rightOpen << endl; - logger.debug("push: is the pattern empty"); - cout << frames.top().entry.pattern.empty() << endl; + logger.warning("ignored token"); } + + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); +} + +std::string +TPushParser::drop(bool special) +{ + std::string str = do_drop(special); + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return str; } void TPushParser::advance(const TNode& node) { assert(node); - TNode parent = node.parent(); - if (!parent) - ; // nothing to do, the cursor is not in the document any more - else if (parent.isG()) + + if (!node.parent()) + { + // this is an error + logger.error("wrong TML tree"); + } + else if (node.parent().isG()) { TNode next = node.next(); if (next) next.insert(cursor); - else parent.append(cursor); + else node.parent().append(cursor); } - else if (parent.isC()) + else if (node.parent().isC()) { - if (node.next()) - ; // cursor removed - else - { - Frame& frame = frames.top(); - if (frame.pos == frame.entry.pattern.size()) + assert(!frames.empty()); + if ((frames.top().pos + 1 == frames.top().entry.pattern.size()) || (frames.top().entry.pattern.empty())) + { + // we are here when we have a right open macro, or the inserted element is the last one + if (frames.top().entry.rightOpen) { + // we have to remove the frame from the stack frames.pop(); - advance(parent); + advance(node.parent().parent()); } else - parent.append(cursor); + { + frames.pop(); + advance(node.parent()); + } + } + else if (frames.top().entry.paramDelimited(frames.top().pos + 1)) + { + // the next argument is delimited, so we have to create a phantom group + TNode g = doc.createG(); + g.append(cursor); + node.parent().append(g); + frames.top().pos++; + } + else + { + // the next argumet is not delimited, so we have to append the cursor + // to the MACRO + node.parent().append(cursor); + frames.top().pos++; } } - else if (parent.is("math")) - ; // we are done - else - advance(parent); + else advance(node.parent()); } 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::hideCursor() +{ + if (hiddenCursor++ == 0) + { + cursor["visible"] = "0"; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return true; + } + else + return false; +} + +bool +TPushParser::showCursor() +{ + if (hiddenCursor > 0 && --hiddenCursor == 0) + { + cursor["visible"] = "1"; + if (factory && doc.dirtyNode() && !frozen()) factory->documentModified(doc); + return true; + } + else + return false; +} + +bool +TPushParser::thaw() +{ + if (APushParser::thaw() && factory && doc.dirtyNode()) + { + factory->documentModified(doc); + return true; + } + else + return false; }