From a4dce889a44a7d363ce670585704876857543a5e Mon Sep 17 00:00:00 2001 From: Akim Demaille Date: Tue, 23 Oct 2018 20:14:47 +0200 Subject: [PATCH] doc: an introductory example for C++ Suggested by Victor Khomenko. http://lists.gnu.org/archive/html/bug-bison/2018-08/msg00037.html * doc/bison.texi (A Simple C++ Example): New. * examples/c++/local.mk, examples/c++/simple.test: New. Extract, check, and install this new example. * examples/local.mk: Adjust. * examples/test: Adjust to the case where the dirname differs from the test name. --- configure.ac | 3 +- doc/bison.texi | 229 ++++++++++++++++++++++++++++++++++++++- examples/c++/local.mk | 45 ++++++++ examples/c++/simple.test | 4 + examples/calc++/local.mk | 1 + examples/local.mk | 1 + examples/mfcalc/local.mk | 1 + examples/rpcalc/local.mk | 1 + examples/test | 5 +- 9 files changed, 283 insertions(+), 7 deletions(-) create mode 100644 examples/c++/local.mk create mode 100644 examples/c++/simple.test diff --git a/configure.ac b/configure.ac index f2240100..c81446ca 100644 --- a/configure.ac +++ b/configure.ac @@ -80,6 +80,7 @@ BISON_CXXSTD([14]) BISON_CXXSTD([17]) BISON_CXXSTD([2a]) AM_CONDITIONAL([ENABLE_CXX11], [test x"$CXX11_CXXFLAGS" != x]) +AM_CONDITIONAL([ENABLE_CXX14], [test x"$CXX14_CXXFLAGS" != x]) AC_LANG_POP([C++]) AC_ARG_ENABLE([gcc-warnings], @@ -161,7 +162,7 @@ if test "$enable_gcc_warnings" = yes; then do gl_WARN_ADD([$i], [WARN_CXXFLAGS_TEST]) done - # Clang++ 3.2+ reject C code generated by Flex. + # Clang++ 3.2+ rejects C code generated by Flex. gl_WARN_ADD([-Wno-null-conversion], [FLEX_SCANNER_CXXFLAGS]) # So does G++ 4.8... gl_WARN_ADD([-Wno-sign-compare], [FLEX_SCANNER_CXXFLAGS]) diff --git a/doc/bison.texi b/doc/bison.texi index ac2165ed..f6d240da 100644 --- a/doc/bison.texi +++ b/doc/bison.texi @@ -338,6 +338,7 @@ Parsers Written In Other Languages C++ Parsers +* A Simple C++ Example:: A short introduction to C++ parsers * C++ Bison Interface:: Asking for C++ parser generation * C++ Parser Interface:: Instantiating and running the parser * C++ Semantic Values:: %union vs. C++ @@ -10610,6 +10611,7 @@ The Bison parser in C++ is an object, an instance of the class @code{yy::parser}. @menu +* A Simple C++ Example:: A short introduction to C++ parsers * C++ Bison Interface:: Asking for C++ parser generation * C++ Parser Interface:: Instantiating and running the parser * C++ Semantic Values:: %union vs. C++ @@ -10618,6 +10620,224 @@ The Bison parser in C++ is an object, an instance of the class * A Complete C++ Example:: Demonstrating their use @end menu +@node A Simple C++ Example +@subsection A Simple C++ Example + +This tutorial about C++ parsers is based on a simple, self contained +example. The following sections are the reference manual for Bison with +C++, the last one showing a fully blown example (@pxref{A Complete C++ +Example}). + +To look nicer, our example will be in C++14. It is not required: Bison +supports the original C++98 standard. + +A Bison file has three parts. In the first part, the prologue, we start by +making sure we run a version of Bison which is recent enough, and that we +generate C++. + +@comment file: c++/simple.yy: 1 +@example +%require "@value{VERSION}" +%language "c++" +@end example + +Let's dive directly into the middle part: the grammar. Our input is a +simple list of strings, that we display once the parsing is done. + +@comment file: c++/simple.yy: 2 +@example +%% +@group +result: + list @{ std::cout << $1 << '\n'; @} +; +@end group + +%type > list; +@group +list: + %empty @{ /* Generates an empty string list */ @} +| list item @{ $$ = $1; $$.push_back ($2); @} +; +@end group +@end example + +We used a vector of strings as a semantic value! To use genuine C++ objects +as semantic values---not just PODs---we cannot rely on the union that Bison +uses by default to store them, we need @emph{variants} (@pxref{C++ +Variants}): + +@comment file: c++/simple.yy: 1 +@example +%define api.value.type variant +@end example + +Our list of strings will be built from two types of items: numbers and +strings: + +@comment file: c++/simple.yy: 2 +@example +%type item; +%token TEXT; +%token NUMBER; +@group +item: + TEXT +| NUMBER @{ $$ = to_string ($1); @} +; +@end group +@end example + +In the case of @code{TEXT}, the implicit default action applies: @w{@code{$$ += $1}.} We recommend that you keep the actions simple, and move details +into auxiliary functions, as we did with @code{to_string}, which we +implement in the prologue as follows: + +@comment file: c++/simple.yy: 1 +@example +%code +@{ + #include + +@group + // Convert to string. + template + auto to_string (const T& t) -> std::string + @{ + std::ostringstream o; + o << t; + return o.str (); + @} +@end group +@} +@end example + +Obviously, the rule for @code{result} needs to print a vector of strings. +Again, in the prologue, we add: + +@comment file: c++/simple.yy: 1 +@example +%code +@{ + // Print a list of strings. + auto + operator<< (std::ostream& o, const std::vector& ss) + -> std::ostream& + @{ + o << '@{'; + const char *sep = ""; +@group + for (const auto& s: ss) + @{ + o << sep << s; + sep = ", "; + @} +@end group + return o << '@}'; + @} +@} +@end example + +@noindent +You may want to move it into the @code{yy} namespace to avoid leaking it in +your default namespace. + +@sp 1 + +Our scanner deserves some attention. The traditional interface of +@code{yylex} is not type safe: since the token type and the token value are +not correlated, you may return a @code{NUMBER} with a string as semantic +value. To avoid this, we use @emph{token constructors} (@pxref{Complete +Symbols}). This directive: + +@comment file: c++/simple.yy: 1 +@example +%define api.token.constructor +@end example + +@noindent +requests that Bison generates the functions @code{make_TEXT} and +@code{make_NUMBER}. As a matter of fact, it is convenient to have also a +symbol to mark the end of input, say @code{END_OF_FILE}: + +@comment file: c++/simple.yy: 1 +@example +%token END_OF_FILE 0 +@end example + +@noindent +The @code{0} tells Bison this token is special: when it is reached, parsing +finishes. + +Everything is in place for our scanner: + +@comment file: c++/simple.yy: 1 +@example +%code +@{ + namespace yy + @{ + // Return the next token. + auto yylex () -> parser::symbol_type + @{ + static int count = 0; + switch (int stage = count++) + @{ +@group + case 0: + return parser::make_TEXT ("I have three numbers for you."); +@end group +@group + case 1: case 2: case 3: + return parser::make_NUMBER (stage); +@end group +@group + case 4: + return parser::make_TEXT ("And that's all!"); +@end group +@group + default: + return parser::make_END_OF_FILE (); +@end group + @} + @} + @} +@} +@end example + +In the epilogue, the third part of a Bison grammar file, we leave simple +details: the error reporting function, and the main function. + +@comment file: c++/simple.yy: 3 +@example +%% +namespace yy +@{ + // Report an error to the user. + auto parser::error (const std::string& msg) -> void + @{ + std::cerr << msg << '\n'; + @} +@} + +int main () +@{ + yy::parser parse; + return parse (); +@} +@end example + +Compile, and run! + +@example +$ bison simple.yy -o simple.cc +$ g++ -std=c++14 simple.cc -o simple +@group +$ ./simple +@{I have three numbers for you., 1, 2, 3, And that's all!@} +@end group +@end example + @node C++ Bison Interface @subsection C++ Bison Interface @c - %skeleton "lalr1.cc" @@ -13760,17 +13980,18 @@ London, Department of Computer Science, TR-00-12 (December 2000). @c LocalWords: subdirectory Solaris nonassociativity perror schemas Malloy ints @c LocalWords: Scannerless ispell american ChangeLog smallexample CSTYPE CLTYPE @c LocalWords: clval CDEBUG cdebug deftypeopx yyterminate LocationType yyo -@c LocalWords: parsers parser's documentencoding documentlanguage Wempty +@c LocalWords: parsers parser's documentencoding documentlanguage Wempty ss @c LocalWords: associativity subclasses precedences unresolvable runnable @c LocalWords: allocators subunit initializations unreferenced untyped dir @c LocalWords: errorVerbose subtype subtypes Wmidrule midrule's src rvalues -@c LocalWords: automove evolutions Wother Wconflicts PNG lookaheads Acc +@c LocalWords: automove evolutions Wother Wconflicts PNG lookaheads Acc sep @c LocalWords: xsltproc XSL xsl xhtml html num Wprecedence Werror fcaret @c LocalWords: fdiagnostics setlocale nullptr ast srcdir copyable iff drv -@c LocalWords: deftypefunx pragma Wnull dereference Wdocumentation elif -@c LocalWords: Wdeprecated Wregister noinput yyloc yypos +@c LocalWords: deftypefunx pragma Wnull dereference Wdocumentation elif ish +@c LocalWords: Wdeprecated Wregister noinput yyloc yypos PODs sstream Wsign @c Local Variables: @c ispell-dictionary: "american" @c fill-column: 76 @c End: +@c LocalWords: typename emplace Wconversion Wshorten diff --git a/examples/c++/local.mk b/examples/c++/local.mk new file mode 100644 index 00000000..438decba --- /dev/null +++ b/examples/c++/local.mk @@ -0,0 +1,45 @@ +## Copyright (C) 2018 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 . + +## -------------------- ## +## Building & testing. ## +## -------------------- ## + +BUILT_SOURCES += $(simple_sources) +CLEANFILES += %D%/simple.[ch] %D%/simple.output +CLEANDIRS += %D%/*.dSYM + +simple_extracted = %D%/simple.yy +simple_sources = $(simple_extracted) +extracted += $(simple_extracted) + +if ENABLE_CXX14 + check_PROGRAMS += %D%/simple + nodist_%C%_simple_SOURCES = $(simple_sources) + + %C%_simple_CXXFLAGS = $(CXX11_CXXFLAGS) + # Don't use gnulib's system headers. + %C%_simple_CPPFLAGS = -I$(top_builddir) + dist_TESTS += %D%/simple.test + %D%/simple.cc: $(BISON_IN) $(dist_pkgdata_DATA) +endif + + +## ------------ ## +## Installing. ## +## ------------ ## + +cxxdir = $(docdir)/examples/c++ +cxx_DATA = $(simple_extracted) diff --git a/examples/c++/simple.test b/examples/c++/simple.test new file mode 100644 index 00000000..8300c9dc --- /dev/null +++ b/examples/c++/simple.test @@ -0,0 +1,4 @@ +#! /bin/sh + +: >input +run 0 "{I have three numbers for you., 1, 2, 3, And that's all!}" diff --git a/examples/calc++/local.mk b/examples/calc++/local.mk index e0458dba..6809188e 100644 --- a/examples/calc++/local.mk +++ b/examples/calc++/local.mk @@ -69,6 +69,7 @@ check_PROGRAMS += %D%/calc++ nodist_%C%_calc___SOURCES = \ $(calcxx_sources) +# Don't use gnulib's system headers. %C%_calc___CPPFLAGS = -I$(top_builddir)/%D% %C%_calc___CXXFLAGS = $(AM_CXXFLAGS) $(FLEX_SCANNER_CXXFLAGS) dist_TESTS += %D%/calc++.test diff --git a/examples/local.mk b/examples/local.mk index 2e653363..81ecbfab 100644 --- a/examples/local.mk +++ b/examples/local.mk @@ -69,5 +69,6 @@ CLEANFILES += %D%/variant.output %D%/variant-11.output CLEANDIRS += %D%/*.dSYM include %D%/calc++/local.mk +include %D%/c++/local.mk include %D%/mfcalc/local.mk include %D%/rpcalc/local.mk diff --git a/examples/mfcalc/local.mk b/examples/mfcalc/local.mk index 192214bf..9df3b05e 100644 --- a/examples/mfcalc/local.mk +++ b/examples/mfcalc/local.mk @@ -28,6 +28,7 @@ extracted += $(mfcalc_extracted) check_PROGRAMS += %D%/mfcalc nodist_%C%_mfcalc_SOURCES = $(mfcalc_sources) +# Don't use gnulib's system headers. %C%_mfcalc_CPPFLAGS = -I$(top_builddir)/%D% %C%_mfcalc_LDADD = -lm diff --git a/examples/rpcalc/local.mk b/examples/rpcalc/local.mk index c6631508..48f6ac67 100644 --- a/examples/rpcalc/local.mk +++ b/examples/rpcalc/local.mk @@ -28,6 +28,7 @@ extracted += $(rpcalc_extracted) check_PROGRAMS += %D%/rpcalc nodist_%C%_rpcalc_SOURCES = $(rpcalc_sources) +# Don't use gnulib's system headers. %C%_rpcalc_CPPFLAGS = -I$(top_builddir)/%D% %C%_rpcalc_LDADD = -lm diff --git a/examples/test b/examples/test index 733c870b..e9db3d61 100755 --- a/examples/test +++ b/examples/test @@ -15,7 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -me=`basename $1 .test` +me=$(basename "$1" .test) +medir=$(basename "$(dirname "$1")") # Number of the current test. number=1 @@ -27,7 +28,7 @@ exit=true cwd=`pwd` # The exercised program. -for p in $cwd/examples/$me/$me $cwd/examples/$me +for p in "$cwd/examples/$medir/$me" "$cwd/examples/$me" do if test -x "$p"; then prog=$p