examples: add an example with a reentrant parser in Flex+Bison

Suggested by Eric S. Raymond.
https://lists.gnu.org/archive/html/bison-patches/2019-02/msg00066.html

* examples/c/reentrant-calc/Makefile, examples/c/reentrant-calc/README.md,
* examples/c/reentrant-calc/parse.y, examples/c/reentrant-calc/scan.l
* examples/c/reentrant-calc/lexcalc.test,
* examples/c/reentrant-calc/local.mk:
New.
This commit is contained in:
Akim Demaille
2019-02-16 13:13:30 +01:00
parent 0adda755a2
commit 6289d673a0
15 changed files with 845 additions and 3 deletions

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 = reccalc
BISON = bison
FLEX = flex
XSLTPROC = xsltproc
all: $(BASE)
%.c %.h %.xml %.gv: %.y
$(BISON) $(BISONFLAGS) --defines --xml --graph=$*.gv -o $*.c $<
%.c %.h: %.l
$(FLEX) $(FLEXFLAGS) -o$*.c --header-file=$*.h $<
scan.o: parse.h
parse.o: scan.h
$(BASE): parse.o scan.o
$(CC) $(CFLAGS) -o $@ $^
run: $(BASE)
@echo "Type arithmetic expressions. Quit with ctrl-d."
./$<
html: parse.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.[ch]
clean:
rm -f $(CLEANFILES)

View File

@@ -0,0 +1,45 @@
# reccalc - recursive calculator with Flex and Bison
In this example the generated parser is pure and reentrant: it can be used
concurrently in different threads, or recursively. As a proof of this
reentrancy, expressions in parenthesis are tokenized as strings, and then
recursively parsed from the parser:
```
exp: STR
{
result r = parse_string ($1);
free ($1);
if (r.nerrs)
{
res->nerrs += r.nerrs;
YYERROR;
}
else
$$ = r.value;
}
```
<!---
Local Variables:
fill-column: 76
ispell-dictionary: "american"
End:
Copyright (C) 2018-2019 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,53 @@
## Copyright (C) 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/>.
reccalcdir = $(docdir)/%D%
## ------ ##
## Calc. ##
## ------ ##
check_PROGRAMS += %D%/reccalc
TESTS += %D%/reccalc.test
EXTRA_DIST += %D%/reccalc.test %D%/scan.l
%C%_reccalc_SOURCES = %D%/parse.y %D%/parse.h
nodist_%C%_reccalc_SOURCES = %D%/scan.h %D%/scan.c
BUILT_SOURCES += $(nodist_%C%_reccalc_SOURCES)
%D%/parse.c: $(dependencies)
# Tell Make that parse.o depends on scan.h, so that scan.h is built
# before parse.o. Obfuscate the name of the target, otherwise
# Automake removes its recipe for parse.o and leaves only our
# additional dependency.
DASH = -
%D%/reccalc$(DASH)parse.o: %D%/scan.h
%D%/scan.c %D%/scan.h: %D%/scan.stamp
@test -f $@ || rm -f %D%/scan.stamp
@test -f $@ || $(MAKE) $(AM_MAKEFLAGS) %D%/scan.stamp
%D%/scan.stamp: %D%/scan.l
$(AM_V_LEX)rm -f $@ $@.tmp
$(AM_V_at)$(MKDIR_P) %D%
$(AM_V_at)touch $@.tmp
$(AM_V_at) $(LEX) -o %D%/scan.c --header-file=%D%/scan.h $<
$(AM_V_at)mv $@.tmp $@
# Don't use gnulib's system headers.
%C%_reccalc_CPPFLAGS = -I$(top_srcdir)/%D% -I$(top_builddir)/%D%
dist_reccalc_DATA = %D%/parse.y %D%/scan.l %D%/Makefile %D%/README.md
CLEANFILES += %D%/reccalc %D%/*.o %D%/parse.[ch] %D%/scan.[ch] %D%/*.stamp
CLEANDIRS += %D%/*.dSYM

183
examples/c/reccalc/parse.y Normal file
View File

@@ -0,0 +1,183 @@
// Prologue (directives).
%expect 0
// Emitted in the header file, before the definition of YYSTYPE.
%code requires
{
typedef void* yyscan_t;
typedef struct
{
// Whether to print the intermediate results.
int verbose;
// Value of the last computation.
int value;
// Number of errors.
int nerrs;
} result;
}
// Emitted in the header file, after the definition of YYSTYPE.
%code provides
{
// Tell Flex the expected prototype of yylex.
// The scanner argument must be named yyscanner.
#define YY_DECL \
enum yytokentype yylex (YYSTYPE* yylval, yyscan_t yyscanner, result *res)
YY_DECL;
void yyerror (yyscan_t scanner, result *res, const char *msg, ...);
}
// Emitted on top of the implementation file.
%code top
{
#include <stdarg.h> // va_list.
#include <stdio.h> // printf.
#include <stdlib.h> // getenv.
}
%code
{
result parse_string (const char* cp);
result parse (void);
}
%define api.pure full
%define api.token.prefix {TOK_}
%define api.value.type union
%define parse.error verbose
%define parse.trace
%verbose
// Scanner and error count are exchanged between main, yyparse and yylex.
%param {yyscan_t scanner}{result *res}
%token
PLUS "+"
MINUS "-"
STAR "*"
SLASH "/"
EOL "end-of-line"
EOF 0 "end-of-file"
;
%token <int> NUM "number"
%type <int> exp
%printer { fprintf (yyo, "%d", $$); } <int>
%token <char*> STR "string"
%printer { fprintf (yyo, "\"%s\"", $$); } <char*>
%destructor { free ($$); } <char*>
// Precedence (from lowest to highest) and associativity.
%left "+" "-"
%left "*" "/"
%precedence UNARY
%%
// Rules.
input:
line
| input line
;
line:
exp eol
{
res->value = $exp;
if (res->verbose)
printf ("%d\n", $exp);
}
| error eol
{
yyerrok;
}
;
eol:
EOF
| EOL
;
exp:
NUM { $$ = $1; }
| exp "+" exp { $$ = $1 + $3; }
| exp "-" exp { $$ = $1 - $3; }
| exp "*" exp { $$ = $1 * $3; }
| exp "/" exp
{
if ($3 == 0)
{
yyerror (scanner, res, "invalid division by zero");
YYERROR;
}
else
$$ = $1 / $3;
}
| "+" exp %prec UNARY { $$ = + $2; }
| "-" exp %prec UNARY { $$ = - $2; }
| STR
{
result r = parse_string ($1);
free ($1);
if (r.nerrs)
{
res->nerrs += r.nerrs;
YYERROR;
}
else
$$ = r.value;
}
;
%%
// Epilogue (C code).
#include "scan.h"
result
parse (void)
{
yyscan_t scanner;
yylex_init (&scanner);
result res = {1, 0, 0};
yyparse (scanner, &res);
yylex_destroy (scanner);
return res;
}
result
parse_string (const char *str)
{
yyscan_t scanner;
yylex_init (&scanner);
YY_BUFFER_STATE buf = yy_scan_string (str ? str : "", scanner);
result res = {0, 0, 0};
yyparse (scanner, &res);
yy_delete_buffer (buf, scanner);
yylex_destroy (scanner);
return res;
}
void
yyerror (yyscan_t scanner, result *res,
const char *msg, ...)
{
(void) scanner;
va_list args;
va_start (args, msg);
vfprintf (stderr, msg, args);
va_end (args);
fputc ('\n', stderr);
res->nerrs += 1;
}
int
main (void)
{
// Possibly enable parser runtime debugging.
yydebug = !!getenv ("YYDEBUG");
result res = parse ();
// Exit on failure if there were errors.
return !!res.nerrs;
}

View File

@@ -0,0 +1,57 @@
#! /bin/sh
# Copyright (C) 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/>.
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
(((1)+(2))*((3)+(4)))
EOF
run 0 '21'
# Some really deep computation.
# for 4, gives 4 + (3 + (2 + (1 + (- (4 * (4 + 1)) / 2)))).
n=100
for i in $(seq 0 $n)
do
if [ "$i" -eq 0 ]; then
input="- ($n * ($n + 1)) / 2"
else
input="$i + ($input)"
fi
done
echo "$input" > input
run 0 '0'
cat >input <<EOF
() + ()
EOF
run 1 'err: syntax error, unexpected end-of-file, expecting + or - or number or string'
cat >input <<EOF
1 + $
EOF
run 1 'err: syntax error, invalid character: $
err: syntax error, unexpected end-of-line, expecting + or - or number or string'

85
examples/c/reccalc/scan.l Normal file
View File

@@ -0,0 +1,85 @@
/* Prologue (directives). -*- C++ -*- */
/* Disable Flex features we don't need, to avoid warnings. */
%option nodefault noinput nounput noyywrap
%option reentrant
%{
#include <assert.h>
#include <limits.h> /* INT_MIN */
#include <stdlib.h> /* strtol */
#include "parse.h"
%}
%x SC_STRING
%%
%{
int nesting = 0;
char *str = NULL;
int size = 0;
int capacity = 0;
#define STR_APPEND() \
do { \
if (capacity < size + yyleng + 1) \
{ \
do \
capacity = capacity ? 2 * capacity : 128; \
while (capacity < size + yyleng + 1); \
str = realloc (str, capacity); \
} \
strncpy (str + size, yytext, yyleng); \
size += yyleng; \
assert (size < capacity); \
} while (0)
%}
// Rules.
"+" return TOK_PLUS;
"-" return TOK_MINUS;
"*" return TOK_STAR;
"/" return TOK_SLASH;
"(" nesting += 1; BEGIN SC_STRING;
/* Scan an integer. */
[0-9]+ {
errno = 0;
long n = strtol (yytext, NULL, 10);
if (! (INT_MIN <= n && n <= INT_MAX && errno != ERANGE))
yyerror (yyscanner, res, "integer is out of range");
yylval->TOK_NUM = (int) n;
return TOK_NUM;
}
/* Ignore white spaces. */
[ \t]+ continue;
"\n" return TOK_EOL;
. yyerror (yyscanner, res, "syntax error, invalid character: %c", yytext[0]);
<SC_STRING>
{
"("+ nesting += yyleng; STR_APPEND ();
")" {
if (!--nesting)
{
BEGIN INITIAL;
if (str)
str[size] = 0;
yylval->TOK_STR = str;
return TOK_STR;
}
else
STR_APPEND ();
}
[^()]+ STR_APPEND ();
}
<<EOF>> return TOK_EOF;
%%
/* Epilogue (C code). */