tests: style: reorder the calculator test macros
Akim Demaille <[email protected]>
| Newsgroups | gmane.comp.parsers.bison.patches |
|---|---|
| Message-ID | <[email protected]> |
Let's make calc.at easier to read. commit c19c1e7ec5c579c66190ec35b010f6d3dca73f98 Author: Akim Demaille <[email protected]> Date: Sun Sep 27 12:16:31 2020 +0200 tests: style: reorder the calculator test macros * tests/local.at (AT_TOKEN_TRANSLATE_IF): New, moved from... * tests/calc.at: here. Instead of sorting per feature (main, yylex, calc.y) and then by language, do the converse, so that C bits are together, etc. diff --git a/tests/calc.at b/tests/calc.at index e017e5e4..0da2bbeb 100644 --- a/tests/calc.at +++ b/tests/calc.at @@ -19,16 +19,42 @@ ## Compile the grammar described in the documentation. ## ## ---------------------------------------------------- ## + +m4_pushdef([AT_CALC_MAIN], [AT_LANG_DISPATCH([$0], $@)]) +m4_pushdef([AT_CALC_YYLEX], [AT_LANG_DISPATCH([$0], $@)]) + # -------------- # -# AT_CALC_MAIN. # +# AT_DATA_CALC. # # -------------- # -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])])]) +# _AT_DATA_CALC_Y($1, $2, $3, [BISON-DIRECTIVES]) +# ----------------------------------------------- +# Produce 'calc.y' and, if %header 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 %header 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], $@)]) + + +## ----------- ## +## Calc in C. ## +## ----------- ## + +# AT_CALC_MAIN(c). m4_define([AT_CALC_MAIN(c)], [[#include <assert.h> #include <unistd.h> @@ -95,44 +121,8 @@ main (int argc, const char **argv) } ]]) -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], $@)]) - +# AT_CALC_YYLEX(c). m4_define([AT_CALC_YYLEX(c)], [[#include <ctype.h> @@ -234,224 +224,6 @@ read_integer (]AT_YYLEX_FORMALS[) } ]]) -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; - } - - TokenKind 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; - } - - switch (c) - { - case '+': return TokenKind.PLUS; - case '-': return TokenKind.MINUS; - case '*': return TokenKind.STAR; - case '/': return TokenKind.SLASH; - case '(': return TokenKind.LPAR; - case ')': return TokenKind.RPAR; - case '\n': return TokenKind.EOL; - case '=': return TokenKind.EQUAL; - case '^': return TokenKind.POW; - case '!': return TokenKind.NOT; - default: return TokenKind.YYUNDEF; - } - } -} -]]) - - -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 %header 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 %header 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], @@ -504,7 +276,6 @@ void location_print (FILE *o, Span s); %printer { ]AT_CXX_IF([[yyo << $$]], [[fprintf (yyo, "%d", $$)]])[; } <ival>; -]AT_LANG_MATCH([c\|c++], [[ %code provides { #include <stdio.h> @@ -542,7 +313,6 @@ void location_print (FILE *o, Span s); } ]])[ } -]])[ ]AT_LOCATION_TYPE_SPAN_IF([[ %initial-action @@ -571,7 +341,7 @@ input: line: '\n' -| exp '\n' { ]AT_PARAM_IF([*result = global_result = $1;], [AT_D_IF([], [USE ($1);])])[ } +| exp '\n' { ]AT_PARAM_IF([*result = global_result = $1;], [USE ($1);])[ } ; exp: @@ -686,8 +456,159 @@ AT_DATA_SOURCE([[calc-main.]AT_LANG_EXT], ])# _AT_DATA_CALC_Y(c) + +## ------------- ## +## Calc in C++. ## +## ------------- ## + +m4_copy([AT_CALC_MAIN(c)], [AT_CALC_MAIN(c++)]) +m4_copy([AT_CALC_YYLEX(c)], [AT_CALC_YYLEX(c++)]) m4_copy([_AT_DATA_CALC_Y(c)], [_AT_DATA_CALC_Y(c++)]) + +## ----------- ## +## Calc in D. ## +## ----------- ## + +# AT_CALC_MAIN(d). +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_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; + } + + TokenKind 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; + } + + switch (c) + { + case '+': return TokenKind.PLUS; + case '-': return TokenKind.MINUS; + case '*': return TokenKind.STAR; + case '/': return TokenKind.SLASH; + case '(': return TokenKind.LPAR; + case ')': return TokenKind.RPAR; + case '\n': return TokenKind.EOL; + case '=': return TokenKind.EQUAL; + case '^': return TokenKind.POW; + case '!': return TokenKind.NOT; + default: return TokenKind.YYUNDEF; + } + } +} +]]) + m4_define([_AT_DATA_CALC_Y(d)], [AT_DATA_GRAMMAR([calc.y], [[/* Infix notation calculator--calc */ @@ -733,7 +654,7 @@ input: line: EOL -| exp EOL { ]AT_PARAM_IF([*result = global_result = $1;], [AT_D_IF([], [USE ($1);])])[ } +| exp EOL { ]AT_PARAM_IF([*result = global_result = $1;], [USE ($1);])[ } ; exp: @@ -757,7 +678,7 @@ exp: | "-" exp %prec NEG { $$ = -$2; } | exp "^" exp { $$ = power ($1, $3); } | "(" exp ")" { $$ = $2; } -| "(" error ")" { $$ = 1111; ]AT_D_IF([], [yyerrok;])[ } +| "(" error ")" { $$ = 1111; yyerrok; } | "!" { $$ = 0; return YYERROR; } | "-" error { $$ = 0; return YYERROR; } ; @@ -778,6 +699,91 @@ power (int base, int exponent) AT_CALC_MAIN]) ])# _AT_DATA_CALC_Y(d) + + +## -------------- ## +## Calc in Java. ## +## -------------- ## + +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); + } +]]) + +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([], [[}]])[ +}; +]]) + m4_define([_AT_DATA_CALC_Y(java)], [AT_DATA_GRAMMAR([Calc.y], [[/* Infix notation calculator--calc */ @@ -868,6 +874,12 @@ exp: + +## ------------------ ## +## Calculator tests. ## +## ------------------ ## + + # AT_DATA_CALC_Y([BISON-OPTIONS]) # ------------------------------- # Produce 'calc.y' and, if %header was specified, 'calc-lex.c' or @@ -877,7 +889,6 @@ m4_define([AT_DATA_CALC_Y], ]) - # _AT_CHECK_CALC(BISON-OPTIONS, INPUT, [STDOUT], [NUM-STDERR-LINES]) # ------------------------------------------------------------------ # Run 'calc' on INPUT and expect no STDOUT nor STDERR. @@ -1404,6 +1415,5 @@ AT_CHECK_CALC_LALR1_JAVA([%define parse.trace %define parse.error custom %locati 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]) diff --git a/tests/local.at b/tests/local.at index 362b28cc..e07f0e51 100644 --- a/tests/local.at +++ b/tests/local.at @@ -340,6 +340,10 @@ m4_pushdef([AT_PURE_LEX_IF], [AT_PURE_IF([$1], [AT_CXX_IF([$1], [$2])])]) +# Whether token translation is supported. +m4_pushdef([AT_TOKEN_TRANSLATE_IF], +[AT_ERROR_CUSTOM_IF([$1], [AT_ERROR_DETAILED_IF([$1], [$2])])]) + m4_pushdef([AT_YYSTYPE], [AT_CXX_IF([AT_NAMESPACE[::parser::semantic_type]], [AT_API_PREFIX[STYPE]])]) @@ -409,6 +413,7 @@ m4_popdef([AT_YYLTYPE]) m4_popdef([AT_YYSTYPE]) m4_popdef([AT_VAL]) m4_popdef([AT_LOC]) +m4_popdef([AT_TOKEN_TRANSLATE_IF]) m4_popdef([AT_PURE_LEX_IF]) m4_popdef([AT_YYERROR_SEES_LOC_IF]) m4_popdef([AT_YYERROR_ARG_LOC_IF])