| # Checking the C++ Features. -*- Autotest -*- |
| |
| # Copyright (C) 2004-2005, 2007-2015, 2018-2019 Free Software |
| # Foundation, Inc. |
| |
| # This program is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation, either version 3 of the License, or |
| # (at your option) any later version. |
| # |
| # This program 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 General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| AT_BANNER([[C++ Features.]]) |
| |
| |
| ## -------------------------- ## |
| ## C++ Locations Unit Tests. ## |
| ## -------------------------- ## |
| |
| AT_SETUP([C++ Locations Unit Tests]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%locations %skeleton "lalr1.cc"]) |
| AT_DATA_GRAMMAR([[input.y]], |
| [[%code {#include <sstream>} |
| %locations |
| %debug |
| %skeleton "lalr1.cc" |
| %code |
| { |
| ]AT_YYERROR_DECLARE[ |
| ]AT_YYLEX_DECLARE[ |
| } |
| %% |
| exp: %empty; |
| %% |
| ]AT_YYERROR_DEFINE[ |
| ]AT_YYLEX_DEFINE[ |
| |
| template <typename T> |
| bool |
| check (const T& in, const std::string& s) |
| { |
| std::stringstream os; |
| os << in; |
| if (os.str () != s) |
| { |
| std::cerr << "fail: " << os.str () << ", expected: " << s << '\n'; |
| return false; |
| } |
| return true; |
| } |
| |
| int |
| main (void) |
| { |
| int fail = 0; |
| ]AT_YYLTYPE[ loc; fail += check (loc, "1.1"); |
| fail += check (loc + 10, "1.1-10"); |
| loc += 10; fail += check (loc, "1.1-10"); |
| loc += -5; fail += check (loc, "1.1-5"); |
| fail += check (loc - 5, "1.1"); |
| loc -= 5; fail += check (loc, "1.1"); |
| // Check that we don't go below. |
| // http://lists.gnu.org/archive/html/bug-bison/2013-02/msg00000.html |
| loc -= 10; fail += check (loc, "1.1"); |
| |
| loc.columns (10); loc.lines (10); fail += check (loc, "1.1-11.0"); |
| loc.lines (-2); fail += check (loc, "1.1-9.0"); |
| loc.lines (-10); fail += check (loc, "1.1"); |
| |
| ]AT_YYLTYPE[ loc2 (YY_NULLPTR, 5, 10); |
| fail += check (loc2, "5.10"); |
| fail += check (loc + loc2, "1.1-5.9"); |
| loc += loc2; fail += check (loc, "1.1-5.9"); |
| return !fail; |
| } |
| ]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_FULL_COMPILE([input]) |
| AT_PARSER_CHECK([input], 0) |
| ]) |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| |
| ## -------------------------------------- ## |
| ## C++ Variant-based Symbols Unit Tests. ## |
| ## -------------------------------------- ## |
| |
| # Not checking the grammar, only the variants and variant based |
| # symbols. |
| |
| AT_SETUP([C++ Variant-based Symbols Unit Tests]) |
| |
| AT_KEYWORDS([variant]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc" %debug $1]) |
| # Store strings and integers in a vector of strings. |
| AT_DATA_GRAMMAR([list.yy], |
| [[%skeleton "lalr1.cc" |
| %define api.value.type variant |
| %define parse.assert |
| %debug |
| |
| %code top |
| { |
| // Get access to stack_symbol_type for the tests. |
| # define private public |
| } |
| %code provides |
| { |
| ]AT_YYLEX_DECLARE[ |
| } |
| |
| %token <int> INT "int" |
| %type <std::vector<int>> exp |
| |
| %printer { yyo << $$; } <int> |
| %printer |
| { |
| for (std::vector<int>::const_iterator i = $$.begin (); i != $$.end (); ++i) |
| { |
| if (i != $$.begin ()) |
| yyo << ", "; |
| yyo << *i; |
| } |
| } <std::vector<int>> |
| |
| %code requires { #include <vector> } |
| %code { int yylex (yy::parser::semantic_type* lvalp); } |
| |
| %% |
| exp: "int" { $$.push_back ($1); } |
| %% |
| ]AT_YYERROR_DEFINE[ |
| ]AT_YYLEX_DEFINE[ |
| |
| template <typename Exp, typename Eff> |
| void assert_eq (const Exp& exp, const Eff& eff) |
| { |
| if (getenv ("DEBUG")) |
| std::cerr << "Assert: " << exp << " == " << eff << '\n'; |
| if (exp != eff) |
| std::cerr << "Assertion failed: " << exp << " != " << eff << '\n'; |
| } |
| |
| int main() |
| { |
| using yy::parser; |
| // symbol_type: construction, accessor. |
| { |
| parser::symbol_type s = parser::make_INT (12); |
| assert_eq (s.value.as<int> (), 12); |
| } |
| |
| // symbol_type: move constructor. |
| #if 201103L <= YY_CPLUSPLUS |
| { |
| auto s = parser::make_INT (42); |
| auto s2 = std::move (s); |
| assert_eq (s2.value.as<int> (), 42); |
| // Used to crash here, because s was improperly cleared, and |
| // its destructor tried to delete its (moved) value. |
| } |
| #endif |
| |
| // symbol_type: copy constructor. |
| { |
| parser::symbol_type s = parser::make_INT (51); |
| parser::symbol_type s2 = s; |
| assert_eq (s.value.as<int> (), 51); |
| assert_eq (s2.value.as<int> (), 51); |
| } |
| |
| // stack_symbol_type: construction, accessor. |
| { |
| #if 201103L <= YY_CPLUSPLUS |
| auto ss = parser::stack_symbol_type(1, parser::make_INT(123)); |
| #else |
| parser::symbol_type s = parser::make_INT (123); |
| parser::stack_symbol_type ss(1, s); |
| #endif |
| assert_eq (ss.value.as<int> (), 123); |
| } |
| |
| // Pushing on the stack. |
| // Sufficiently many so that it will be resized. |
| // Probably 3 times (starting at 200). |
| { |
| parser::stack_type st; |
| const int mucho = 1700; |
| const int int_reduction_state = 1; // Read list.output to find it. |
| for (int i = 0; i < mucho; ++i) |
| { |
| #if 201103L <= YY_CPLUSPLUS |
| st.push(parser::stack_symbol_type{int_reduction_state, |
| parser::make_INT (i)}); |
| #else |
| parser::symbol_type s = parser::make_INT (i); |
| parser::stack_symbol_type ss (int_reduction_state, s); |
| st.push (ss); |
| #endif |
| } |
| for (int i = mucho - 1; 0 <= i; --i) |
| { |
| assert_eq (st[0].value.as<int>(), i); |
| st.pop (); |
| } |
| } |
| } |
| ]]) |
| |
| AT_BISON_CHECK([[-o list.cc list.yy]]) |
| AT_FOR_EACH_CXX([ |
| AT_COMPILE_CXX([list]) |
| AT_PARSER_CHECK([list]) |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| ## --------------------------------------------------- ## |
| ## Multiple occurrences of $n and api.value.automove. ## |
| ## --------------------------------------------------- ## |
| |
| AT_SETUP([Multiple occurrences of $n and api.value.automove]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc"]) |
| |
| AT_DATA_GRAMMAR([input.yy], |
| [[%skeleton "lalr1.cc" |
| %define api.value.automove |
| %token <int> NUMBER "number" |
| %type <int> exp |
| %% |
| exp: |
| "number" { $$ = $1; $$; } |
| | "twice" exp { $$ = $2 + $2; } |
| | "thrice" exp[val] { $$ = $2 + $val + $2; } |
| ]]) |
| |
| AT_BISON_CHECK([[-fcaret input.yy]], [0], [], |
| [[input.yy:16.33-34: warning: multiple occurrences of $2 with api.value.automove [-Wother] |
| 16 | | "twice" exp { $$ = $2 + $2; } |
| | ^~ |
| input.yy:17.33-36: warning: multiple occurrences of $2 with api.value.automove [-Wother] |
| 17 | | "thrice" exp[val] { $$ = $2 + $val + $2; } |
| | ^~~~ |
| input.yy:17.40-41: warning: multiple occurrences of $2 with api.value.automove [-Wother] |
| 17 | | "thrice" exp[val] { $$ = $2 + $val + $2; } |
| | ^~ |
| ]]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| |
| ## ---------- ## |
| ## Variants. ## |
| ## ---------- ## |
| |
| # Check that the variants are properly supported, including in error |
| # recovery. |
| |
| # AT_TEST([DIRECTIVES]) |
| # --------------------- |
| # Check the support of variants in C++, with the additional DIRECTIVES. |
| m4_pushdef([AT_TEST], |
| [AT_SETUP([Variants $1]) |
| |
| AT_KEYWORDS([variant]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%debug $1]) |
| # Store strings and integers in a vector of strings. |
| AT_DATA_GRAMMAR([list.y], |
| [[%debug |
| %define api.value.type variant |
| ]m4_bpatsubst([$1], [\\n], [ |
| ])[ |
| |
| %code top // code for the .cc file. |
| { |
| #include <cstdlib> // abort, getenv |
| #include <iostream> |
| #include <vector> |
| #include <sstream> |
| #include <string> |
| |
| class string |
| { |
| public: |
| string () {} |
| |
| string (const std::string& s) |
| : val_(s) |
| {} |
| |
| string (const string& s) |
| : val_(s.val_) |
| {} |
| |
| string& operator= (const string& s) |
| { |
| val_ = s.val_; |
| return *this; |
| } |
| |
| #if defined __cplusplus && 201103L <= __cplusplus |
| string (string&& s) noexcept |
| : val_(std::move(s.val_)) |
| { |
| s.val_.clear(); |
| } |
| |
| string& operator= (string&& s) |
| { |
| val_ = std::move(s.val_); |
| s.val_.clear (); |
| return *this; |
| } |
| #endif |
| |
| friend |
| std::ostream& operator<< (std::ostream& o, const string& s) |
| { |
| return o << s.val_; |
| } |
| |
| private: |
| std::string val_; |
| }; |
| |
| typedef std::vector<string> strings_type; |
| |
| namespace yy |
| { |
| // Must be available early, as is used in %destructor. |
| std::ostream& |
| operator<<(std::ostream& o, const strings_type& s) |
| { |
| o << '('; |
| for (strings_type::const_iterator i = s.begin (); i != s.end (); ++i) |
| { |
| if (i != s.begin ()) |
| o << ", "; |
| o << *i; |
| } |
| return o << ')'; |
| } |
| } |
| } |
| |
| %code // code for the .cc file. |
| { |
| namespace yy |
| { |
| static |
| ]AT_YYLEX_PROTOTYPE[; |
| |
| // Conversion to string. |
| template <typename T> |
| inline |
| string |
| to_string (const T& t) |
| { |
| std::ostringstream o; |
| o << t; |
| return string (o.str ()); |
| } |
| } |
| } |
| |
| %token <::string> TEXT; |
| %token <int> NUMBER; |
| %token END_OF_FILE 0; |
| %token COMMA "," |
| |
| // Starting with :: to ensure we don't output "<::" which starts by the |
| // digraph for the left square bracket. |
| %type <::string> item; |
| // Using the template type to exercize its parsing. |
| %type <::std::vector<string>> list; |
| |
| %printer { yyo << $$; } <int> <::string> <::std::vector<string>>; |
| %destructor { std::cerr << "Destroy: " << $$ << '\n'; } <*>; |
| %destructor { std::cerr << "Destroy: \"" << $$ << "\"\n"; } <::string>; |
| %% |
| |
| result: |
| list { std::cout << $][1 << '\n'; } |
| ; |
| |
| list: |
| item { $$.push_back ($][1); } |
| | list "," item { $$ = $][1; $$.push_back ($][3); } |
| | list error { $$ = $][1; } |
| ; |
| |
| item: |
| TEXT { $$ = $][1; } |
| | NUMBER { int v = $][1; if (v == 3) YYERROR; else $$ = to_string (v); } |
| ; |
| %% |
| ]AT_TOKEN_CTOR_IF([], |
| [[#ifdef TWO_STAGE_BUILD |
| # define BUILD(Type, Value) build<Type> () = Value |
| #else |
| # define BUILD(Type, Value) build (Value) |
| #endif |
| ]])[ |
| #define STAGE_MAX 5 |
| namespace yy |
| { |
| static |
| ]AT_YYLEX_PROTOTYPE[ |
| { |
| // The 5 is a syntax error whose recovery requires that we discard |
| // the lookahead. This tests a regression, see |
| // <http://savannah.gnu.org/support/?108481>. |
| static char const *input = "0,1,2,3,45,6"; |
| switch (int stage = *input++) |
| { |
| case 0:]AT_TOKEN_CTOR_IF([[ |
| return parser::make_END_OF_FILE (]AT_LOCATION_IF([location ()])[);]], |
| [AT_LOCATION_IF([ |
| *llocp = location ();])[ |
| return parser::token::END_OF_FILE;]])[ |
| |
| case ',':]AT_TOKEN_CTOR_IF([[ |
| return parser::make_COMMA (]AT_LOCATION_IF([location ()])[);]], |
| [AT_LOCATION_IF([ |
| *llocp = location ();])[ |
| return parser::token::COMMA;]])[ |
| |
| default: |
| stage = stage - '0'; |
| if (stage % 2) |
| {]AT_TOKEN_CTOR_IF([[ |
| return parser::make_NUMBER (stage]AT_LOCATION_IF([, location ()])[);]], [[ |
| lvalp->BUILD (int, stage);]AT_LOCATION_IF([ |
| *llocp = location ();])[ |
| return parser::token::NUMBER;]])[ |
| } |
| else |
| {]AT_TOKEN_CTOR_IF([[ |
| return parser::make_TEXT (to_string (stage)]AT_LOCATION_IF([, location ()])[);]], [[ |
| lvalp->BUILD (string, to_string (stage));]AT_LOCATION_IF([ |
| *llocp = location ();])[ |
| return parser::token::TEXT;]])[ |
| } |
| } |
| } |
| } |
| |
| ]AT_YYERROR_DEFINE[ |
| ]AT_MAIN_DEFINE[ |
| ]]) |
| |
| AT_DATA_SOURCE([[modern.cc]], |
| [[#include <iostream> |
| int main() |
| { |
| #if defined __cplusplus && 201103L <= __cplusplus |
| std::cout << "Modern C++: " << __cplusplus << '\n'; |
| return 0; |
| #else |
| std::cout << "Legac++\n"; |
| return 1; |
| #endif |
| } |
| ]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_FULL_COMPILE([list]) |
| |
| # Are we compiling with modern C++ enabled? |
| AT_COMPILE_CXX([modern]) |
| here=. # Pacify cfg.mk's sc_at_parser_check. |
| AT_CHECK([$here/modern], [ignore], [ignore]) |
| if test $at_status = 0; then |
| modern=true |
| else |
| modern=false |
| fi |
| |
| if AT_AUTOMOVE_IF([$modern], [false]); then |
| AT_PARSER_CHECK([list], 0, |
| [[(0, 1, 2, 4, 6) |
| ]], |
| [[Destroy: "" |
| Destroy: "" |
| Destroy: 1 |
| Destroy: "" |
| Destroy: () |
| Destroy: "" |
| Destroy: "" |
| Destroy: () |
| Destroy: "" |
| Destroy: 3 |
| Destroy: () |
| Destroy: "" |
| Destroy: "" |
| Destroy: () |
| Destroy: () |
| Destroy: 5 |
| Destroy: () |
| Destroy: "" |
| Destroy: "" |
| Destroy: () |
| Destroy: (0, 1, 2, 4, 6) |
| ]]) |
| else |
| AT_PARSER_CHECK([list], 0, |
| [[(0, 1, 2, 4, 6) |
| ]], |
| [[Destroy: "0" |
| Destroy: "0" |
| Destroy: 1 |
| Destroy: "1" |
| Destroy: (0) |
| Destroy: "2" |
| Destroy: "2" |
| Destroy: (0, 1) |
| Destroy: "" |
| Destroy: 3 |
| Destroy: (0, 1, 2) |
| Destroy: "4" |
| Destroy: "4" |
| Destroy: (0, 1, 2) |
| Destroy: (0, 1, 2, 4) |
| Destroy: 5 |
| Destroy: (0, 1, 2, 4) |
| Destroy: "6" |
| Destroy: "6" |
| Destroy: (0, 1, 2, 4) |
| Destroy: (0, 1, 2, 4, 6) |
| ]]) |
| fi |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| ]) |
| |
| AT_TEST([[%skeleton "lalr1.cc"]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %define api.value.automove]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %locations]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %code {\n#define TWO_STAGE_BUILD\n}]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %define api.token.constructor]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %define api.token.constructor %define api.token.prefix {TOK_}]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %define api.token.constructor %define api.token.prefix {TOK_} %locations]]) |
| AT_TEST([[%skeleton "lalr1.cc" %define parse.assert %define api.token.constructor %define api.token.prefix {TOK_} %locations %define api.value.automove]]) |
| |
| m4_popdef([AT_TEST]) |
| |
| |
| |
| ## ------------------------------------ ## |
| ## Variants and Typed Midrule Actions. ## |
| ## ------------------------------------ ## |
| |
| AT_SETUP([Variants and Typed Midrule Actions]) |
| |
| # See http://lists.gnu.org/archive/html/bug-bison/2017-06/msg00000.html. |
| # |
| # Check that typed midrule actions behave properly (pre-construction |
| # of $$ before the user action, support of %printer and %destructor, |
| # etc.). |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc"]) |
| |
| AT_DATA_GRAMMAR([[input.y]], |
| [[%skeleton "lalr1.cc" |
| %defines |
| |
| %debug |
| %define parse.assert |
| %define api.value.type variant |
| %define api.token.constructor |
| %define parse.error verbose |
| |
| %code |
| { |
| #include <iostream> |
| namespace yy |
| { |
| static yy::parser::symbol_type yylex(); |
| } |
| } |
| |
| %token <int> NUMBER; |
| %type <int> expr; |
| %token EOI 0; |
| %printer { yyo << $$; } <int>; |
| %destructor { std::cerr << "destroy: " << $$ << '\n'; } <int> |
| %% |
| expr: |
| NUMBER { $$ = $1 * 10; } |
| | expr <int>{ $$ = 20; } NUMBER |
| { |
| std::cerr << "expr: " << $1 << ' ' << $2 << ' ' << $3 << '\n'; |
| $$ = 40; |
| } |
| ; |
| |
| %% |
| namespace yy |
| { |
| parser::symbol_type yylex() |
| { |
| static int loc = 0; |
| switch (loc++) |
| { |
| case 0: |
| return parser::make_NUMBER (1); |
| case 1: |
| return parser::make_NUMBER (30); |
| default: |
| return parser::make_EOI (); |
| } |
| } |
| |
| void parser::error(const std::string& message) |
| { |
| std::cerr << message << '\n'; |
| } |
| } |
| |
| int main() |
| { |
| yy::parser p; |
| p.set_debug_level (1); |
| return p.parse(); |
| } |
| ]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_FULL_COMPILE([[input]]) |
| # This used to print "Discarding 'a'." again at the end. |
| AT_PARSER_CHECK([[input]], [[0]], [[]], |
| [[Starting parse |
| Entering state 0 |
| Reading a token: Next token is token NUMBER (1) |
| Shifting token NUMBER (1) |
| Entering state 1 |
| Reducing stack by rule 1 (line 34): |
| $1 = token NUMBER (1) |
| -> $$ = nterm expr (10) |
| destroy: 1 |
| Stack now 0 |
| Entering state 2 |
| Reading a token: Next token is token NUMBER (30) |
| Reducing stack by rule 2 (line 35): |
| -> $$ = nterm @1 (20) |
| Stack now 2 0 |
| Entering state 4 |
| Next token is token NUMBER (30) |
| Shifting token NUMBER (30) |
| Entering state 5 |
| Reducing stack by rule 3 (line 35): |
| $1 = nterm expr (10) |
| $2 = nterm @1 (20) |
| $3 = token NUMBER (30) |
| expr: 10 20 30 |
| -> $$ = nterm expr (40) |
| destroy: 30 |
| destroy: 20 |
| destroy: 10 |
| Stack now 0 |
| Entering state 2 |
| Reading a token: Next token is token EOI () |
| Shifting token EOI () |
| Entering state 3 |
| Cleanup: popping token EOI () |
| Cleanup: popping nterm expr (40) |
| destroy: 40 |
| ]]) |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| |
| ## ----------------------- ## |
| ## Doxygen Documentation. ## |
| ## ----------------------- ## |
| |
| m4_define([AT_CHECK_DOXYGEN], |
| [m4_case([$1], |
| [Public], [m4_pushdef([AT_DOXYGEN_PRIVATE], [NO])], |
| [Private], [m4_pushdef([AT_DOXYGEN_PRIVATE], [YES])], |
| [m4_fatal([invalid argument: $1])]) |
| AT_SETUP([Doxygen $1 Documentation]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc" %locations]) |
| AT_DATA([input.yy], |
| [[%require "3.2" |
| %skeleton "lalr1.cc" |
| %locations |
| %defines |
| %debug |
| %% |
| exp: %empty; |
| %% |
| ]AT_YYERROR_DEFINE[ |
| ]]) |
| |
| AT_BISON_CHECK([-o input.cc input.yy]) |
| |
| AT_DATA([Doxyfile], |
| [# The PROJECT_NAME tag is a single word (or a sequence of words |
| # surrounded by quotes) that should identify the project. |
| PROJECT_NAME = "Bison C++ Parser" |
| |
| # The QUIET tag can be used to turn on/off the messages that are |
| # generated by doxygen. Possible values are YES and NO. If left blank |
| # NO is used. |
| QUIET = YES |
| |
| # The WARNINGS tag can be used to turn on/off the warning messages |
| # that are generated by doxygen. Possible values are YES and NO. If |
| # left blank NO is used. |
| WARNINGS = YES |
| # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate |
| # warnings for undocumented members. If EXTRACT_ALL is set to YES then |
| # this flag will automatically be disabled. |
| WARN_IF_UNDOCUMENTED = YES |
| # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings |
| # for potential errors in the documentation, such as not documenting |
| # some parameters in a documented function, or documenting parameters |
| # that don't exist or using markup commands wrongly. |
| WARN_IF_DOC_ERROR = YES |
| # The WARN_FORMAT tag determines the format of the warning messages |
| # that doxygen can produce. The string should contain the $file, |
| # $line, and $text tags, which will be replaced by the file and line |
| # number from which the warning originated and the warning text. |
| WARN_FORMAT = "$file:$line: $text" |
| |
| # If the EXTRACT_ALL tag is set to YES doxygen will assume all |
| # entities in documentation are documented, even if no documentation |
| # was available. Private class members and static file members will |
| # be hidden unless the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set |
| # to YES |
| EXTRACT_ALL = YES |
| |
| # If the EXTRACT_PRIVATE tag is set to YES all private members of a |
| # class will be included in the documentation. |
| EXTRACT_PRIVATE = AT_DOXYGEN_PRIVATE |
| |
| # If the EXTRACT_STATIC tag is set to YES all static members of a file |
| # will be included in the documentation. |
| EXTRACT_STATIC = AT_DOXYGEN_PRIVATE |
| ]) |
| |
| AT_REQUIRE([doxygen --version], 0, ignore) |
| AT_CHECK([doxygen], 0, [], [ignore]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| m4_popdef([AT_DOXYGEN_PRIVATE]) |
| ])# AT_CHECK_DOXYGEN |
| |
| AT_CHECK_DOXYGEN([Public]) |
| AT_CHECK_DOXYGEN([Private]) |
| |
| |
| ## ------------ ## |
| ## Namespaces. ## |
| ## ------------ ## |
| |
| # AT_TEST(NAMESPACE-DECL, [COMPILE-ERROR]) |
| # ---------------------------------------- |
| # See if Bison can handle %define namespace "NAMESPACE-DECL". If COMPILE-ERROR |
| # is specified, then Bison should accept the input, but compilation will fail, |
| # so don't check compilation. |
| m4_pushdef([AT_TEST], |
| [AT_BISON_OPTION_PUSHDEFS([%language "C++" %define api.namespace {$1}]) |
| AT_DATA_GRAMMAR([[input.yy]], |
| [[%language "C++" |
| %define api.namespace {]$1[} |
| %union { int i; } |
| %define global_tokens_and_yystype |
| %locations |
| |
| %code { |
| // YYSTYPE contains a namespace reference. |
| int yylex (YYSTYPE *lval, const ]$1[::parser::location_type*) { |
| lval->i = 3; |
| return 0; |
| } |
| } |
| |
| %% |
| |
| start: ; |
| |
| %% |
| |
| void |
| ]$1[::parser::error (const ]$1[::parser::location_type &loc, |
| const std::string &msg) |
| { |
| std::cerr << "At " << loc << ": " << msg << '\n'; |
| } |
| |
| ]AT_MAIN_DEFINE[ |
| ]]) |
| |
| |
| AT_BISON_CHECK([[-o input.cc input.yy]]) |
| |
| m4_if([$#], [1], |
| [AT_FOR_EACH_CXX([ |
| AT_COMPILE_CXX([[input]]) |
| AT_PARSER_CHECK([[input]])])]) |
| AT_BISON_OPTION_POPDEFS |
| ]) |
| |
| AT_SETUP([[Relative namespace references]]) |
| AT_TEST([[foo]]) |
| AT_TEST([[foo::bar]]) |
| AT_TEST([[foo::bar::baz]]) |
| AT_CLEANUP |
| |
| AT_SETUP([[Absolute namespace references]]) |
| AT_TEST([[::foo]]) |
| AT_TEST([[::foo::bar]]) |
| AT_TEST([[::foo::bar::baz]]) |
| AT_TEST([[@tb@::foo]]) |
| AT_TEST([[ @tb@ ::foo::bar]]) |
| AT_TEST([[ ::foo::bar::baz]]) |
| AT_CLEANUP |
| |
| AT_SETUP([[Syntactically invalid namespace references]]) |
| AT_TEST([[:foo:bar]], [[-]]) |
| AT_TEST([[foo: :bar]], [[-]]) |
| # This one is interesting because '[3]' is encoded as '@<:@3@:>@', which |
| # contains single occurrences of ':'. |
| AT_TEST([[foo[3]::bar::baz]], [[-]]) |
| AT_TEST([[foo::bar,baz]], [[-]]) |
| AT_TEST([[foo::bar::(baz /* Pacify Emacs ) */]], [[-]]) |
| AT_CLEANUP |
| |
| m4_popdef([AT_TEST]) |
| |
| |
| ## -------------------------------------- ## |
| ## Syntax error discarding no lookahead. ## |
| ## -------------------------------------- ## |
| |
| # After a syntax error, lalr1.cc used to not check whether there |
| # actually is a lookahead before discarding the lookahead. As a result, |
| # it mistakenly invoked the destructor for the previous lookahead. |
| |
| AT_SETUP([[Syntax error discarding no lookahead]]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc"]) |
| |
| AT_DATA_GRAMMAR([[input.y]], |
| [[%skeleton "lalr1.cc" |
| |
| %code { |
| #include <string> |
| int yylex (yy::parser::semantic_type *); |
| #define USE(Args) |
| } |
| |
| %define parse.error verbose |
| |
| %nonassoc 'a' ; |
| |
| %destructor { |
| std::cerr << "Discarding 'a'.\n"; |
| } 'a' |
| |
| %% |
| |
| start: error-reduce consistent-error 'a' { USE ($3); }; |
| |
| error-reduce: |
| 'a' 'a' consistent-error 'a' { USE (($1, $2, $4)); } |
| | 'a' error { std::cerr << "Reducing 'a'.\n"; USE ($1); } |
| ; |
| |
| consistent-error: |
| 'a' |
| | %empty %prec 'a' |
| ; |
| |
| // Provide another context in which all rules are useful so that this |
| // test case looks a little more realistic. |
| start: 'b' consistent-error ; |
| |
| %% |
| |
| int |
| yylex (yy::parser::semantic_type *) |
| { |
| static char const *input = "aa"; |
| return *input++; |
| } |
| |
| void |
| yy::parser::error (const std::string &m) |
| { |
| std::cerr << m << '\n'; |
| } |
| |
| ]AT_MAIN_DEFINE[ |
| ]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_FULL_COMPILE([[input]]) |
| # This used to print "Discarding 'a'." again at the end. |
| AT_PARSER_CHECK([[input]], [[1]], [[]], |
| [[syntax error |
| Discarding 'a'. |
| Reducing 'a'. |
| ]]) |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| |
| ## --------------------------- ## |
| ## Syntax error as exception. ## |
| ## --------------------------- ## |
| |
| # AT_TEST([BISON-DIRECTIVES = '']) |
| m4_pushdef([AT_TEST], |
| [AT_SETUP([[Syntax error as exception: $1]]) |
| |
| AT_BISON_OPTION_PUSHDEFS([$1 %debug]) |
| |
| AT_DATA_GRAMMAR([[input.yy]], |
| [[$1 |
| %defines |
| |
| %code |
| { |
| #include <cstdlib> |
| int yylex (yy::parser::semantic_type *); |
| } |
| |
| %define parse.error verbose |
| %define parse.trace |
| %% |
| |
| start: with-recovery | '!' without-recovery; |
| |
| with-recovery: |
| %empty |
| | with-recovery item |
| | with-recovery error { std::cerr << "caught error\n"; } |
| ; |
| |
| without-recovery: |
| %empty |
| | without-recovery item |
| ; |
| |
| item: |
| 'a' |
| | 's' |
| { |
| throw syntax_error ("invalid expression"); |
| } |
| |
| %% |
| |
| void |
| yy::parser::error (const std::string &m) |
| { |
| std::cerr << "error: " << m << '\n'; |
| } |
| ]AT_MAIN_DEFINE[ |
| ]]) |
| |
| # Another file to check syntax_error's linkage. |
| AT_DATA_SOURCE([scan.cc], |
| [[#include <cstdio> // getchar |
| #include "input.hh" |
| |
| // 'a': valid item, 's': syntax error, 'l': lexical error. |
| int |
| yylex (yy::parser::semantic_type *lval) |
| { |
| switch (int res = getchar ()) |
| { |
| // Don't choke on echo's \n. |
| case '\n': |
| return yylex (lval); |
| case 'l': |
| throw yy::parser::syntax_error ("invalid character"); |
| default: |
| return res; |
| } |
| } |
| ]]) |
| |
| AT_BISON_CHECK([[-o input.cc input.yy]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_LANG_COMPILE([[input]], [[input.cc scan.cc]]) |
| |
| # Leave enough valid tokens to make sure we recovered from the |
| # previous error, otherwise we might hide some error messages |
| # (discarded during error recoevery). |
| echo "asaaalaa" >in |
| AT_PARSER_CHECK([[input < in]], [[0]], [[]], |
| [[error: invalid expression |
| caught error |
| error: invalid character |
| caught error |
| ]]) |
| |
| echo "!as" >in |
| AT_PARSER_CHECK([[input < in]], [1], [], |
| [[error: invalid expression |
| ]]) |
| |
| echo "!al" >in |
| AT_PARSER_CHECK([[input < in]], [1], [], |
| [[error: invalid character |
| ]]) |
| |
| ]) # AT_FOR_EACH_CXX |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| ]) |
| |
| AT_TEST([%skeleton "lalr1.cc"]) |
| AT_TEST([%skeleton "glr.cc"]) |
| |
| m4_popdef([AT_TEST]) |
| |
| |
| |
| ## ------------------ ## |
| ## Exception safety. ## |
| ## ------------------ ## |
| |
| # AT_TEST([BISON-DIRECTIVES = ''], [WITH-RECOVERY = "with"]) |
| # ---------------------------------------------------------- |
| # Check that no object is leaked when exceptions are thrown. |
| # WITH-RECOVERY = "with" or "without". |
| m4_pushdef([AT_TEST], |
| [AT_SETUP([[Exception safety $2 error recovery $1]]) |
| |
| AT_SKIP_IF_EXCEPTION_SUPPORT_IS_POOR |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc" $1]) |
| |
| AT_DATA_GRAMMAR([[input.yy]], |
| [[%skeleton "lalr1.cc" |
| %debug |
| %define parse.error verbose |
| $1 |
| %code requires |
| { |
| #include <cassert> |
| #include <cstdlib> // size_t and getenv. |
| #include <iostream> |
| #include <set> |
| #include <string> |
| |
| bool debug = false; |
| |
| /// A class that tracks its instances. |
| struct Object |
| { |
| char val; |
| |
| Object () |
| : val ('?') |
| { |
| log (this, "Object::Object"); |
| Object::instances.insert (this); |
| } |
| |
| Object (const Object& that) |
| : val (that.val) |
| { |
| log (this, "Object::Object"); |
| Object::instances.insert (this); |
| } |
| |
| Object (char v) |
| : val (v) |
| { |
| log (this, "Object::Object"); |
| Object::instances.insert (this); |
| } |
| |
| ~Object () |
| { |
| log (this, "Object::~Object"); |
| objects::iterator i = instances.find (this); |
| // Make sure this object is alive. |
| assert (i != instances.end ()); |
| Object::instances.erase (i); |
| } |
| |
| Object& operator= (const Object& that) |
| { |
| val = that.val; |
| return *this; |
| } |
| |
| Object& operator= (char v) |
| { |
| val = v; |
| return *this; |
| } |
| |
| // Static part. |
| typedef std::set<const Object*> objects; |
| static objects instances; |
| |
| static bool |
| empty () |
| { |
| return instances.empty (); |
| } |
| |
| static void |
| log (Object const *o, const std::string& msg) |
| { |
| if (debug) |
| { |
| if (o) |
| std::cerr << o << "->"; |
| std::cerr << msg << " {"; |
| const char* sep = " "; |
| for (objects::const_iterator i = instances.begin(), |
| i_end = instances.end(); |
| i != i_end; |
| ++i) |
| { |
| std::cerr << sep << *i; |
| sep = ", "; |
| } |
| std::cerr << " }\n"; |
| } |
| } |
| }; |
| } |
| |
| %code |
| { |
| #include <cassert> |
| #include <cstring> // strchr |
| #include <stdexcept> |
| int yylex (yy::parser::semantic_type *); |
| Object::objects Object::instances; |
| static char const *input; |
| } |
| |
| ]AT_VARIANT_IF([[ |
| %printer |
| { |
| yyo << &$$ << " '" << $$.val << '\''; |
| if ($$.val == 'p') |
| throw std::runtime_error ("printer"); |
| } <Object>; |
| |
| %token <Object> 'a' 'E' 'e' 'p' 'R' 's' 'T' |
| %type <Object> list item |
| ]], [[ |
| %union |
| { |
| Object *obj; |
| } |
| %destructor { delete $$; } <obj>; |
| %printer |
| { |
| yyo << $$ << " '" << $$->val << '\''; |
| if ($$->val == 'p') |
| throw std::runtime_error ("printer"); |
| } <obj>; |
| |
| %token <obj> 'a' 'E' 'e' 'p' 'R' 's' 'T' |
| %type <obj> list item |
| ]])[ |
| |
| %initial-action |
| { |
| if (strchr (input, 'i')) |
| throw std::runtime_error ("initial-action"); |
| } |
| |
| %% |
| |
| start: list {]AT_VARIANT_IF([], [ delete $][1]; )[}; |
| |
| list: |
| item { $$ = $][1; } |
| // Right recursion to load the stack. |
| | item list { $$ = $][1; ]AT_VARIANT_IF([], [delete $][2]; )[} |
| ; |
| |
| item: |
| 'a' { $$ = $][1; } |
| | 'e' { YYUSE ($$); YYUSE ($][1); error ("syntax error"); } |
| // Not just 'E', otherwise we reduce when 'E' is the lookahead, and |
| // then the stack is emptied, defeating the point of the test. |
| | 'E' 'a' { YYUSE ($][1); $$ = $][2; } |
| | 'R' { ]AT_VARIANT_IF([], [$$ = YY_NULLPTR; delete $][1]; )[YYERROR; } |
| | 'p' { $$ = $][1; } |
| | 's' { $$ = $][1; throw std::runtime_error ("reduction"); } |
| | 'T' { ]AT_VARIANT_IF([], [$$ = YY_NULLPTR; delete $][1]; )[YYABORT; } |
| ]m4_if([$2], [with], |
| [[| error { $$ = ]AT_VARIANT_IF([], [new ])[Object ('R'); yyerrok; }]])[ |
| ; |
| %% |
| |
| int |
| yylex (yy::parser::semantic_type *lvalp) |
| { |
| // 'a': no error. |
| // 'e': user action calls error. |
| // 'E': syntax error, with yyerror that throws. |
| // 'i': initial action throws. |
| // 'l': yylex throws. |
| // 'R': call YYERROR in the action |
| // 's': reduction throws. |
| // 'T': call YYABORT in the action |
| switch (char res = *input++) |
| { |
| case 'l': |
| throw std::runtime_error ("yylex"); |
| default: |
| lvalp->]AT_VARIANT_IF([build<Object> (res)], |
| [obj = new Object (res)])[; |
| goto zero; |
| zero: |
| case 0: |
| return res; |
| } |
| } |
| |
| /* A C++ error reporting function. */ |
| void |
| yy::parser::error (const std::string& m) |
| { |
| throw std::runtime_error (m); |
| } |
| |
| int |
| main (int argc, const char *argv[]) |
| { |
| switch (argc) |
| { |
| case 2: |
| input = argv[1]; |
| break; |
| case 3: |
| assert (std::string(argv[1]) == "--debug"); |
| debug = 1; |
| input = argv[2]; |
| break; |
| default: |
| abort (); |
| } |
| |
| yy::parser parser; |
| debug |= !!getenv ("YYDEBUG"); |
| parser.set_debug_level (debug); |
| int res = 2; |
| try |
| { |
| res = parser.parse (); |
| } |
| catch (const std::exception& e) |
| { |
| std::cerr << "exception caught: " << e.what () << '\n'; |
| } |
| catch (...) |
| { |
| std::cerr << "unknown exception caught\n"; |
| } |
| Object::log (YY_NULLPTR, "end"); |
| assert (Object::empty()); |
| return res; |
| } |
| ]]) |
| AT_BISON_CHECK([[-o input.cc --report=all input.yy]]) |
| |
| AT_FOR_EACH_CXX([ |
| AT_COMPILE_CXX([[input]]) |
| |
| AT_PARSER_CHECK([[input aaaas]], [[2]], [[]], |
| [[exception caught: reduction |
| ]]) |
| |
| AT_PARSER_CHECK([[input aaaal]], [[2]], [[]], |
| [[exception caught: yylex |
| ]]) |
| |
| AT_PARSER_CHECK([[input i]], [[2]], [[]], |
| [[exception caught: initial-action |
| ]]) |
| |
| AT_PARSER_CHECK([[input aaaap]]) |
| |
| AT_PARSER_CHECK([[input --debug aaaap]], [[2]], [[]], [[stderr]]) |
| AT_CHECK([[grep '^exception caught: printer$' stderr]], [], [ignore]) |
| |
| AT_PARSER_CHECK([[input aaaae]], [[2]], [[]], |
| [[exception caught: syntax error |
| ]]) |
| |
| AT_PARSER_CHECK([[input aaaaE]], [[2]], [[]], |
| [[exception caught: syntax error, unexpected $end, expecting 'a' |
| ]]) |
| |
| AT_PARSER_CHECK([[input aaaaT]], [[1]]) |
| |
| AT_PARSER_CHECK([[input aaaaR]], [m4_if([$2], [with], [0], [1])]) |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| |
| AT_CLEANUP |
| ]) |
| |
| AT_TEST([], [with]) |
| AT_TEST([], [without]) |
| AT_TEST([%define api.value.type variant], [with]) |
| AT_TEST([%define api.value.type variant], [without]) |
| |
| m4_popdef([AT_TEST]) |
| |
| ## ------------------------------------- ## |
| ## C++ GLR parser identifier shadowing. ## |
| ## ------------------------------------- ## |
| |
| AT_SETUP([[C++ GLR parser identifier shadowing]]) |
| |
| AT_BISON_OPTION_PUSHDEFS |
| AT_DATA_GRAMMAR([input.yy], [ |
| %skeleton "glr.cc" |
| |
| %union |
| { |
| int ival; |
| } |
| |
| %token <ival> ZERO; |
| |
| %code |
| { |
| int yylex (yy::parser::semantic_type *lvalp); |
| } |
| |
| %% |
| exp: ZERO |
| |
| %% |
| |
| int yylex (yy::parser::semantic_type *lvalp) |
| { |
| // Note: this argument is unused, but named on purpose. There used to be a |
| // bug with a macro that erroneously expanded this identifier to |
| // yystackp->yyval. |
| YYUSE (lvalp); |
| return yy::parser::token::ZERO; |
| } |
| |
| void yy::parser::error (std::string const&) |
| {} |
| |
| int main () |
| {} |
| ]) |
| |
| AT_BISON_CHECK([[-o input.cc input.yy]]) |
| AT_FOR_EACH_CXX([AT_COMPILE_CXX([[input]])]) |
| |
| AT_BISON_OPTION_POPDEFS |
| AT_CLEANUP |
| |
| |
| |
| ## ------------------ ## |
| ## Shared locations. ## |
| ## ------------------ ## |
| |
| AT_SETUP([Shared locations]) |
| |
| # AT_TEST([PREFIX], [DIRECTIVES]) |
| # ------------------------------- |
| # Generate and compile to *.o. Make sure there is no (allowed) YY* |
| # nor yy* identifiers in the header after applying api.prefix. Check |
| # that headers can be compiled by a C++ compiler. |
| m4_pushdef([AT_TEST], |
| [AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc" %define api.namespace {$1} $2]) |
| AT_LOC_PUSHDEF([begin.line], [begin.column], [end.line], [end.column]) |
| AT_DATA_GRAMMAR([$1.yy], |
| [[%skeleton "lalr1.cc" |
| %define api.namespace {$1} |
| $2 |
| %code { |
| ]AT_YYERROR_DECLARE[ |
| ]AT_YYLEX_DECLARE[ |
| } |
| %% |
| exp: '0'; |
| %% |
| ]AT_YYERROR_DEFINE[ |
| ]AT_YYLEX_DEFINE(["0"])[ |
| ]]) |
| |
| AT_BISON_CHECK([-fcaret -o $1.cc $1.yy]) |
| AT_LANG_COMPILE([$1.o], [], [-Iinclude]) |
| |
| AT_LOC_POPDEF |
| AT_BISON_OPTION_POPDEFS |
| ]) |
| |
| mkdir -p include/ast |
| |
| AT_TEST([x1], |
| [%defines |
| %locations |
| %define api.location.file "include/ast/loc.hh" |
| %define api.location.include {<ast/loc.hh>}]) |
| |
| # Check the CPP guard and Doxyen comments. |
| AT_CHECK([sed -ne '/INCLUDED/p;/\\file/{p;n;p;}' include/ast/loc.hh], [], |
| [[ ** \file ast/loc.hh |
| ** Define the x1::location class. |
| #ifndef YY_YY_AST_LOC_HH_INCLUDED |
| # define YY_YY_AST_LOC_HH_INCLUDED |
| #endif // !YY_YY_AST_LOC_HH_INCLUDED |
| ]]) |
| |
| AT_TEST([x2], |
| [%defines |
| %locations |
| %code requires {#include <ast/loc.hh>} |
| %define api.location.type {x1::location}]) |
| |
| m4_popdef([AT_TEST]) |
| |
| AT_DATA([main.cc], |
| [AT_DATA_SOURCE_PROLOGUE |
| [#include "x1.hh" |
| #include "x2.hh" |
| |
| #define RUN(S) \ |
| do { \ |
| S::parser parser; \ |
| int res = parser.parse(); \ |
| if (res) \ |
| std::cerr << #S": " << res << '\n'; \ |
| } while (false) |
| |
| int |
| main (void) |
| { |
| RUN(x1); |
| RUN(x2); |
| } |
| ]])# main.cc |
| |
| |
| AT_COMPILE_CXX([parser], [[x[12].o main.cc]], [-Iinclude]) |
| AT_PARSER_CHECK([parser], [0]) |
| |
| AT_CLEANUP |
| |
| |
| |
| ## ---------------- ## |
| ## Default action. ## |
| ## ---------------- ## |
| |
| # In C++ we generate explicitly the code for the default action |
| # instead of simply copying blindly the semantic value buffer. This |
| # is important when copying raw memory is not enough, as exemplified |
| # by move-only types. |
| |
| AT_SETUP([Default action]) |
| AT_KEYWORDS([action]) |
| |
| AT_BISON_OPTION_PUSHDEFS([%skeleton "lalr1.cc" |
| %define api.token.constructor |
| %define api.value.type variant]) |
| |
| AT_DATA_GRAMMAR([test.y], |
| [[%code requires { |
| #include <memory> // unique_ptr |
| } |
| %code { |
| ]AT_YYERROR_DECLARE[ |
| ]AT_YYLEX_DECLARE[ |
| } |
| ]AT_BISON_OPTIONS[ |
| %define api.value.automove |
| %token ONE TWO EOI 0 |
| %type <std::unique_ptr<int>> ONE TWO one two one.opt two.opt |
| %% |
| exp: one.opt two.opt { std::cout << *$][1 << ", " << *$][2 << '\n'; } |
| one.opt: one | %empty {} |
| two.opt: two | %empty {} |
| one: ONE |
| two: TWO |
| %% |
| ]AT_YYERROR_DEFINE[ |
| ]AT_YYLEX_DEFINE(["12"], |
| [ if (res == '1') |
| return yy::parser::make_ONE (std::make_unique<int> (10)); |
| else if (res == '2') |
| return yy::parser::make_TWO (std::make_unique<int> (20)); |
| else |
| return yy::parser::make_EOI (); |
| ])[ |
| ]AT_MAIN_DEFINE[ |
| ]]) |
| |
| AT_LANG_FOR_EACH_STD([ |
| AT_REQUIRE_CXX_STD(14, [echo "$at_std not supported"; continue]) |
| AT_FULL_COMPILE([[test]], [], [], [], [-fcaret]) |
| AT_PARSER_CHECK([[test]], 0, [[10, 20 |
| ]]) |
| ]) |
| |
| AT_BISON_OPTION_POPDEFS |
| |
| AT_CLEANUP |