diff --git a/.gitignore b/.gitignore index bcc8bea..b7231b1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ Dockerfile Makefile .qmake.stash SyntaxTutor +.cache diff --git a/src/backend/grammar_factory.cpp b/src/backend/grammar_factory.cpp index ea34e83..c4f490e 100644 --- a/src/backend/grammar_factory.cpp +++ b/src/backend/grammar_factory.cpp @@ -255,7 +255,7 @@ bool GrammarFactory::HasUnreachableSymbols(Grammar& grammar) const { if (it != grammar.g_.end()) { for (const auto& production : it->second) { for (const auto& symbol : production) { - if (!grammar.st_.IsTerminal(symbol) && + if (grammar.st_.IsNonTerminal(symbol) && !reachable.contains(symbol)) { reachable.insert(symbol); pending.push(symbol); @@ -283,7 +283,7 @@ bool GrammarFactory::IsInfinite(Grammar& grammar) const { for (const auto& prod : productions) { bool all_generating = true; for (const auto& symbol : prod) { - if (!grammar.st_.IsTerminal(symbol) && + if (grammar.st_.IsNonTerminal(symbol) && !generating.contains(symbol)) { all_generating = false; break; @@ -321,11 +321,11 @@ bool GrammarFactory::HasIndirectLeftRecursion(const Grammar& grammar) const { for (const auto& [nt, productions] : grammar.g_) { graph[nt] = {}; for (const production& prod : productions) { - if (!grammar.st_.IsTerminal(prod[0])) { + if (grammar.st_.IsNonTerminal(prod[0])) { graph[nt].insert(prod[0]); } for (size_t i = 1; i < prod.size(); ++i) { - if (grammar.st_.IsTerminal(prod[i])) { + if (!grammar.st_.IsNonTerminal(prod[i])) { break; } graph[nt].insert(prod[i]); @@ -576,7 +576,7 @@ void GrammarFactory::NormalizeNonTerminals(FactoryItem& item, for (auto& [old_nt, prods] : item.g_) { for (auto& prod : prods) { for (auto& symbol : prod) { - if (!item.st_.IsTerminal(symbol)) { + if (item.st_.IsNonTerminal(symbol)) { symbol = nt; } } diff --git a/src/backend/ll1_parser.cpp b/src/backend/ll1_parser.cpp index b8fd62b..aa0aa62 100644 --- a/src/backend/ll1_parser.cpp +++ b/src/backend/ll1_parser.cpp @@ -144,7 +144,7 @@ void LL1Parser::ComputeFollowSets() { for (const production& rhs : rule.second) { for (size_t i = 0; i < rhs.size(); ++i) { const std::string& symbol = rhs[i]; - if (!gr_.st_.IsTerminal(symbol)) { + if (gr_.st_.IsNonTerminal(symbol)) { std::unordered_set first_remaining; if (i + 1 < rhs.size()) { @@ -201,4 +201,4 @@ LL1Parser::PredictionSymbols(const std::string& antecedent, hd.erase(gr_.st_.EPSILON_); hd.merge(Follow(antecedent)); return hd; -} \ No newline at end of file +} diff --git a/src/backend/slr1_parser.cpp b/src/backend/slr1_parser.cpp index 6a235ac..a3bc7d5 100644 --- a/src/backend/slr1_parser.cpp +++ b/src/backend/slr1_parser.cpp @@ -193,7 +193,7 @@ void SLR1Parser::ClosureUtil(std::unordered_set& items, if (next == gr_.st_.EPSILON_) { continue; } - if (!gr_.st_.IsTerminal(next) && + if (gr_.st_.IsNonTerminal(next) && std::find(visited.cbegin(), visited.cend(), next) == visited.cend()) { const std::vector& rules = gr_.g_.at(next); @@ -328,7 +328,7 @@ void SLR1Parser::ComputeFollowSets() { for (const production& rhs : rule.second) { for (size_t i = 0; i < rhs.size(); ++i) { const std::string& symbol = rhs[i]; - if (!gr_.st_.IsTerminal(symbol)) { + if (gr_.st_.IsNonTerminal(symbol)) { std::unordered_set first_remaining; if (i + 1 < rhs.size()) { diff --git a/src/backend/symbol_table.cpp b/src/backend/symbol_table.cpp index 179f35c..48379a0 100644 --- a/src/backend/symbol_table.cpp +++ b/src/backend/symbol_table.cpp @@ -21,11 +21,32 @@ #include void SymbolTable::PutSymbol(const std::string& identifier, bool isTerminal) { + if (identifier == EPSILON_) { + st_[identifier] = symbol_type::META; + meta_symbols_.insert(identifier); + terminals_.erase(identifier); + terminals_wtho_eol_.erase(identifier); + non_terminals_.erase(identifier); + return; + } + + if (identifier == EOL_) { + st_[identifier] = symbol_type::TERMINAL; + terminals_.insert(identifier); + terminals_wtho_eol_.erase(identifier); + non_terminals_.erase(identifier); + meta_symbols_.erase(identifier); + return; + } + + if (st_.contains(identifier)) { + return; + } + if (isTerminal) { st_.insert({identifier, symbol_type::TERMINAL}); terminals_.insert(identifier); terminals_wtho_eol_.insert(identifier); - } else { st_.insert({identifier, symbol_type::NO_TERMINAL}); non_terminals_.insert(identifier); @@ -37,9 +58,20 @@ bool SymbolTable::In(const std::string& s) const { } bool SymbolTable::IsTerminal(const std::string& s) const { - return terminals_.contains(s); + auto it = st_.find(s); + return it != st_.end() && it->second == symbol_type::TERMINAL; } bool SymbolTable::IsTerminalWthoEol(const std::string& s) const { - return s != EPSILON_ && terminals_.contains(s); + return terminals_wtho_eol_.contains(s); +} + +bool SymbolTable::IsNonTerminal(const std::string& s) const { + auto it = st_.find(s); + return it != st_.end() && it->second == symbol_type::NO_TERMINAL; +} + +bool SymbolTable::IsMeta(const std::string& s) const { + auto it = st_.find(s); + return it != st_.end() && it->second == symbol_type::META; } diff --git a/src/backend/symbol_table.hpp b/src/backend/symbol_table.hpp index 9df81cf..99710ef 100644 --- a/src/backend/symbol_table.hpp +++ b/src/backend/symbol_table.hpp @@ -27,10 +27,10 @@ * @enum symbol_type * @brief Represents the type of a grammar symbol. * - * This enum distinguishes between terminal and non-terminal symbols + * This enum distinguishes between terminal, non-terminal, and meta symbols * within the grammar and the symbol table. */ -enum class symbol_type { NO_TERMINAL, TERMINAL }; +enum class symbol_type { NO_TERMINAL, TERMINAL, META }; /** * @struct SymbolTable @@ -38,9 +38,9 @@ enum class symbol_type { NO_TERMINAL, TERMINAL }; * special markers. * * This structure holds information about all terminals and non-terminals used - * in a grammar, as well as special symbols such as EPSILON and the end-of-line - * marker ($). It supports symbol classification, membership checks, and - * filtered views such as terminals excluding $. + * in a grammar, as well as special symbols such as EPSILON (meta symbol) and + * the end-of-line marker ($). It supports symbol classification, membership + * checks, and filtered views such as terminals excluding $. */ struct SymbolTable { /// @brief End-of-line symbol used in parsing, initialized as "$". @@ -50,10 +50,9 @@ struct SymbolTable { /// "EPSILON". std::string EPSILON_{"EPSILON"}; - /// @brief Main symbol table, mapping identifiers to a pair of symbol type - /// and its regex. + /// @brief Main symbol table, mapping identifiers to their symbol type. std::unordered_map st_{ - {EOL_, symbol_type::TERMINAL}, {EPSILON_, symbol_type::TERMINAL}}; + {EOL_, symbol_type::TERMINAL}, {EPSILON_, symbol_type::META}}; /** * @brief Set of all terminal symbols (including EOL). @@ -71,10 +70,15 @@ struct SymbolTable { std::unordered_set non_terminals_; /** - * @brief Adds a non-terminal symbol to the symbol table. + * @brief Set of meta symbols (e.g., EPSILON). + */ + std::unordered_set meta_symbols_{EPSILON_}; + + /** + * @brief Adds a symbol to the symbol table. * - * @param identifier Name of the symbol. - * @param isTerminal True if the identifier is a terminal symbol + * @param identifier Name of the symbol. + * @param isTerminal True if the identifier is a terminal symbol. */ void PutSymbol(const std::string& identifier, bool isTerminal); @@ -101,4 +105,20 @@ struct SymbolTable { * @return true if the symbol is terminal, otherwise false. */ bool IsTerminalWthoEol(const std::string& s) const; + + /** + * @brief Checks if a symbol is a non-terminal. + * + * @param s Symbol identifier to check. + * @return true if the symbol is non-terminal, otherwise false. + */ + bool IsNonTerminal(const std::string& s) const; + + /** + * @brief Checks if a symbol is a meta symbol (e.g., EPSILON). + * + * @param s Symbol identifier to check. + * @return true if the symbol is meta, otherwise false. + */ + bool IsMeta(const std::string& s) const; }; diff --git a/src/backend/tests.cpp b/src/backend/tests.cpp index 034e02f..0f1a190 100644 --- a/src/backend/tests.cpp +++ b/src/backend/tests.cpp @@ -68,7 +68,7 @@ TEST(GrammarTest, GrammarWithEpsilon) { ASSERT_EQ(gr.axiom_, "S"); ASSERT_EQ(gr.g_, expected); - ASSERT_TRUE(gr.st_.terminals_.contains(eps)); + ASSERT_TRUE(gr.st_.IsMeta(eps)); ASSERT_TRUE(gr.st_.non_terminals_.contains("A")); } @@ -145,7 +145,7 @@ TEST(GrammarTest, ComplexGrammarWithEpsilonAndRecursion) { ASSERT_TRUE(gr.st_.terminals_.contains("a")); ASSERT_TRUE(gr.st_.terminals_.contains("b")); ASSERT_TRUE(gr.st_.terminals_.contains("c")); - ASSERT_TRUE(gr.st_.terminals_.contains(eps)); + ASSERT_TRUE(gr.st_.IsMeta(eps)); ASSERT_TRUE(gr.st_.terminals_.contains("$")); ASSERT_TRUE(gr.st_.non_terminals_.contains("A")); ASSERT_TRUE(gr.st_.non_terminals_.contains("B")); @@ -435,28 +435,6 @@ TEST(GrammarFactoryTest, GeneratedLv5LL1GrammarIsAlwaysLL1) { } } -TEST(GrammarFactoryTest, GeneratedLv6LL1GrammarIsAlwaysLL1) { - GrammarFactory factory; - - factory.Init(); - for (int i = 0; i < 10; ++i) { - Grammar g = factory.GenLL1Grammar(6); - LL1Parser ll1(g); - ASSERT_TRUE(ll1.CreateLL1Table()); - } -} - -TEST(GrammarFactoryTest, GeneratedLv7LL1GrammarIsAlwaysLL1) { - GrammarFactory factory; - - factory.Init(); - for (int i = 0; i < 3; ++i) { - Grammar g = factory.GenLL1Grammar(7); - LL1Parser ll1(g); - ASSERT_TRUE(ll1.CreateLL1Table()); - } -} - TEST(GrammarFactoryTest, GeneratedLv1SLR1GrammarIsAlwaysSLR1) { GrammarFactory factory; @@ -512,28 +490,6 @@ TEST(GrammarFactoryTest, GeneratedLv5SLR1GrammarIsAlwaysSLR1) { } } -TEST(GrammarFactoryTest, GeneratedLv6SLR1GrammarIsAlwaysSLR1) { - GrammarFactory factory; - - factory.Init(); - for (int i = 0; i < 5; ++i) { - Grammar g = factory.GenSLR1Grammar(6); - SLR1Parser slr1(g); - ASSERT_TRUE(slr1.MakeParser()); - } -} - -TEST(GrammarFactoryTest, GeneratedLv7SLR1GrammarIsAlwaysSLR1) { - GrammarFactory factory; - - factory.Init(); - for (int i = 0; i < 5; ++i) { - Grammar g = factory.GenSLR1Grammar(7); - SLR1Parser slr1(g); - ASSERT_TRUE(slr1.MakeParser()); - } -} - TEST(GrammarFactoryTest, NormalizeNonTerminals_Basic) { GrammarFactory factory; @@ -2333,6 +2289,7 @@ TEST(LL1__Test, FollowSet2) { Grammar g; g.st_.PutSymbol("S'", false); g.st_.PutSymbol("S", false); + g.st_.PutSymbol("A", false); g.st_.PutSymbol("A'", false); g.st_.PutSymbol("B", false); g.st_.PutSymbol("C", false); @@ -2354,7 +2311,7 @@ TEST(LL1__Test, FollowSet2) { g.AddProduction("C", {"c"}); LL1Parser ll1(g); - + ASSERT_TRUE(ll1.CreateLL1Table()); std::unordered_set result; std::unordered_set expected{"b", "c", "a", g.st_.EOL_}; result = ll1.Follow("A"); @@ -3574,7 +3531,8 @@ TEST(SymbolTableTest, IsTerminalWthoEol_ExcludesEpsilon) { SymbolTable st; st.PutSymbol(st.EPSILON_, true); - EXPECT_TRUE(st.IsTerminal(st.EPSILON_)); + EXPECT_TRUE(st.IsMeta(st.EPSILON_)); + EXPECT_FALSE(st.IsTerminal(st.EPSILON_)); EXPECT_FALSE(st.IsTerminalWthoEol(st.EPSILON_)); } diff --git a/src/gui/lltutorwindow.cpp b/src/gui/lltutorwindow.cpp index 2d256b2..ec7681e 100644 --- a/src/gui/lltutorwindow.cpp +++ b/src/gui/lltutorwindow.cpp @@ -482,8 +482,8 @@ void LLTutorWindow::showTableForCPrime() { const QString& rowHeader = sortedNonTerminals[i]; for (int j = 0; j < rawTable[i].size(); ++j) { - const QString& colHeader = colHeaders[j]; - QString& cell = rawTable[i][j]; + const QString& colHeader = colHeaders[j]; + QString& cell = rawTable[i][j]; cell.remove(kWhitespace); if (cell.isEmpty()) { continue; @@ -494,7 +494,7 @@ void LLTutorWindow::showTableForCPrime() { // Split could not process the string production = {cell}; } - lltable[rowHeader][colHeader] = production; + lltable[rowHeader][colHeader] = production; } } on_confirmButton_clicked(); @@ -1204,9 +1204,7 @@ bool LLTutorWindow::verifyResponseForC() { QStringList LLTutorWindow::solutionForA() { int nt = grammar.st_.non_terminals_.size(); - int t = grammar.st_.terminals_.contains(grammar.st_.EPSILON_) - ? grammar.st_.terminals_.size() - 1 - : grammar.st_.terminals_.size(); + int t = grammar.st_.terminals_.size(); return {QString::number(nt), QString::number(t)}; } @@ -1217,9 +1215,7 @@ QString LLTutorWindow::solutionForA1() { } QString LLTutorWindow::solutionForA2() { - int t = grammar.st_.terminals_.contains(grammar.st_.EPSILON_) - ? grammar.st_.terminals_.size() - 2 - : grammar.st_.terminals_.size() - 1; + int t = grammar.st_.terminals_wtho_eol_.size(); QString solution(QString::number(t)); return solution; @@ -1363,25 +1359,15 @@ QString LLTutorWindow::feedbackForA2() { stdUnorderedSetToQSet(grammar.st_.terminals_wtho_eol_); QList l(terminals.begin(), terminals.end()); - if (ll1.gr_.st_.terminals_.contains(ll1.gr_.st_.EPSILON_)) { - l.removeOne(QString::fromStdString(ll1.gr_.st_.EPSILON_)); - return tr("Los TERMINALES son todos los símbolos que aparecen en los " - "consecuentes\n" - "y que NO son no terminales, excluyendo el símbolo de fin de " - "entrada ($). La " - "cadena EPSILON, tampoco cuenta como símbolo terminal, pues " - "es un metasímbolo " - "que representa la cadena vacía.\n" - "En esta gramática: %1") - .arg(l.join(", ")); - } else { - return tr("Los TERMINALES son todos los símbolos que aparecen en los " - "consecuentes\n" - "y que NO son no terminales, excluyendo el símbolo de fin de " - "entrada ($).\n" - "En esta gramática: %1") - .arg(l.join(", ")); - } + return tr("Los TERMINALES son todos los símbolos que aparecen en los " + "consecuentes\n" + "y que NO son no terminales, excluyendo el símbolo de fin de " + "entrada ($). La " + "cadena EPSILON tampoco cuenta como símbolo terminal, pues " + "es un metasímbolo " + "que representa la cadena vacía.\n" + "En esta gramática: %1") + .arg(l.join(", ")); } QString LLTutorWindow::feedbackForAPrime() { @@ -2317,9 +2303,7 @@ QString LLTutorWindow::TeachLL1Table() { "cada terminal excepto epsilon más %2 (%3 columnas).\n") .arg(ll1.gr_.st_.non_terminals_.size()) .arg(QString::fromStdString(ll1.gr_.st_.EOL_)) - .arg(ll1.gr_.st_.terminals_.contains(ll1.gr_.st_.EPSILON_) - ? ll1.gr_.st_.terminals_.size() - 1 - : ll1.gr_.st_.terminals_.size()); + .arg(ll1.gr_.st_.terminals_.size()); output += tr("5. Coloca α en la celda (A,β) si β ∈ SD(A → α), déjala " "vacía en otro caso.\n"); diff --git a/src/gui/slrtutorwindow.cpp b/src/gui/slrtutorwindow.cpp index 999df7e..4c7c660 100644 --- a/src/gui/slrtutorwindow.cpp +++ b/src/gui/slrtutorwindow.cpp @@ -403,10 +403,7 @@ void SLRTutorWindow::showTable() { slrtable.clear(); - const int nTerm = - slr1.gr_.st_.terminals_.contains(slr1.gr_.st_.EPSILON_) - ? slr1.gr_.st_.terminals_.size() - 1 - : slr1.gr_.st_.terminals_.size(); + const int nTerm = slr1.gr_.st_.terminals_.size(); for (int state = 0; state < rawTable.size(); ++state) { for (int j = 0; j < rawTable[state].size(); ++j) { QString cell = rawTable[state][j]; @@ -1742,9 +1739,7 @@ QString SLRTutorWindow::solutionForD1() { } QString SLRTutorWindow::solutionForD2() { - unsigned terminals = slr1.gr_.st_.terminals_.contains(slr1.gr_.st_.EPSILON_) - ? slr1.gr_.st_.terminals_.size() - 1 - : slr1.gr_.st_.terminals_.size(); + unsigned terminals = slr1.gr_.st_.terminals_.size(); unsigned non_terminals = slr1.gr_.st_.non_terminals_.size(); return QString::number(terminals + non_terminals); } @@ -2612,7 +2607,7 @@ void SLRTutorWindow::TeachClosureStep(std::unordered_set& items, output += indent + " - " + tr("Ítem: ") + QString::fromStdString(item.ToString()) + "\n"; - if (!slr1.gr_.st_.IsTerminal(next) && !visited.contains(next)) { + if (slr1.gr_.st_.IsNonTerminal(next) && !visited.contains(next)) { output += indent + tr(" - Encontrado un no terminal: %1\n") .arg(QString::fromStdString(next)); output += indent + tr(" - Añade todas las producciones de %1 " diff --git a/src/gui/slrwizard.h b/src/gui/slrwizard.h index b704054..91d21eb 100644 --- a/src/gui/slrwizard.h +++ b/src/gui/slrwizard.h @@ -63,11 +63,8 @@ class SLRWizard : public QWizard { : QWizard(parent) { setWindowTitle(tr("Ayuda interactiva: Tabla SLR(1)")); - const int nTerm = - parser.gr_.st_.terminals_.contains(parser.gr_.st_.EPSILON_) - ? parser.gr_.st_.terminals_.size() - 1 - : parser.gr_.st_.terminals_.size(); - SLRWizardPage* last = nullptr; + const int nTerm = parser.gr_.st_.terminals_.size(); + SLRWizardPage* last = nullptr; // Generar explicación y páginas int rows = rawTable.size(); int cols = colHeaders.size();