mirror of
https://git.savannah.gnu.org/git/bison.git
synced 2026-03-09 04:13:03 +00:00
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:
35
examples/c/reccalc/Makefile
Normal file
35
examples/c/reccalc/Makefile
Normal 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)
|
||||
45
examples/c/reccalc/README.md
Normal file
45
examples/c/reccalc/README.md
Normal 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/>.
|
||||
--->
|
||||
53
examples/c/reccalc/local.mk
Normal file
53
examples/c/reccalc/local.mk
Normal 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
183
examples/c/reccalc/parse.y
Normal 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;
|
||||
}
|
||||
57
examples/c/reccalc/reccalc.test
Normal file
57
examples/c/reccalc/reccalc.test
Normal 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
85
examples/c/reccalc/scan.l
Normal 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). */
|
||||
Reference in New Issue
Block a user