examples: add a complete example with all the bells and whistles

* examples/c/bistromathic/Makefile,
* examples/c/bistromathic/README.md,
* examples/c/bistromathic/bistromathic.test,
* examples/c/bistromathic/local.mk,
* examples/c/bistromathic/parse.y,
* examples/c/bistromathic/scan.l:
New.

* Makefile.am (AM_YFLAGS_WITH_LINES): Add -Wdangling-alias.
* examples/test: Make failure errors easier to read.
This commit is contained in:
Akim Demaille
2020-01-25 19:38:39 +01:00
parent 7bfff37f01
commit f374310119
10 changed files with 455 additions and 5 deletions

View File

@@ -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
-->

View File

@@ -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)

View File

@@ -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/>.
--->

View File

@@ -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 *"

View File

@@ -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

View File

@@ -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 ();
}

View File

@@ -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). */

View File

@@ -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