| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QLALR project on Qt Labs. |
| ** |
| ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "recognizer.h" |
| |
| #include <QtCore/qdir.h> |
| |
| #include <cstdlib> |
| #include <cstring> |
| #include <cctype> |
| |
| Recognizer::Recognizer (Grammar *grammar, bool no_lines): |
| tos(0), |
| stack_size(0), |
| state_stack(0), |
| _M_line(1), |
| _M_action_line(0), |
| _M_grammar(grammar), |
| _M_no_lines(no_lines) |
| { |
| } |
| |
| Recognizer::~Recognizer() |
| { |
| if (stack_size) |
| ::free(state_stack); |
| } |
| |
| inline void Recognizer::reallocateStack() |
| { |
| if (! stack_size) |
| stack_size = 128; |
| else |
| stack_size <<= 1; |
| |
| sym_stack.resize (stack_size); |
| |
| if (! state_stack) |
| state_stack = reinterpret_cast<int*> (::malloc(stack_size * sizeof(int))); |
| else |
| state_stack = reinterpret_cast<int*> (::realloc(state_stack, stack_size * sizeof(int))); |
| } |
| |
| int Recognizer::nextToken() |
| { |
| QString text; |
| |
| Lagain: |
| while (ch.isSpace ()) |
| inp (); |
| |
| if (ch.isNull ()) |
| return EOF_SYMBOL; |
| |
| int token = ch.unicode (); |
| |
| if (token == '"') |
| { |
| inp(); // skip " |
| text.clear (); |
| while (! ch.isNull () && ch != QLatin1Char ('"')) |
| { |
| if (ch == QLatin1Char ('\\')) |
| { |
| text += ch; |
| inp(); |
| } |
| text += ch; |
| inp (); |
| } |
| |
| if (ch == QLatin1Char ('"')) |
| inp (); |
| else |
| qerr() << _M_input_file << ":" << _M_line << ": Warning. Expected `\"'" << Qt::endl; |
| |
| _M_current_value = text; |
| return (token = STRING_LITERAL); |
| } |
| |
| else if (ch.isLetterOrNumber () || ch == QLatin1Char ('_')) |
| { |
| text.clear (); |
| do { text += ch; inp (); } |
| while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('.')); |
| _M_current_value = text; |
| return (token = ID); |
| } |
| |
| else if (token == '%') |
| { |
| text.clear (); |
| |
| do { inp (); } |
| while (ch.isSpace ()); |
| |
| do { text += ch; inp (); } |
| while (ch.isLetterOrNumber () || ch == QLatin1Char ('_') || ch == QLatin1Char ('-')); |
| |
| if (text == QLatin1String("token_prefix")) |
| return (token = TOKEN_PREFIX); |
| else if (text == QLatin1String("merged_output")) |
| return (token = MERGED_OUTPUT); |
| else if (text == QLatin1String("token")) |
| return (token = TOKEN); |
| else if (text == QLatin1String("start")) |
| return (token = START); |
| else if (text == QLatin1String("parser")) |
| return (token = PARSER); |
| else if (text == QLatin1String("decl")) |
| return (token = DECL_FILE); |
| else if (text == QLatin1String("impl")) |
| return (token = IMPL_FILE); |
| else if (text == QLatin1String("expect")) |
| return (token = EXPECT); |
| else if (text == QLatin1String("expect-rr")) |
| return (token = EXPECT_RR); |
| else if (text == QLatin1String("left")) |
| return (token = LEFT); |
| else if (text == QLatin1String("right")) |
| return (token = RIGHT); |
| else if (text == QLatin1String("nonassoc")) |
| return (token = NONASSOC); |
| else if (text == QLatin1String("prec")) |
| return (token = PREC); |
| else |
| { |
| qerr() << _M_input_file << ":" << _M_line << ": Unknown keyword `" << text << "'" << Qt::endl; |
| exit (EXIT_FAILURE); |
| return (token = ERROR); |
| } |
| } |
| |
| inp (); |
| |
| if (token == '-' && ch == QLatin1Char ('-')) |
| { |
| do { inp (); } |
| while (! ch.isNull () && ch != QLatin1Char ('\n')); |
| goto Lagain; |
| } |
| |
| else if (token == ':' && ch == QLatin1Char (':')) |
| { |
| inp (); |
| if (ch != QLatin1Char ('=')) |
| return (token = ERROR); |
| inp (); |
| return (token = COLON); |
| } |
| |
| else if (token == '/' && ch == QLatin1Char (':')) |
| { |
| _M_action_line = _M_line; |
| |
| text.clear (); |
| if (! _M_no_lines) |
| text += QLatin1String("\n#line ") + QString::number(_M_action_line) + |
| QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); |
| inp (); // skip ':' |
| |
| forever |
| { |
| while (! ch.isNull ()) |
| { |
| token = ch.unicode (); |
| inp (); |
| |
| if (token == ':' && ch == QLatin1Char ('/')) |
| break; |
| |
| text += QLatin1Char (token); |
| } |
| |
| if (ch != QLatin1Char ('/')) |
| return (token = ERROR); |
| |
| inp (); |
| |
| if (ch.isNull () || ch.isSpace ()) |
| { |
| _M_current_value = text; |
| return (token = DECL); |
| } |
| else |
| text += QLatin1String (":/"); |
| } |
| } |
| |
| else if (token == '/' && ch == QLatin1Char ('.')) |
| { |
| _M_action_line = _M_line; |
| |
| text.clear (); |
| if (! _M_no_lines) |
| text += QLatin1String("\n#line ") + QString::number(_M_action_line) + |
| QLatin1String(" \"") + QDir::fromNativeSeparators(_M_input_file) + QLatin1String("\"\n"); |
| |
| inp (); // skip ':' |
| |
| forever |
| { |
| while (! ch.isNull ()) |
| { |
| token = ch.unicode (); |
| inp (); |
| |
| if (token == '.' && ch == QLatin1Char ('/')) |
| break; |
| |
| text += QLatin1Char (token); |
| } |
| |
| if (ch != QLatin1Char ('/')) |
| return (token = ERROR); |
| |
| inp (); |
| |
| if (ch.isNull () || ch.isSpace ()) |
| { |
| _M_current_value = text; |
| return (token = IMPL); |
| } |
| else |
| text += QLatin1String (""); |
| } |
| } |
| |
| switch (token) { |
| case ':': |
| return (token = COLON); |
| |
| case ';': |
| return (token = SEMICOLON); |
| |
| case '|': |
| return (token = OR); |
| |
| default: |
| break; |
| } |
| |
| return token; |
| } |
| |
| bool Recognizer::parse (const QString &input_file) |
| { |
| _M_input_file = input_file; |
| |
| QFile file(_M_input_file); |
| if (! file.open(QFile::ReadOnly)) |
| { |
| qerr() << "qlalr: no input file\n"; |
| return false; |
| } |
| |
| QString _M_contents = QTextStream(&file).readAll(); |
| _M_firstChar = _M_contents.constBegin(); |
| _M_lastChar = _M_contents.constEnd(); |
| _M_currentChar = _M_firstChar; |
| _M_line = 1; |
| |
| int yytoken = -1; |
| inp (); |
| |
| reallocateStack(); |
| |
| _M_current_rule = _M_grammar->rules.end (); |
| _M_decls.clear (); |
| _M_impls.clear (); |
| |
| tos = 0; |
| state_stack[++tos] = 0; |
| |
| while (true) |
| { |
| if (yytoken == -1 && - TERMINAL_COUNT != action_index [state_stack [tos]]) |
| yytoken = nextToken(); |
| |
| int act = t_action (state_stack [tos], yytoken); |
| |
| if (act == ACCEPT_STATE) |
| return true; |
| |
| else if (act > 0) |
| { |
| if (++tos == stack_size) |
| reallocateStack(); |
| |
| sym_stack [tos] = _M_current_value; |
| state_stack [tos] = act; |
| yytoken = -1; |
| } |
| |
| else if (act < 0) |
| { |
| int r = - act - 1; |
| |
| tos -= rhs [r]; |
| act = state_stack [tos++]; |
| |
| switch (r) { |
| |
| case 3: { |
| Name name = _M_grammar->intern (sym(2)); |
| _M_grammar->start = name; |
| _M_grammar->non_terminals.insert (name); |
| } break; |
| |
| case 5: { |
| _M_grammar->table_name = sym(2); |
| } break; |
| |
| case 6: { |
| _M_grammar->merged_output = sym(2); |
| } break; |
| |
| case 7: { |
| _M_grammar->decl_file_name = sym(2); |
| } break; |
| |
| case 8: { |
| _M_grammar->impl_file_name = sym(2); |
| } break; |
| |
| case 9: { |
| _M_grammar->expected_shift_reduce = sym(2).toInt(); |
| } break; |
| |
| case 10: { |
| _M_grammar->expected_reduce_reduce = sym(2).toInt(); |
| } break; |
| |
| case 11: { |
| _M_grammar->token_prefix = sym(2); |
| } break; |
| case 17:case 18: { |
| Name name = _M_grammar->intern (sym(1)); |
| _M_grammar->terminals.insert (name); |
| _M_grammar->spells.insert (name, sym(2)); |
| } break; |
| |
| case 19: { |
| _M_grammar->current_assoc = Grammar::Left; |
| ++_M_grammar->current_prec; |
| } break; |
| |
| case 20: { |
| _M_grammar->current_assoc = Grammar::Right; |
| ++_M_grammar->current_prec; |
| } break; |
| |
| case 21: { |
| _M_grammar->current_assoc = Grammar::NonAssoc; |
| ++_M_grammar->current_prec; |
| } break; |
| |
| case 25: { |
| Name name = _M_grammar->intern (sym(1)); |
| _M_grammar->terminals.insert (name); |
| |
| Grammar::TokenInfo info; |
| info.prec = _M_grammar->current_prec; |
| info.assoc = _M_grammar->current_assoc; |
| _M_grammar->token_info.insert (name, info); |
| } break; |
| |
| case 26: { |
| _M_decls += expand (sym(1)); |
| } break; |
| |
| case 27: { |
| _M_impls += expand (sym(1)); |
| } break; |
| |
| case 34: { |
| _M_current_rule = _M_grammar->rules.insert (_M_grammar->rules.end (), Rule ()); |
| _M_current_rule->lhs = _M_grammar->intern (sym(1)); |
| _M_grammar->declared_lhs.insert (_M_current_rule->lhs); |
| |
| if (_M_grammar->terminals.find (_M_current_rule->lhs) != _M_grammar->terminals.end ()) |
| { |
| qerr() << _M_input_file << ":" << _M_line << ": Invalid non terminal `" << *_M_current_rule->lhs << "'" << Qt::endl; |
| return false; |
| } |
| |
| _M_grammar->non_terminals.insert (_M_current_rule->lhs); |
| } break; |
| |
| case 38: { |
| Name lhs = _M_current_rule->lhs; |
| _M_current_rule = _M_grammar->rules.insert (_M_grammar->rules.end (), Rule ()); |
| _M_current_rule->lhs = lhs; |
| _M_grammar->declared_lhs.insert (_M_current_rule->lhs); |
| |
| if (_M_grammar->terminals.find (_M_current_rule->lhs) != _M_grammar->terminals.end ()) |
| { |
| qerr() << _M_input_file << ":" << _M_line << ": Invalid non terminal `" << *_M_current_rule->lhs << "'" << Qt::endl; |
| return false; |
| } |
| |
| _M_grammar->non_terminals.insert (_M_current_rule->lhs); |
| } break; |
| |
| case 39: { |
| _M_current_rule->prec = _M_grammar->names.end (); |
| |
| for (NameList::iterator it = _M_current_rule->rhs.begin (); it != _M_current_rule->rhs.end (); ++it) |
| { |
| if (! _M_grammar->isTerminal (*it)) |
| continue; |
| |
| _M_current_rule->prec = *it; |
| } |
| } break; |
| |
| case 40: { |
| Name tok = _M_grammar->intern (sym(2)); |
| if (! _M_grammar->isTerminal (tok)) |
| { |
| qerr() << _M_input_file << ":" << _M_line << ": `" << *tok << " is not a terminal symbol" << Qt::endl; |
| _M_current_rule->prec = _M_grammar->names.end (); |
| } |
| else |
| _M_current_rule->prec = tok; |
| } break; |
| |
| case 42: { |
| Name name = _M_grammar->intern (sym(2)); |
| |
| if (_M_grammar->terminals.find (name) == _M_grammar->terminals.end ()) |
| _M_grammar->non_terminals.insert (name); |
| |
| _M_current_rule->rhs.push_back (name); |
| } break; |
| |
| case 43: { |
| sym(1) = QString(); |
| } break; |
| |
| } // switch |
| |
| state_stack [tos] = nt_action (act, lhs [r] - TERMINAL_COUNT); |
| } |
| |
| else |
| { |
| break; |
| } |
| } |
| |
| qerr() << _M_input_file << ":" << _M_line << ": Syntax error" << Qt::endl; |
| return false; |
| } |
| |