diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | examples/c/README.md | 16 | ||||
-rw-r--r-- | examples/c/bistromathic/Makefile | 35 | ||||
-rw-r--r-- | examples/c/bistromathic/README.md | 32 | ||||
-rwxr-xr-x | examples/c/bistromathic/bistromathic.test | 48 | ||||
-rw-r--r-- | examples/c/bistromathic/local.mk | 36 | ||||
-rw-r--r-- | examples/c/bistromathic/parse.y | 225 | ||||
-rw-r--r-- | examples/c/bistromathic/scan.l | 61 | ||||
-rw-r--r-- | examples/c/local.mk | 1 | ||||
-rwxr-xr-x | examples/test | 4 |
10 files changed, 455 insertions, 5 deletions
diff --git a/Makefile.am b/Makefile.am index e357db17..677f9b01 100644 --- a/Makefile.am +++ b/Makefile.am @@ -42,7 +42,7 @@ dependencies = $(BISON_IN) $(src_bison_SOURCES) $(dist_skeletons_DATA) BISON = $(top_builddir)/tests/bison BISON_IN = $(top_srcdir)/tests/bison.in YACC = $(BISON) -o y.tab.c -AM_YFLAGS_WITH_LINES = --defines -Werror -Wall --report=all +AM_YFLAGS_WITH_LINES = --defines -Werror -Wall,dangling-alias --report=all AM_YFLAGS = $(AM_YFLAGS_WITH_LINES) --no-lines # Initialization before completion by local.mk's. diff --git a/examples/c/README.md b/examples/c/README.md index 5225afab..26110cf1 100644 --- a/examples/c/README.md +++ b/examples/c/README.md @@ -48,6 +48,14 @@ until the input is drained. This example is a straightforward conversion of the 'calc' example to the push-parser model. +## bistromathic - all the bells and whistles +This example demonstrates the best practices when using Bison. +- Its interface is pure. +- It uses a custom syntax error with location tracking, lookahead correction + and token internationalization. +- It enables debug trace support with formatting of semantic values. + +It also uses Flex to generate the scanner. <!--- @@ -65,6 +73,8 @@ Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the "GNU Free Documentation License" file as part of this distribution. -# LocalWords: mfcalc calc parsers yy rpcalc lexcalc redux reccalc ispell -# LocalWords: reentrant tokenized american postfix ----> +LocalWords: mfcalc calc parsers yy rpcalc lexcalc redux reccalc ispell +LocalWords: reentrant tokenized american postfix pushcalc bistromathic +LocalWords: lookahead + +--> diff --git a/examples/c/bistromathic/Makefile b/examples/c/bistromathic/Makefile new file mode 100644 index 00000000..359d0ac5 --- /dev/null +++ b/examples/c/bistromathic/Makefile @@ -0,0 +1,35 @@ +# This Makefile is designed to be simple and readable. It does not +# aim at portability. It requires GNU Make. + +BASE = bistromathic +BISON = bison +FLEX = flex +XSLTPROC = xsltproc + +all: $(BASE) + +%.c %.h %.xml %.gv: %.y + $(BISON) $(BISONFLAGS) --defines --xml --graph=$*.gv -o $*.c $< + +%.c: %.l + $(FLEX) $(FLEXFLAGS) -o$*.c $< + +scan.o: parse.h +$(BASE): parse.o scan.o + $(CC) $(CFLAGS) -o $@ $^ + +run: $(BASE) + @echo "Type bistromathic expressions. Quit with ctrl-d." + ./$< + +html: $(BASE).html +%.html: %.xml + $(XSLTPROC) $(XSLTPROCFLAGS) -o $@ $$($(BISON) --print-datadir)/xslt/xml2xhtml.xsl $< + +CLEANFILES = \ + $(BASE) *.o \ + parse.[ch] parse.output parse.xml parse.html parse.gv \ + scan.c + +clean: + rm -f $(CLEANFILES) diff --git a/examples/c/bistromathic/README.md b/examples/c/bistromathic/README.md new file mode 100644 index 00000000..764866e3 --- /dev/null +++ b/examples/c/bistromathic/README.md @@ -0,0 +1,32 @@ +# bistromathic - all the bells and whistles +This example demonstrates the best practices when using Bison. +- Its interface is pure. +- It uses a custom syntax error with location tracking, lookahead correction + and token internationalization. +- It enables debug trace support with formatting of semantic values. + +It also uses Flex to generate the scanner. + +<!--- +Local Variables: +fill-column: 76 +ispell-dictionary: "american" +End: + +Copyright (C) 2020 Free Software Foundation, Inc. + +This file is part of Bison, the GNU Compiler Compiler. + +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/>. +---> diff --git a/examples/c/bistromathic/bistromathic.test b/examples/c/bistromathic/bistromathic.test new file mode 100755 index 00000000..4d19c98b --- /dev/null +++ b/examples/c/bistromathic/bistromathic.test @@ -0,0 +1,48 @@ +#! /bin/sh + +# Copyright (C) 2020 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/>. + +cat >input <<EOF +1+2*3 +EOF +run 0 7 + +cat >input <<EOF +(1+2) * 3 +EOF +run 0 9 +run -noerr 0 9 -p + +cat >input <<EOF +a = 256 +sqrt (a) +EOF +run 0 '256 +16' + +cat >input <<EOF +a = .16 +b = 10 ^ 2 +sqrt (a * b) +EOF +run 0 '0.16 +100 +4' + +cat >input <<EOF +* +EOF +run 0 "err: 1.1: syntax error expected end of file or - or ( or end of line or double precision number or function or variable before *" diff --git a/examples/c/bistromathic/local.mk b/examples/c/bistromathic/local.mk new file mode 100644 index 00000000..78f2c937 --- /dev/null +++ b/examples/c/bistromathic/local.mk @@ -0,0 +1,36 @@ +## Copyright (C) 2020 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/>. + +bistromathicdir = $(docdir)/%D% + +## --------------- ## +## Bistromathics. ## +## --------------- ## + +if FLEX_WORKS + check_PROGRAMS += %D%/bistromathic + TESTS += %D%/bistromathic.test + EXTRA_DIST += %D%/bistromathic.test + nodist_%C%_bistromathic_SOURCES = %D%/parse.y %D%/parse.h %D%/scan.l + %D%/calc.c: $(dependencies) + + # Don't use gnulib's system headers. + %C%_bistromathic_CPPFLAGS = -I$(top_srcdir)/%D% -I$(top_builddir)/%D% + %C%_bistromathic_LDADD = -lm +endif FLEX_WORKS + +dist_bistromathic_DATA = %D%/parse.y %D%/scan.l %D%/Makefile %D%/README.md +CLEANFILES += %D%/parse.[ch] %D%/scan.c %D%/parse.output +CLEANDIRS += %D%/*.dSYM diff --git a/examples/c/bistromathic/parse.y b/examples/c/bistromathic/parse.y new file mode 100644 index 00000000..cac38f32 --- /dev/null +++ b/examples/c/bistromathic/parse.y @@ -0,0 +1,225 @@ +%require "3.6" + +%code top { + #include <ctype.h> // isdigit + #include <math.h> // cos, sin, etc. + #include <stddef.h> // ptrdiff_t + #include <stdio.h> // printf + #include <string.h> // strcmp +} + +%code requires { + // Function type. + typedef double (func_t) (double); + + // Data type for links in the chain of symbols. + typedef struct symrec symrec; + struct symrec + { + char *name; // name of symbol + int type; // type of symbol: either VAR or FUN + union + { + double var; // value of a VAR + func_t *fun; // value of a FUN + } value; + symrec *next; // link field + }; + + symrec *putsym (char const *name, int sym_type); + symrec *getsym (char const *name); +} + +%code provides { + #define YY_DECL \ + int yylex (YYSTYPE *yylval, YYLTYPE *yylloc) + YY_DECL; + + void yyerror (YYLTYPE *yylloc, char const *); +} + +%code { +#define N_ +#define _ +} + +// Don't share global variables between the scanner and the parser. +%define api.pure full + +// To avoid name clashes (e.g., with C's EOF) prefix token definitions +// with TOK_ (e.g., TOK_EOF). +%define api.token.prefix {TOK_} + +// Customized syntax error messages (see yyreport_syntax_error). +%define parse.error custom + +// with locations. +%locations + +// and acurate list of expected tokens. +%define parse.lac full + +// Generate the parser description file (calc.output). +%verbose + +// Generate YYSTYPE from the types assigned to symbols. +%define api.value.type union +%token + PLUS "+" + MINUS "-" + STAR "*" + SLASH "/" + CARET "^" + LPAREN "(" + RPAREN ")" + EQUAL "=" + EOL _("end of line") + EOF 0 _("end of file") + <double> + NUM _("double precision number") + <symrec*> + FUN _("function") + VAR _("variable") + +%nterm <double> exp + +// Enable run-time traces (yydebug). +%define parse.trace + +// Formatting semantic values in debug traces. +%printer { fprintf (yyo, "%s", $$->name); } VAR; +%printer { fprintf (yyo, "%s()", $$->name); } FUN; +%printer { fprintf (yyo, "%g", $$); } <double>; + + +// Precedence (from lowest to highest) and associativity. +%precedence "=" +%left "+" "-" +%left "*" "/" +%precedence NEG // negation--unary minus +%right "^" // exponentiation + +%% // The grammar follows. +input: + %empty +| input line +; + +line: + EOL +| exp EOL { printf ("%.10g\n", $1); } +| error EOL { yyerrok; } +; + +exp: + NUM +| VAR { $$ = $1->value.var; } +| VAR "=" exp { $$ = $3; $1->value.var = $3; } +| FUN "(" exp ")" { $$ = $1->value.fun ($3); } +| exp "+" exp { $$ = $1 + $3; } +| exp "-" exp { $$ = $1 - $3; } +| exp "*" exp { $$ = $1 * $3; } +| exp "/" exp + { + if ($3 == 0) + { + yyerror (&@$, "division by zero"); + YYERROR; + } + else + $$ = $1 / $3; + } +| "-" exp %prec NEG { $$ = -$2; } +| exp "^" exp { $$ = pow ($1, $3); } +| "(" exp ")" { $$ = $2; } +; + +// End of grammar. +%% + +struct init +{ + char const *name; + func_t *fun; +}; + +static struct init const funs[] = +{ + { "atan", atan }, + { "cos", cos }, + { "exp", exp }, + { "ln", log }, + { "sin", sin }, + { "sqrt", sqrt }, + { 0, 0 }, +}; + +// The symbol table: a chain of 'struct symrec'. +static symrec *sym_table; + +// Put functions in table. +static void +init_table (void) +{ + for (int i = 0; funs[i].name; i++) + { + symrec *ptr = putsym (funs[i].name, TOK_FUN); + ptr->value.fun = funs[i].fun; + } +} + +symrec * +putsym (char const *name, int sym_type) +{ + symrec *res = (symrec *) malloc (sizeof (symrec)); + res->name = strdup (name); + res->type = sym_type; + res->value.var = 0; // Set value to 0 even if fun. + res->next = sym_table; + sym_table = res; + return res; +} + +symrec * +getsym (char const *name) +{ + for (symrec *p = sym_table; p; p = p->next) + if (strcmp (p->name, name) == 0) + return p; + return NULL; +} + +int +yyreport_syntax_error (const yyparse_context_t *ctx) +{ + enum { ARGMAX = 10 }; + int arg[ARGMAX]; + int n = yysyntax_error_arguments (ctx, arg, ARGMAX); + if (n == -2) + return 2; + YY_LOCATION_PRINT (stderr, *yyparse_context_location (ctx)); + fprintf (stderr, ": syntax error"); + for (int i = 1; i < n; ++i) + fprintf (stderr, " %s %s", + i == 1 ? "expected" : "or", yysymbol_name (arg[i])); + if (n) + fprintf (stderr, " before %s", yysymbol_name (arg[0])); + fprintf (stderr, "\n"); + return 0; +} + +// Called by yyparse on error. +void yyerror (YYLTYPE *loc, char const *s) +{ + YY_LOCATION_PRINT (stderr, *loc); + fprintf (stderr, ": %s\n", s); +} + +int main (int argc, char const* argv[]) +{ + // Enable parse traces on option -p. + if (argc == 2 && strcmp(argv[1], "-p") == 0) + yydebug = 1; + init_table (); + return yyparse (); +} diff --git a/examples/c/bistromathic/scan.l b/examples/c/bistromathic/scan.l new file mode 100644 index 00000000..c5f57c1e --- /dev/null +++ b/examples/c/bistromathic/scan.l @@ -0,0 +1,61 @@ +/* Prologue (directives). -*- C -*- */ + +/* Disable Flex features we don't need, to avoid warnings. */ +%option nodefault noinput nounput noyywrap + +%{ +#include <errno.h> /* errno, ERANGE */ +#include <limits.h> /* INT_MIN */ +#include <stdlib.h> /* strtol */ + +#include "parse.h" + + // Each time a rule is matched, advance the end cursor/position. +#define YY_USER_ACTION \ + yylloc->last_column += yyleng; +%} + +%% +%{ + // Each time yylex is called, move the head position to the end one. + yylloc->first_line = yylloc->last_line; + yylloc->first_column = yylloc->last_column; +%} + /* Rules. */ + +"+" return TOK_PLUS; +"-" return TOK_MINUS; +"*" return TOK_STAR; +"/" return TOK_SLASH; +"^" return TOK_CARET; + +"(" return TOK_LPAREN; +")" return TOK_RPAREN; + +"=" return TOK_EQUAL; + + /* Scan an identifier. */ +[a-z]+ { + symrec *s = getsym (yytext); + if (!s) + s = putsym (yytext, TOK_VAR); + yylval->TOK_VAR = s; + return s->type; +} + + /* Scan a double precision number. */ +[0-9]+(\.[0-9]*)?|(\.[0-9]+) { + sscanf (yytext, "%lf", &yylval->TOK_NUM); + return TOK_NUM; +} + +"\n" yylloc->last_line++; yylloc->last_column = 1; return TOK_EOL; + + /* Ignore white spaces. */ +[ \t]+ continue; + +<<EOF>> return TOK_EOF; + +. yyerror (yylloc, "syntax error, invalid character"); +%% +/* Epilogue (C code). */ diff --git a/examples/c/local.mk b/examples/c/local.mk index 9ed47a55..860d6994 100644 --- a/examples/c/local.mk +++ b/examples/c/local.mk @@ -16,6 +16,7 @@ cdir = $(docdir)/%D% dist_c_DATA = %D%/README.md +include %D%/bistromathic/local.mk include %D%/calc/local.mk include %D%/lexcalc/local.mk include %D%/mfcalc/local.mk diff --git a/examples/test b/examples/test index 79b8c994..d726a517 100755 --- a/examples/test +++ b/examples/test @@ -89,7 +89,9 @@ run () if test "$out_eff" = "$out_exp"; then echo "$me: PASS: $number" else - echo "$me: FAIL: $number (expected output: $out_exp, effective: $out_eff)" + echo "$me: FAIL: $number" + echo "$me: expected output: $out_exp" + echo "$me: effective output: $out_eff" cat err_eff exit=false fi |