# Simple calculator. -*- Autotest -*- # Copyright (C) 2000-2015, 2018-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 . ## ---------------------------------------------------- ## ## Compile the grammar described in the documentation. ## ## ---------------------------------------------------- ## # -------------- # # AT_CALC_MAIN. # # -------------- # m4_pushdef([AT_CALC_MAIN], [AT_LANG_DISPATCH([$0], $@)]) # Whether token translation is supported. m4_pushdef([AT_TOKEN_TRANSLATE_IF], [AT_ERROR_CUSTOM_IF([$1], [AT_ERROR_DETAILED_IF([$1], [$2])])]) m4_define([AT_CALC_MAIN(c)], [[#include #include ]AT_CXX_IF([[ namespace { /* A C++ ]AT_NAME_PREFIX[parse that simulates the C signature. */ int ]AT_NAME_PREFIX[parse (]AT_PARAM_IF([semantic_value *result, int *count, int *nerrs]))[ { ]AT_NAME_PREFIX[::parser parser]AT_PARAM_IF([ (result, count, nerrs)])[; #if ]AT_API_PREFIX[DEBUG parser.set_debug_level (1); #endif return parser.parse (); } } ]])[ /* Value of the last computation. */ semantic_value global_result = 0; /* Total number of computations. */ int global_count = 0; /* Total number of errors. */ int global_nerrs = 0; /* A C main function. */ int main (int argc, const char **argv) {]AT_PARAM_IF([[ semantic_value result = 0; int count = 0; int nerrs = 0;]])[ int status; /* This used to be alarm (10), but that isn't enough time for a July 1995 vintage DEC Alphastation 200 4/100 system, according to Nelson H. F. Beebe. 100 seconds was enough for regular users, but the Hydra build farm, which is heavily loaded needs more. */ alarm (200); if (argc == 2) input = fopen (argv[1], "r"); else input = stdin; if (!input) { perror (argv[1]); return 3; } ]AT_CXX_IF([], [AT_DEBUG_IF([ ]AT_NAME_PREFIX[debug = 1;])])[ status = ]AT_NAME_PREFIX[parse (]AT_PARAM_IF([[&result, &count, &nerrs]])[); if (fclose (input)) perror ("fclose");]AT_PARAM_IF([[ assert (global_result == result); (void) result; assert (global_count == count); (void) count; assert (global_nerrs == nerrs); (void) nerrs; printf ("final: %d %d %d\n", global_result, global_count, global_nerrs);]])[ return status; } ]]) m4_copy([AT_CALC_MAIN(c)], [AT_CALC_MAIN(c++)]) m4_define([AT_CALC_MAIN(d)], [[int main (string[] args) {]AT_PARAM_IF([[ semantic_value result = 0; int count = 0;]])[ File input = args.length == 2 ? File (args[1], "r") : stdin; auto l = calcLexer (input); auto p = new YYParser (l);]AT_DEBUG_IF([[ p.setDebugLevel (1);]])[ return !p.parse (); } ]]) m4_define([AT_CALC_MAIN(java)], [[public static void main (String[] args) throws IOException {]AT_LEXPARAM_IF([[ Calc p = new Calc (System.in);]], [[ CalcLexer l = new CalcLexer (System.in); Calc p = new Calc (l);]])AT_DEBUG_IF([[ p.setDebugLevel (1);]])[ boolean success = p.parse (); if (!success) System.exit (1); } ]]) # --------------- # # AT_CALC_YYLEX. # # --------------- # m4_pushdef([AT_CALC_YYLEX], [AT_LANG_DISPATCH([$0], $@)]) m4_define([AT_CALC_YYLEX(c)], [[#include ]AT_YYLEX_DECLARE_EXTERN[ ]AT_LOCATION_IF([ static AT_YYLTYPE last_yylloc; ])[ static int get_char (]AT_YYLEX_FORMALS[) { int res = getc (input); ]AT_USE_LEX_ARGS[; ]AT_LOCATION_IF([ last_yylloc = AT_LOC; if (res == '\n') { AT_LOC_LAST_LINE++; AT_LOC_LAST_COLUMN = 1; } else AT_LOC_LAST_COLUMN++; ])[ return res; } static void unget_char (]AT_YYLEX_PRE_FORMALS[ int c) { ]AT_USE_LEX_ARGS[; ]AT_LOCATION_IF([ /* Wrong when C == '\n'. */ AT_LOC = last_yylloc; ])[ ungetc (c, input); } static int read_integer (]AT_YYLEX_FORMALS[) { int c = get_char (]AT_YYLEX_ARGS[); int res = 0; ]AT_USE_LEX_ARGS[; while (isdigit (c)) { res = 10 * res + (c - '0'); c = get_char (]AT_YYLEX_ARGS[); } unget_char (]AT_YYLEX_PRE_ARGS[ c); return res; } /*---------------------------------------------------------------. | Lexical analyzer returns an integer on the stack and the token | | NUM, or the ASCII character read if not a number. Skips all | | blanks and tabs, returns 0 for EOF. | `---------------------------------------------------------------*/ ]AT_YYLEX_PROTOTYPE[ { int c; /* Skip white spaces. */ do { ]AT_LOCATION_IF([ AT_LOC_FIRST_COLUMN = AT_LOC_LAST_COLUMN; AT_LOC_FIRST_LINE = AT_LOC_LAST_LINE; ])[ } while ((c = get_char (]AT_YYLEX_ARGS[)) == ' ' || c == '\t'); /* Process numbers. */ if (isdigit (c)) { unget_char (]AT_YYLEX_PRE_ARGS[ c); ]AT_VAL[.ival = read_integer (]AT_YYLEX_ARGS[); return ]AT_CXX_IF([AT_NAMESPACE::parser::token::])[]AT_TOKEN_PREFIX[NUM; } /* Return end-of-file. */ if (c == EOF) return ]AT_CXX_IF([AT_NAMESPACE::parser::token::])[]AT_TOKEN_PREFIX[CALC_EOF; /* An explicit error raised by the scanner. */ if (c == '#') {]AT_LOCATION_IF([ fprintf (stderr, "%d.%d: ", AT_LOC_FIRST_LINE, AT_LOC_FIRST_COLUMN);])[ fputs ("syntax error: invalid character: '#'\n", stderr); return ]AT_CXX_IF([AT_NAMESPACE::parser::token::])[]AT_TOKEN_PREFIX[]AT_API_PREFIX[error; } /* Return single chars. */ return c; } ]]) m4_copy([AT_CALC_YYLEX(c)], [AT_CALC_YYLEX(c++)]) m4_define([AT_CALC_YYLEX(d)], [[import std.range.primitives; import std.stdio; auto calcLexer(R)(R range) if (isInputRange!R && is (ElementType!R : dchar)) { return new CalcLexer!R(range); } auto calcLexer (File f) { import std.algorithm : map, joiner; import std.utf : byDchar; return f.byChunk(1024) // avoid making a syscall roundtrip per char .map!(chunk => cast(char[]) chunk) // because byChunk returns ubyte[] .joiner // combine chunks into a single virtual range of char .calcLexer; // forward to other overload } class CalcLexer(R) : Lexer if (isInputRange!R && is (ElementType!R : dchar)) { R input; this(R r) { input = r; } ]AT_YYERROR_DEFINE[ YYSemanticType semanticVal_;]AT_LOCATION_IF([[ YYLocation location = new YYLocation; public final @property YYPosition startPos() { return location.begin; } public final @property YYPosition endPos() { return location.end; } ]])[ public final @property YYSemanticType semanticVal() { return semanticVal_; } int parseInt () { auto res = 0; import std.uni : isNumber; while (input.front.isNumber) { res = res * 10 + (input.front - '0');]AT_LOCATION_IF([[ location.end.column += 1;]])[ input.popFront; } return res; } int yylex () {]AT_LOCATION_IF([[ location.begin = location.end;]])[ import std.uni : isWhite, isNumber; // Skip initial spaces while (!input.empty && input.front != '\n' && isWhite (input.front)) { input.popFront;]AT_LOCATION_IF([[ location.begin.column += 1; location.end.column += 1;]])[ } // EOF. if (input.empty) return TokenKind.CALC_EOF; // Numbers. if (input.front.isNumber) { semanticVal_.ival = parseInt; return TokenKind.NUM; } // Individual characters auto c = input.front;]AT_LOCATION_IF([[ if (c == '\n') { location.end.line += 1; location.end.column = 1; } else location.end.column += 1;]])[ input.popFront; // An explicit error raised by the scanner. */ if (c == '#') { stderr.writeln (]AT_LOCATION_IF([location, ": ", ])["syntax error: invalid character: '#'"); return TokenKind.YYerror; } return c; } } ]]) m4_define([AT_CALC_YYLEX(java)], [AT_LEXPARAM_IF([[%code lexer {]], [[%code epilogue { class CalcLexer implements Calc.Lexer {]])[ StreamTokenizer st;]AT_LOCATION_IF([[ PositionReader reader;]])[ public ]AT_LEXPARAM_IF([[YYLexer]], [[CalcLexer]])[ (InputStream is) {]AT_LOCATION_IF([[ reader = new PositionReader (new InputStreamReader (is)); st = new StreamTokenizer (reader);]], [[ st = new StreamTokenizer (new InputStreamReader (is));]])[ st.resetSyntax (); st.eolIsSignificant (true); st.wordChars ('0', '9'); } ]AT_LOCATION_IF([[ Position start = new Position (1, 0); Position end = new Position (1, 0); public Position getStartPos () { return new Position (start); } public Position getEndPos () { return new Position (end); } ]])[ ]AT_YYERROR_DEFINE[ Integer yylval; public Object getLVal () { return yylval; } public int yylex () throws IOException {;]AT_LOCATION_IF([[ start.set (reader.getPosition ());]])[ int tkind = st.nextToken ();]AT_LOCATION_IF([[ end.set (reader.getPosition ());]])[ switch (tkind) { case StreamTokenizer.TT_EOF: return CALC_EOF; case StreamTokenizer.TT_EOL:;]AT_LOCATION_IF([[ end.line += 1; end.column = 0;]])[ return (int) '\n'; case StreamTokenizer.TT_WORD: yylval = new Integer (st.sval);]AT_LOCATION_IF([[ end.set (reader.getPreviousPosition ());]])[ return NUM; case ' ': case '\t': return yylex (); case '#': System.err.println(]AT_LOCATION_IF([[start + ": " + ]])["syntax error: invalid character: '#'"); return YYerror; default: return tkind; } } ]AT_LEXPARAM_IF([], [[}]])[ }; ]]) # -------------- # # AT_DATA_CALC. # # -------------- # # _AT_DATA_CALC_Y($1, $2, $3, [BISON-DIRECTIVES]) # ----------------------------------------------- # Produce 'calc.y' and, if %defines was specified, 'calc-lex.c' or # 'calc-lex.cc'. # # Don't call this macro directly, because it contains some occurrences # of '$1' etc. which will be interpreted by m4. So you should call it # with $1, $2, and $3 as arguments, which is what AT_DATA_CALC_Y does. # # When %defines is not passed, generate a single self-contained file. # Otherwise, generate three: calc.y with the parser, calc-lex.c with # the scanner, and calc-main.c with "main()". This is in order to # stress the use of the generated parser header. To avoid code # duplication, AT_CALC_YYLEX and AT_CALC_MAIN contain the body of these # two later files. m4_pushdef([_AT_DATA_CALC_Y], [m4_if([$1$2$3], $[1]$[2]$[3], [], [m4_fatal([$0: Invalid arguments: $@])])dnl AT_LANG_DISPATCH([$0], $@)]) m4_define([_AT_DATA_CALC_Y(c)], [AT_DATA_GRAMMAR([calc.y], [[/* Infix notation calculator--calc */ ]$4[ ]AT_LANG_MATCH( [d], [[ %code imports { alias semantic_value = int; } ]], [c\|c++], [[ %code requires { ]AT_LOCATION_TYPE_SPAN_IF([[ typedef struct { int l; int c; } Point; typedef struct { Point first; Point last; } Span; # define YYLLOC_DEFAULT(Current, Rhs, N) \ do \ if (N) \ { \ (Current).first = YYRHSLOC (Rhs, 1).first; \ (Current).last = YYRHSLOC (Rhs, N).last; \ } \ else \ { \ (Current).first = (Current).last = YYRHSLOC (Rhs, 0).last; \ } \ while (0) ]AT_C_IF( [[#include void location_print (FILE *o, Span s); #define LOCATION_PRINT location_print ]])[ ]])[ /* Exercise pre-prologue dependency to %union. */ typedef int semantic_value; } ]])[ /* Exercise %union. */ %union { semantic_value ival; }; %printer { ]AT_CXX_IF([[yyo << $$]], [[fprintf (yyo, "%d", $$)]])[; } ; ]AT_LANG_MATCH([c\|c++], [[ %code provides { #include /* The input. */ extern FILE *input; extern semantic_value global_result; extern int global_count; extern int global_nerrs; } %code { #include #include #define USE(Var) FILE *input; static int power (int base, int exponent); ]AT_YYERROR_DECLARE[ ]AT_YYLEX_DECLARE_EXTERN[ ]AT_TOKEN_TRANSLATE_IF([[ #define N_ static const char * _ (const char *cp) { if (strcmp (cp, "end of input") == 0) return "end of file"; else if (strcmp (cp, "number") == 0) return "nombre"; else return cp; } ]])[ } ]])[ ]AT_LOCATION_TYPE_SPAN_IF([[ %initial-action { @$.first.l = @$.first.c = 1; @$.last = @$.first; }]])[ /* Bison Declarations */ %token CALC_EOF 0 ]AT_TOKEN_TRANSLATE_IF([_("end of input")], ["end of input"])[ %token NUM "number" %type exp %nonassoc '=' /* comparison */ %left '-' '+' %left '*' '/' %precedence NEG /* negation--unary minus */ %right '^' /* exponentiation */ /* Grammar follows */ %% input: line | input line { ]AT_PARAM_IF([++*count; ++global_count;])[ } ; line: '\n' | exp '\n' { ]AT_PARAM_IF([*result = global_result = $1;], [AT_D_IF([], [USE ($1);])])[ } ; exp: NUM | exp '=' exp { if ($1 != $3)]AT_LANG_CASE( [c], [[ { char buf[1024]; snprintf (buf, sizeof buf, "calc: error: %d != %d", $1, $3);]AT_YYERROR_ARG_LOC_IF([[ yyerror (&@$, ]AT_PARAM_IF([result, count, nerrs, ])[buf);]], [[ { YYLTYPE old_yylloc = yylloc; yylloc = @$; yyerror (]AT_PARAM_IF([result, count, nerrs, ])[buf); yylloc = old_yylloc; } ]])[ }]], [c++], [[ { char buf[1024]; snprintf (buf, sizeof buf, "calc: error: %d != %d", $1, $3); ]AT_GLR_IF([[yyparser.]])[error (]AT_LOCATION_IF([[@$, ]])[buf); }]], [d], [[ yyerror (]AT_LOCATION_IF([[@$, ]])[format ("calc: error: %d != %d", $1, $3));]])[ $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = power ($1, $3); } | '(' exp ')' { $$ = $2; } | '(' error ')' { $$ = 1111; ]AT_D_IF([], [yyerrok;])[ } | '!' { $$ = 0; ]AT_D_IF([return YYERROR], [YYERROR])[; } | '-' error { $$ = 0; ]AT_D_IF([return YYERROR], [YYERROR])[; } ; %% int power (int base, int exponent) { int res = 1; assert (0 <= exponent); for (/* Niente */; exponent; --exponent) res *= base; return res; } ]AT_LOCATION_TYPE_SPAN_IF([AT_CXX_IF([[ #include namespace { std::ostream& operator<< (std::ostream& o, const Span& s) { o << s.first.l << '.' << s.first.c; if (s.first.l != s.last.l) o << '-' << s.last.l << '.' << s.last.c - 1; else if (s.first.c != s.last.c - 1) o << '-' << s.last.c - 1; return o; } } ]], [[ void location_print (FILE *o, Span s) { fprintf (o, "%d.%d", s.first.l, s.first.c); if (s.first.l != s.last.l) fprintf (o, "-%d.%d", s.last.l, s.last.c - 1); else if (s.first.c != s.last.c - 1) fprintf (o, "-%d", s.last.c - 1); } ]])])[ ]AT_YYERROR_DEFINE[ ]AT_DEFINES_IF([], [AT_CALC_YYLEX AT_CALC_MAIN])]) AT_DEFINES_IF([AT_DATA_SOURCE([[calc-lex.]AT_LANG_EXT], [[#include "calc.]AT_LANG_HDR[" ]AT_CALC_YYLEX]) AT_DATA_SOURCE([[calc-main.]AT_LANG_EXT], [[#include "calc.]AT_LANG_HDR[" ]AT_CALC_MAIN]) ]) ])# _AT_DATA_CALC_Y m4_copy([_AT_DATA_CALC_Y(c)], [_AT_DATA_CALC_Y(c++)]) m4_copy([_AT_DATA_CALC_Y(c)], [_AT_DATA_CALC_Y(d)]) m4_define([_AT_DATA_CALC_Y(java)], [AT_DATA_GRAMMAR([Calc.y], [[/* Infix notation calculator--calc */ %define api.prefix {Calc} %define api.parser.class {Calc} %define public ]$4[ %code imports {]AT_LOCATION_IF([[ import java.io.BufferedReader;]])[ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StreamTokenizer; } %code { ]AT_CALC_MAIN[ ]AT_TOKEN_TRANSLATE_IF([[ static String i18n(String s) { if (s.equals ("end of input")) return "end of file"; else if (s.equals ("number")) return "nombre"; else return s; } ]])[ } /* Bison Declarations */ %token CALC_EOF 0 ]AT_TOKEN_TRANSLATE_IF([_("end of input")], ["end of input"])[ %token NUM "number" %type exp %nonassoc '=' /* comparison */ %left '-' '+' %left '*' '/' %precedence NEG /* negation--unary minus */ %right '^' /* exponentiation */ /* Grammar follows */ %% input: line | input line ; line: '\n' | exp '\n' ; exp: NUM | exp '=' exp { if ($1.intValue () != $3.intValue ()) yyerror (]AT_LOCATION_IF([[@$, ]])["calc: error: " + $1 + " != " + $3); } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '*' exp { $$ = $1 * $3; } | exp '/' exp { $$ = $1 / $3; } | '-' exp %prec NEG { $$ = -$2; } | exp '^' exp { $$ = (int) Math.pow ($1, $3); } | '(' exp ')' { $$ = $2; } | '(' error ')' { $$ = 1111; } | '!' { $$ = 0; return YYERROR; } | '-' error { $$ = 0; return YYERROR; } ; ]AT_CALC_YYLEX[ ]AT_LOCATION_IF([[ %% ]AT_JAVA_POSITION_DEFINE])[ ]]) ])# _AT_DATA_JAVA_CALC_Y # AT_DATA_CALC_Y([BISON-OPTIONS]) # ------------------------------- # Produce 'calc.y' and, if %defines was specified, 'calc-lex.c' or # 'calc-lex.cc'. m4_define([AT_DATA_CALC_Y], [_AT_DATA_CALC_Y($[1], $[2], $[3], [$1]) ]) # _AT_CHECK_CALC(BISON-OPTIONS, INPUT, [STDOUT], [NUM-STDERR-LINES]) # ------------------------------------------------------------------ # Run 'calc' on INPUT and expect no STDOUT nor STDERR. # # If BISON-OPTIONS contains '%debug' but not '%glr-parser', then # NUM-STDERR-LINES is the number of expected lines on stderr. # Currently this is ignored, though, since the output format is fluctuating. # # We don't count GLR's traces yet, since its traces are somewhat # different from LALR's. Likewise for D. # # The push traces are the same, except for "Return for a new token", don't # count them. m4_define([_AT_CHECK_CALC], [AT_DATA([[input]], [$2 ]) AT_JAVA_IF( [AT_JAVA_PARSER_CHECK([Calc < input], 0, [AT_PARAM_IF([m4_n([$3])])], [stderr])], [AT_PARSER_CHECK([calc input], 0, [AT_PARAM_IF([m4_n([$3])])], [stderr])]) AT_LANG_MATCH([c\|c++\|java], [AT_GLR_IF([], [AT_CHECK([grep -c -v 'Return for a new token:' stderr], [ignore], [m4_n([AT_DEBUG_IF([$4], [0])])])])]) ]) # _AT_CHECK_CALC_ERROR($1 = BISON-OPTIONS, $2 = EXIT-STATUS, $3 = INPUT, # $4 = [STDOUT], # $5 = [NUM-STDERR-LINES], # $6 = [CUSTOM-ERROR-MESSAGE]) # ---------------------------------------------------------------------- # Run 'calc' on INPUT, and expect a 'syntax error' message. # # If INPUT starts with a slash, it is used as absolute input file name, # otherwise as contents. # # NUM-STDERR-LINES is the number of expected lines on stderr. # If BISON-OPTIONS contains '%debug' but not '%glr', then NUM-STDERR-LINES # is the number of expected lines on stderr. # # CUSTOM-ERROR-MESSAGE is the expected error message when parse.error # is 'custom' and locations are enabled. Other expected formats are # computed from it. m4_define([_AT_CHECK_CALC_ERROR], [m4_bmatch([$3], [^/], [AT_JAVA_IF( [AT_JAVA_PARSER_CHECK([Calc < $3], $2, [AT_PARAM_IF([m4_n([$4])])], [stderr])], [AT_PARSER_CHECK([calc $3], $2, [AT_PARAM_IF([m4_n([$4])])], [stderr])])], [AT_DATA([[input]], [[$3 ]]) AT_JAVA_IF( [AT_JAVA_PARSER_CHECK([Calc < input], $2, [AT_PARAM_IF([m4_n([$4])])], [stderr])], [AT_PARSER_CHECK([calc input], $2, [AT_PARAM_IF([m4_n([$4])])], [stderr])]) ]) # Normalize the observed and expected error messages, depending upon the # options. # 1. Remove the traces from observed. sed '/^Starting/d /^Entering/d /^Stack/d /^Reading/d /^Reducing/d /^Return/d /^Shifting/d /^state/d /^Cleanup:/d /^Error:/d /^Next/d /^Now/d /^Discarding/d / \$[[0-9$]]* = /d /^yydestructor:/d' stderr >at-stderr mv at-stderr stderr # 2. Create the reference error message. AT_DATA([[expout]], [$6 ]) # 3. If locations are not used, remove them. AT_YYERROR_SEES_LOC_IF([], [[sed 's/^[-0-9.]*: //' expout >at-expout mv at-expout expout]]) # 4. If parse.error is not custom, turn the expected message to # the traditional one. AT_ERROR_CUSTOM_IF([], [ AT_PERL_REQUIRE([[-pi -e 'use strict; s{syntax error on token \[(.*?)\] \(expected: (.*)\)} { my $unexp = $][1; my @exps = $][2 =~ /\[(.*?)\]/g; ($][#exps && $][#exps < 4) ? "syntax error, unexpected $unexp, expecting @{[join(\" or \", @exps)]}" : "syntax error, unexpected $unexp"; }eg ' expout]]) ]) # 5. If parse.error is simple, strip the', unexpected....' part. AT_ERROR_SIMPLE_IF( [[sed 's/syntax error, .*$/syntax error/' expout >at-expout mv at-expout expout]]) # 6. Actually check. AT_CHECK([cat stderr], 0, [expout]) ]) # AT_CHECK_SPACES([FILES]) # ------------------------ # Make sure we did not introduce bad spaces. Checked here because all # the skeletons are (or should be) exercised here. m4_define([AT_CHECK_SPACES], [AT_PERL_CHECK([-ne ' chomp; print "$ARGV:$.: {$_}\n" if (# No starting/ending empty lines. (eof || $. == 1) && /^\s*$/ # No trailing space. || /\s$/ # No tabs. || /\t/ )' $1 ]) ]) # AT_CHECK_JAVA_GREP(FILE, [LINE], [COUNT=1]) # ------------------------------------------- # Check that FILE contains exactly COUNT lines matching ^LINE$ # with grep. Unquoted so that COUNT can be a shell expression. m4_define([AT_CHECK_JAVA_GREP], [AT_CHECK_UNQUOTED([grep -c '^$2$' $1], [ignore], [m4_default([$3], [1]) ])]) # AT_CHECK_CALC([BISON-OPTIONS], [COMPILER-OPTIONS]) # -------------------------------------------------- # Start a testing chunk which compiles 'calc' grammar with # BISON-OPTIONS, and performs several tests over the parser. m4_define([AT_CHECK_CALC], [m4_ifval([$3], [m4_fatal([$0: expected at most two arguments])]) # We use integers to avoid dependencies upon the precision of doubles. AT_SETUP([Calculator $1 $2]) AT_BISON_OPTION_PUSHDEFS([$1]) AT_DATA_CALC_Y([$1]) AT_FULL_COMPILE(AT_JAVA_IF([[Calc]], [[calc]]), AT_DEFINES_IF([[lex], [main]], [[], []]), [$2], [-Wno-deprecated]) AT_YACC_IF( [# No direct calls to malloc/free. AT_CHECK([[$EGREP '(malloc|free) *\(' calc.[ch] | $EGREP -v 'INFRINGES ON USER NAME SPACE']], [1])]) AT_PUSH_IF([AT_JAVA_IF( [# Verify that this is a push parser. AT_CHECK_JAVA_GREP([[Calc.java]], [[.*public void push_parse_initialize ().*]])])]) AT_CHECK_SPACES([AT_JAVA_IF([Calc], [calc]).AT_LANG_EXT AT_DEFINES_IF([AT_JAVA_IF([Calc], [calc]).AT_LANG_HDR])]) # Test the precedences. # The Java traces do not show the clean up sequence at the end, # since it does not support %destructor. _AT_CHECK_CALC([$1], [[1 + 2 * 3 = 7 1 + 2 * -3 = -5 -1^2 = -1 (-1)^2 = 1 ---1 = -1 1 - 2 - 3 = -4 1 - (2 - 3) = 2 2^2^3 = 256 (2^2)^3 = 64]], [[final: 64 12 0]], [AT_JAVA_IF([1014], [1017])]) # Some syntax errors. _AT_CHECK_CALC_ERROR([$1], [1], [1 2], [[final: 0 0 1]], [15], [AT_JAVA_IF([1.3-1.4], [1.3])[: syntax error on token [number] (expected: ['='] ['-'] ['+'] ['*'] ['/'] ['^'] ['\n'])]]) _AT_CHECK_CALC_ERROR([$1], [1], [1//2], [[final: 0 0 1]], [20], [AT_JAVA_IF([1.3-1.4], [1.3])[: syntax error on token ['/'] (expected: [number] ['-'] ['('] ['!'])]]) _AT_CHECK_CALC_ERROR([$1], [1], [error], [[final: 0 0 1]], [5], [AT_JAVA_IF([1.1-1.2], [1.1])[: syntax error on token [invalid token] (expected: [number] ['-'] ['\n'] ['('] ['!'])]]) _AT_CHECK_CALC_ERROR([$1], [1], [1 = 2 = 3], [[final: 0 0 1]], [30], [AT_LAC_IF( [AT_JAVA_IF([1.7-1.8], [1.7])[: syntax error on token ['='] (expected: ['-'] ['+'] ['*'] ['/'] ['^'] ['\n'])]], [AT_JAVA_IF([1.7-1.8], [1.7])[: syntax error on token ['='] (expected: ['-'] ['+'] ['*'] ['/'] ['^'])]])]) _AT_CHECK_CALC_ERROR([$1], [1], [ +1], [[final: 0 0 1]], [20], [AT_JAVA_IF([2.1-2.2], [2.1])[: syntax error on token ['+'] (expected: ]AT_TOKEN_TRANSLATE_IF([[[end of file]]], [[[end of input]]])[ [number] ['-'] ['\n'] ['('] ['!'])]]) # Exercise error messages with EOF: work on an empty file. _AT_CHECK_CALC_ERROR([$1], [1], [/dev/null], [[final: 0 0 1]], [4], [[1.1: syntax error on token ]AT_TOKEN_TRANSLATE_IF([[[end of file]]], [[[end of input]]])[ (expected: [number] ['-'] ['\n'] ['('] ['!'])]]) # Exercise the error token: without it, we die at the first error, # hence be sure to # # - have several errors which exercise different shift/discardings # - (): nothing to pop, nothing to discard # - (1 + 1 + 1 +): a lot to pop, nothing to discard # - (* * *): nothing to pop, a lot to discard # - (1 + 2 * *): some to pop and discard # # - test the action associated to 'error' # # - check the lookahead that triggers an error is not discarded # when we enter error recovery. Below, the lookahead causing the # first error is ")", which is needed to recover from the error and # produce the "0" that triggers the "0 != 1" error. # _AT_CHECK_CALC_ERROR([$1], [0], [() + (1 + 1 + 1 +) + (* * *) + (1 * 2 * *) = 1], [[final: 4444 0 5]], [250], [AT_JAVA_IF([1.2-1.3], [1.2])[: syntax error on token [')'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.18-1.19], [1.18])[: syntax error on token [')'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.23-1.24], [1.23])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.41-1.42], [1.41])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.1-1.47], [1.1-46])[: calc: error: 4444 != 1]]) # The same, but this time exercising explicitly triggered syntax errors. # POSIX says the lookahead causing the error should not be discarded. _AT_CHECK_CALC_ERROR([$1], [0], [(!) + (1 2) = 1], [[final: 2222 0 2]], [102], [AT_JAVA_IF([1.10-1.11], [1.10])[: syntax error on token [number] (expected: ['='] ['-'] ['+'] ['*'] ['/'] ['^'] [')']) ]AT_JAVA_IF([1.1-1.16], [1.1-15])[: calc: error: 2222 != 1]]) _AT_CHECK_CALC_ERROR([$1], [0], [(- *) + (1 2) = 1], [[final: 2222 0 3]], [113], [AT_JAVA_IF([1.4-1.5], [1.4])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.12-1.13], [1.12])[: syntax error on token [number] (expected: ['='] ['-'] ['+'] ['*'] ['/'] ['^'] [')']) ]AT_JAVA_IF([1.1-1.18], [1.1-17])[: calc: error: 2222 != 1]]) # Check that yyerrok works properly: second error is not reported, # third and fourth are. Parse status is successful. _AT_CHECK_CALC_ERROR([$1], [0], [(* *) + (*) + (*)], [[final: 3333 0 3]], [113], [AT_JAVA_IF([1.2-1.3], [1.2])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.10-1.11], [1.10])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!']) ]AT_JAVA_IF([1.16-1.17], [1.16])[: syntax error on token ['*'] (expected: [number] ['-'] ['('] ['!'])]]) # YYerror. # -------- # Check that returning YYerror from the scanner properly enters # error-recovery without issuing a second error message. _AT_CHECK_CALC_ERROR([$1], [0], [(#) + (#) = 2222], [[final: 2222 0 0]], [102], [[1.2: syntax error: invalid character: '#' 1.8: syntax error: invalid character: '#']]) _AT_CHECK_CALC_ERROR([$1], [0], [(1 + #) = 1111], [[final: 1111 0 0]], [102], [[1.6: syntax error: invalid character: '#']]) _AT_CHECK_CALC_ERROR([$1], [0], [(# + 1) = 1111], [[final: 1111 0 0]], [102], [[1.2: syntax error: invalid character: '#']]) _AT_CHECK_CALC_ERROR([$1], [0], [(1 + # + 1) = 1111], [[final: 1111 0 0]], [102], [[1.6: syntax error: invalid character: '#']]) AT_BISON_OPTION_POPDEFS AT_CLEANUP ])# AT_CHECK_CALC # ----------------- # # LALR Calculator. # # ----------------- # AT_BANNER([[LALR(1) Calculator.]]) # AT_CHECK_CALC_LALR([BISON-OPTIONS]) # ----------------------------------- # Start a testing chunk which compiles 'calc' grammar with # BISON-OPTIONS, and performs several tests over the parser. m4_define([AT_CHECK_CALC_LALR], [AT_CHECK_CALC($@)]) AT_CHECK_CALC_LALR([%define parse.trace]) AT_CHECK_CALC_LALR([%defines]) AT_CHECK_CALC_LALR([%debug %locations]) AT_CHECK_CALC_LALR([%locations %define api.location.type {Span}]) AT_CHECK_CALC_LALR([%name-prefix "calc"]) AT_CHECK_CALC_LALR([%verbose]) AT_CHECK_CALC_LALR([%yacc]) AT_CHECK_CALC_LALR([%define parse.error detailed]) AT_CHECK_CALC_LALR([%define parse.error verbose]) AT_CHECK_CALC_LALR([%define api.pure full %locations]) AT_CHECK_CALC_LALR([%define api.push-pull both %define api.pure full %locations]) AT_CHECK_CALC_LALR([%define parse.error detailed %locations]) AT_CHECK_CALC_LALR([%define parse.error detailed %locations %defines %define api.prefix {calc} %verbose %yacc]) AT_CHECK_CALC_LALR([%define parse.error detailed %locations %defines %name-prefix "calc" %define api.token.prefix {TOK_} %verbose %yacc]) AT_CHECK_CALC_LALR([%debug]) AT_CHECK_CALC_LALR([%define parse.error detailed %debug %locations %defines %name-prefix "calc" %verbose %yacc]) AT_CHECK_CALC_LALR([%define parse.error detailed %debug %locations %defines %define api.prefix {calc} %verbose %yacc]) AT_CHECK_CALC_LALR([%define api.pure full %define parse.error detailed %debug %locations %defines %name-prefix "calc" %verbose %yacc]) AT_CHECK_CALC_LALR([%define api.push-pull both %define api.pure full %define parse.error detailed %debug %locations %defines %define api.prefix {calc} %verbose %yacc]) AT_CHECK_CALC_LALR([%define api.pure %define parse.error detailed %debug %locations %defines %define api.prefix {calc} %verbose %yacc %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR([%no-lines %define api.pure %define parse.error detailed %debug %locations %defines %define api.prefix {calc} %verbose %yacc %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR([%no-lines %define api.pure %define parse.error verbose %debug %locations %defines %define api.prefix {calc} %verbose %yacc %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR([%define parse.error custom]) AT_CHECK_CALC_LALR([%define parse.error custom %locations %define api.prefix {calc}]) AT_CHECK_CALC_LALR([%define parse.error custom %locations %define api.prefix {calc} %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR([%define parse.error custom %locations %define api.prefix {calc} %parse-param {semantic_value *result}{int *count}{int *nerrs} %define api.push-pull both %define api.pure full]) AT_CHECK_CALC_LALR([%define parse.error custom %locations %define api.prefix {calc} %parse-param {semantic_value *result}{int *count}{int *nerrs} %define api.push-pull both %define api.pure full %define parse.lac full]) # ---------------- # # GLR Calculator. # # ---------------- # AT_BANNER([[GLR Calculator.]]) m4_define([AT_CHECK_CALC_GLR], [AT_CHECK_CALC([%glr-parser] $@)]) AT_CHECK_CALC_GLR() AT_CHECK_CALC_GLR([%defines]) AT_CHECK_CALC_GLR([%locations]) AT_CHECK_CALC_GLR([%locations %define api.location.type {Span}]) AT_CHECK_CALC_GLR([%name-prefix "calc"]) AT_CHECK_CALC_GLR([%define api.prefix {calc}]) AT_CHECK_CALC_GLR([%verbose]) AT_CHECK_CALC_GLR([%define parse.error verbose]) AT_CHECK_CALC_GLR([%define api.pure %locations]) AT_CHECK_CALC_GLR([%define parse.error verbose %locations]) AT_CHECK_CALC_GLR([%define parse.error custom %locations %defines %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR([%define parse.error detailed %locations %defines %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR([%define parse.error verbose %locations %defines %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR([%debug]) AT_CHECK_CALC_GLR([%define parse.error verbose %debug %locations %defines %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR([%define parse.error verbose %debug %locations %defines %define api.prefix {calc} %define api.token.prefix {TOK_} %verbose]) AT_CHECK_CALC_GLR([%define api.pure %define parse.error verbose %debug %locations %defines %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR([%define api.pure %define parse.error verbose %debug %locations %defines %name-prefix "calc" %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_GLR([%define api.pure %define parse.error verbose %debug %locations %defines %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_GLR([%no-lines %define api.pure %define parse.error verbose %debug %locations %defines %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) # ---------------------- # # LALR1 C++ Calculator. # # ---------------------- # AT_BANNER([[LALR(1) C++ Calculator.]]) # First let's try using %skeleton AT_CHECK_CALC([%skeleton "lalr1.cc" %defines]) m4_define([AT_CHECK_CALC_LALR1_CC], [AT_CHECK_CALC([%language "C++" $1], [$2])]) AT_CHECK_CALC_LALR1_CC([]) AT_CHECK_CALC_LALR1_CC([%locations]) AT_CHECK_CALC_LALR1_CC([%locations], [$NO_EXCEPTIONS_CXXFLAGS]) AT_CHECK_CALC_LALR1_CC([%locations %define api.location.type {Span}]) AT_CHECK_CALC_LALR1_CC([%defines %locations %define parse.error verbose %name-prefix "calc" %verbose]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.error verbose %define api.prefix {calc} %verbose]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.error verbose %debug %name-prefix "calc" %verbose]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.error verbose %debug %define api.prefix {calc} %verbose]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.error verbose %debug %define api.prefix {calc} %define api.token.prefix {TOK_} %verbose]) AT_CHECK_CALC_LALR1_CC([%defines %locations %define parse.error verbose %debug %name-prefix "calc" %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR1_CC([%define parse.error verbose %debug %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR1_CC([%defines %locations %define parse.error verbose %debug %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR1_CC([%defines %locations %define api.location.file none]) AT_CHECK_CALC_LALR1_CC([%defines %locations %define api.location.file "my-location.hh"]) AT_CHECK_CALC_LALR1_CC([%no-lines %defines %locations %define api.location.file "my-location.hh"]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.lac full %define parse.error verbose]) AT_CHECK_CALC_LALR1_CC([%locations %define parse.lac full %define parse.error detailed]) AT_CHECK_CALC_LALR1_CC([%define parse.error custom]) AT_CHECK_CALC_LALR1_CC([%define parse.error custom %locations %define api.prefix {calc} %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_LALR1_CC([%define parse.error custom %locations %define api.prefix {calc} %parse-param {semantic_value *result}{int *count}{int *nerrs} %define parse.lac full]) # -------------------- # # GLR C++ Calculator. # # -------------------- # AT_BANNER([[GLR C++ Calculator.]]) # Again, we try also using %skeleton. AT_CHECK_CALC([%skeleton "glr.cc"]) m4_define([AT_CHECK_CALC_GLR_CC], [AT_CHECK_CALC([%language "C++" %glr-parser] $@)]) AT_CHECK_CALC_GLR_CC([]) AT_CHECK_CALC_GLR_CC([%locations]) AT_CHECK_CALC_GLR_CC([%locations %define api.location.type {Span}]) AT_CHECK_CALC_GLR_CC([%defines %define parse.error verbose %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR_CC([%define parse.error verbose %define api.prefix {calc} %verbose]) AT_CHECK_CALC_GLR_CC([%debug]) AT_CHECK_CALC_GLR_CC([%define parse.error verbose %debug %name-prefix "calc" %verbose]) AT_CHECK_CALC_GLR_CC([%define parse.error verbose %debug %name-prefix "calc" %define api.token.prefix {TOK_} %verbose]) AT_CHECK_CALC_GLR_CC([%locations %defines %define parse.error verbose %debug %name-prefix "calc" %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_GLR_CC([%locations %defines %define parse.error verbose %debug %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) AT_CHECK_CALC_GLR_CC([%no-lines %locations %defines %define parse.error verbose %debug %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) # -------------------- # # LALR1 D Calculator. # # -------------------- # AT_BANNER([[LALR(1) D Calculator.]]) # First let's try using %skeleton AT_CHECK_CALC([%skeleton "lalr1.d"]) m4_define([AT_CHECK_CALC_LALR1_D], [AT_CHECK_CALC([%language "D" $1], [$2])]) AT_CHECK_CALC_LALR1_D([]) AT_CHECK_CALC_LALR1_D([%locations]) #AT_CHECK_CALC_LALR1_D([%locations %define api.location.type {Span}]) AT_CHECK_CALC_LALR1_D([%define parse.error verbose %define api.prefix {calc} %verbose]) AT_CHECK_CALC_LALR1_D([%debug]) AT_CHECK_CALC_LALR1_D([%define parse.error verbose %debug %verbose]) #AT_CHECK_CALC_LALR1_D([%define parse.error verbose %debug %define api.token.prefix {TOK_} %verbose]) #AT_CHECK_CALC_LALR1_D([%locations %define parse.error verbose %debug %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) #AT_CHECK_CALC_LALR1_D([%locations %define parse.error verbose %debug %define api.prefix {calc} %verbose %parse-param {semantic_value *result}{int *count}{int *nerrs}]) # ----------------------- # # LALR1 Java Calculator. # # ----------------------- # AT_BANNER([[LALR(1) Java Calculator.]]) m4_define([AT_CHECK_CALC_LALR1_JAVA], [AT_CHECK_CALC([%language "Java" $1], [$2])]) AT_CHECK_CALC_LALR1_JAVA AT_CHECK_CALC_LALR1_JAVA([%define parse.error custom]) AT_CHECK_CALC_LALR1_JAVA([%define parse.error detailed]) AT_CHECK_CALC_LALR1_JAVA([%define parse.error verbose]) AT_CHECK_CALC_LALR1_JAVA([%locations %define parse.error custom]) AT_CHECK_CALC_LALR1_JAVA([%locations %define parse.error detailed]) AT_CHECK_CALC_LALR1_JAVA([%locations %define parse.error verbose]) AT_CHECK_CALC_LALR1_JAVA([%define parse.trace %define parse.error verbose]) AT_CHECK_CALC_LALR1_JAVA([%define parse.trace %define parse.error verbose %locations %lex-param {InputStream is}]) AT_CHECK_CALC_LALR1_JAVA([%define api.push-pull both]) AT_CHECK_CALC_LALR1_JAVA([%define api.push-pull both %define parse.error detailed %locations]) AT_CHECK_CALC_LALR1_JAVA([%define parse.trace %define parse.error custom %locations %lex-param {InputStream is} %define api.push-pull both]) AT_CHECK_CALC_LALR1_JAVA([%define parse.trace %define parse.error verbose %locations %lex-param {InputStream is} %define api.push-pull both]) m4_popdef([AT_TOKEN_TRANSLATE_IF]) m4_popdef([AT_CALC_MAIN]) m4_popdef([AT_CALC_YYLEX])