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])
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.