lexer: Reimplement for better testability and internationalization.
[pspp-builds.git] / src / language / lexer / q2c.c
index 4adea9e2dd752cd153e8f54d1c842c0306b7d82f..f53ccfc33c181ad48cba48705f35e23d5cb2d552 100644 (file)
@@ -1,20 +1,18 @@
-/* q2c - parser generator for PSPP procedures.
-   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2008, 2010, 2011 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 2 of the
-   License, or (at your option) any later version.
+   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.
+   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, write to the Free Software
-   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-   02110-1301, USA. */
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
 
 #include <assert.h>
 #include <ctype.h>
@@ -85,8 +83,6 @@ static char *tokstr;
 \f
 /* Utility functions. */
 
-static char nullstr[] = "";
-
 /* Close all open files and delete the output file, on failure. */
 static void
 finish_up (void)
@@ -110,8 +106,8 @@ hcf (void)
   exit (EXIT_FAILURE);
 }
 
-int fail (const char *, ...) PRINTF_FORMAT (1, 2);
-int error (const char *, ...) PRINTF_FORMAT (1, 2);
+int fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN;
+int error (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN;
 
 /* Output an error message and terminate unsuccessfully. */
 int
@@ -229,11 +225,11 @@ st_upper (const char *s)
 /* Returns the address of the first non-whitespace character in S, or
    the address of the null terminator if none. */
 static char *
-skip_ws (const char *s)
+skip_ws (char *s)
 {
   while (isspace ((unsigned char) *s))
     s++;
-  return (char *) s;
+  return s;
 }
 
 /* Read one line from the input file into buf.  Lines having special
@@ -359,6 +355,45 @@ dump_token (void)
 }
 #endif /* DUMP_TOKENS */
 
+
+const char hyphen_proxy = '_';
+
+static void
+id_cpy (char **cp)
+{
+  char *dest = tokstr;
+  char *src = *cp;
+
+  while (*src == '_' || *src == '-' || isalnum ((unsigned char) *src))
+    {
+      *dest++ = *src == '-' ? hyphen_proxy :toupper ((unsigned char) (*src));
+      src++;
+    }
+
+  *cp = src;
+  *dest++ = '\0';
+}
+
+static char *
+unmunge (const char *s)
+{
+  char *dest = xmalloc (strlen (s) + 1);
+  char *d = dest;
+
+  while (*s)
+    {
+      if (*s == hyphen_proxy)
+       *d = '-';
+      else
+       *d = *s;
+      s++;
+      d++;
+    }
+  *d = '\0';
+
+  return dest;
+}
+
 /* Reads a token from the input file. */
 static int
 lex_get (void)
@@ -400,9 +435,8 @@ lex_get (void)
     {
       char *dest = tokstr;
       token = T_ID;
-      while (*cp == '_' || isalnum ((unsigned char) *cp))
-       *dest++ = toupper ((unsigned char) (*cp++));
-      *dest++ = '\0';
+
+      id_cpy (&cp);
     }
   else
     token = *cp++;
@@ -938,6 +972,19 @@ dump (int indention, const char *format, ...)
     indent += BASE_INDENT * indention;
 }
 
+/* Writes a blank line to the output file and adjusts 'indent' by BASE_INDENT
+   * INDENTION characters.
+
+   (This is only useful because GCC complains about using "" as a format
+   string, for whatever reason.) */
+static void
+dump_blank_line (int indention)
+{
+  oln++;
+  indent += BASE_INDENT * indention;
+  putc ('\n', out);
+}
+
 /* Write the structure members for specifier SPEC to the output file.
    SBC is the including subcommand. */
 static void
@@ -1033,7 +1080,7 @@ dump_declarations (void)
          if (buf == NULL)
            buf = xmalloc (1024);
          else
-           dump (0, buf);
+           dump (0, "%s", buf);
 
          if (k)
            sprintf (buf, "%s%s,", st_upper (prefix), sym->name);
@@ -1046,13 +1093,13 @@ dump_declarations (void)
     if (buf)
       {
        buf[strlen (buf) - 1] = 0;
-       dump (0, buf);
+       dump (0, "%s", buf);
        free (buf);
       }
     if (f)
       {
        dump (-1, "};");
-       dump (-1, nullstr);
+       dump_blank_line (-1);
       }
   }
 
@@ -1086,7 +1133,7 @@ dump_declarations (void)
            dump (0, "%s%scount", st_upper (prefix), st_upper (sbc->prefix));
 
            dump (-1, "};");
-           dump (-1, nullstr);
+           dump_blank_line (-1);
          }
        }
   }
@@ -1103,7 +1150,7 @@ dump_declarations (void)
        int f = 0;
 
        if (sbc != subcommands)
-         dump (0, nullstr);
+         dump_blank_line (0);
 
        dump (0, "/* %s subcommand. */", sbc->name);
        dump (0, "int sbc_%s;", st_lower (sbc->name));
@@ -1181,7 +1228,7 @@ dump_declarations (void)
       }
 
     dump (-1, "};");
-    dump (-1, nullstr);
+    dump_blank_line (-1);
   }
 
   /* Write out prototypes for custom_*() functions as necessary. */
@@ -1204,7 +1251,7 @@ dump_declarations (void)
        }
 
     if (seen)
-      dump (0, nullstr);
+      dump_blank_line (0);
   }
 
   /* Prototypes for parsing and freeing functions. */
@@ -1214,7 +1261,7 @@ dump_declarations (void)
          make_identifier (cmdname), make_identifier (cmdname));
     dump (0, "static void free_%s (struct cmd_%s *);",
          make_identifier (cmdname), make_identifier (cmdname));
-    dump (0, nullstr);
+    dump_blank_line (0);
   }
 }
 
@@ -1246,7 +1293,7 @@ dump_specifier_init (const specifier *spec, const subcommand *sbc)
 
            assert (s->value == VAL_INT || s->value == VAL_DBL
                     || s->value == VAL_STRING);
-           init = (s->value == VAL_INT ? "NOT_LONG"
+           init = (s->value == VAL_INT ? "LONG_MIN"
                     : s->value == VAL_DBL ? "SYSMIS"
                     : "NULL");
 
@@ -1341,7 +1388,7 @@ dump_vars_init (int persistent)
                dump (1, "{");
                dump (0, "int i;");
                dump (1, "for (i = 0; i < MAXLISTS; ++i)");
-               dump (0, "p->n_%s[i] = NOT_LONG;", st_lower (sbc->name));
+               dump (0, "p->n_%s[i] = LONG_MIN;", st_lower (sbc->name));
                dump (-2, "}");
                break;
 
@@ -1375,6 +1422,12 @@ make_match (const char *t)
            "|| lex_match_id (lexer, \"FALSE\"))");
   else if (isdigit ((unsigned char) t[0]))
     sprintf (s, "lex_match_int (lexer, %s)", t);
+  else if (strchr (t, hyphen_proxy))
+    {
+      char *c = unmunge (t);
+      sprintf (s, "lex_match_phrase (lexer, \"%s\")", c);
+      free (c);
+    }
   else
     sprintf (s, "lex_match_id (lexer, \"%s\")", t);
 
@@ -1440,12 +1493,12 @@ dump_specifier_parse (const specifier *spec, const subcommand *sbc)
            {
              if (s->optvalue)
                {
-                 dump (1, "if (lex_match (lexer, '('))");
+                 dump (1, "if (lex_match (lexer, T_LPAREN))");
                  dump (1, "{");
                }
              else
                {
-                 dump (1, "if (!lex_match (lexer, '('))");
+                 dump (1, "if (!lex_match (lexer, T_LPAREN))");
                  dump (1, "{");
                  dump (0, "msg (SE, _(\"`(' expected after %s "
                        "specifier of %s subcommand.\"));",
@@ -1483,7 +1536,7 @@ dump_specifier_parse (const specifier *spec, const subcommand *sbc)
           else if (s->value == VAL_STRING)
             {
               dump (1, "if (lex_token (lexer) != T_ID "
-                    "&& lex_token (lexer) != T_STRING)");
+                    "&& !lex_is_string (lexer))");
               dump (1, "{");
               dump (0, "msg (SE, _(\"%s specifier of %s subcommand "
                     "requires a string argument.\"));",
@@ -1491,7 +1544,7 @@ dump_specifier_parse (const specifier *spec, const subcommand *sbc)
              dump (0, "goto lossage;");
              dump (-1, "}");
               dump (-1, "free (p->%s%s);", sbc->prefix, st_lower (s->valname));
-              dump (0, "p->%s%s = xstrdup (ds_cstr (lex_tokstr (lexer)));",
+              dump (0, "p->%s%s = ss_xstrdup (ss_tokss (lexer));",
                     sbc->prefix, st_lower (s->valname));
             }
           else
@@ -1524,7 +1577,7 @@ dump_specifier_parse (const specifier *spec, const subcommand *sbc)
 
          if (s->valtype == VT_PAREN)
            {
-             dump (1, "if (!lex_match (lexer, ')'))");
+             dump (1, "if (!lex_match (lexer, T_RPAREN))");
              dump (1, "{");
              dump (0, "msg (SE, _(\"`)' expected after argument for "
                    "%s specifier of %s.\"));",
@@ -1560,7 +1613,7 @@ dump_subcommand (const subcommand *sbc)
     {
       int count;
 
-      dump (1, "while (lex_token (lexer) != '/' && lex_token (lexer) != '.')");
+      dump (1, "while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)");
       dump (1, "{");
 
       {
@@ -1618,7 +1671,7 @@ dump_subcommand (const subcommand *sbc)
          }
       }
 
-      dump (0, "lex_match (lexer, ',');");
+      dump (0, "lex_match (lexer, T_COMMA);");
       dump (-1, "}");
       outdent ();
     }
@@ -1654,7 +1707,7 @@ dump_subcommand (const subcommand *sbc)
       outdent ();
       if (sbc->restriction)
        {
-         dump (0, "x = ds_length (lex_tokstr (lexer));");
+         dump (0, "x = ss_length (lex_tokss (lexer));");
          dump (1, "if (!(%s))", sbc->restriction);
          dump (1, "{");
          dump (0, "msg (SE, _(\"String for %s must be %s.\"));",
@@ -1664,7 +1717,7 @@ dump_subcommand (const subcommand *sbc)
          outdent ();
        }
       dump (0, "free(p->s_%s);", st_lower(sbc->name) );
-      dump (0, "p->s_%s = ds_xstrdup (lex_tokstr (lexer));",
+      dump (0, "p->s_%s = ss_xstrdup (lex_tokss (lexer));",
            st_lower (sbc->name));
       dump (0, "lex_get (lexer);");
       if (sbc->restriction)
@@ -1704,11 +1757,11 @@ dump_subcommand (const subcommand *sbc)
     }
   else if (sbc->type == SBC_PINT)
     {
-      dump (0, "lex_match (lexer, '(');");
+      dump (0, "lex_match (lexer, T_LPAREN);");
       dump (1, "if (!lex_force_int (lexer))");
       dump (0, "goto lossage;");
       dump (-1, "p->n_%s = lex_integer (lexer);", st_lower (sbc->name));
-      dump (0, "lex_match (lexer, ')');");
+      dump (0, "lex_match (lexer, T_RPAREN);");
     }
   else if (sbc->type == SBC_DBL_LIST || sbc->type == SBC_INT_LIST)
     {
@@ -1718,9 +1771,9 @@ dump_subcommand (const subcommand *sbc)
       dump (0, "goto lossage;");
       dump (-1,"}");
 
-      dump (1, "while (lex_token (lexer) != '/' && lex_token (lexer) != '.')");
+      dump (1, "while (lex_token (lexer) != T_SLASH && lex_token (lexer) != T_ENDCMD)");
       dump (1, "{");
-      dump (0, "lex_match (lexer, ',');");
+      dump (0, "lex_match (lexer, T_COMMA);");
       dump (0, "if (!lex_force_num (lexer))");
       dump (1, "{");
       dump (0, "goto lossage;");
@@ -1782,13 +1835,13 @@ dump_parser (int persistent)
     {
       if (def->type == SBC_VARLIST)
        dump (1, "if (lex_token (lexer) == T_ID "
-              "&& dict_lookup_var (dataset_dict (ds), lex_tokid (lexer)) != NULL "
-             "&& lex_look_ahead (lexer) != '=')");
+              "&& dict_lookup_var (dataset_dict (ds), lex_tokcstr (lexer)) != NULL "
+             "&& lex_next_token (lexer, 1) != T_EQUALS)");
       else
        {
          dump (0, "if ((lex_token (lexer) == T_ID "
-                "&& dict_lookup_var (dataset_dict (ds), lex_tokid (lexer)) "
-               "&& lex_look_ahead () != '=')");
+                "&& dict_lookup_var (dataset_dict (ds), lex_tokcstr (lexer)) "
+               "&& lex_next_token (lexer, 1) != T_EQUALS)");
          dump (1, "     || token == T_ALL)");
        }
       dump (1, "{");
@@ -1832,7 +1885,7 @@ dump_parser (int persistent)
        f = 1;
        dump (1, "{");
 
-       dump (0, "lex_match (lexer, '=');");
+       dump (0, "lex_match (lexer, T_EQUALS);");
        dump (0, "p->sbc_%s++;", st_lower (sbc->name));
        if (sbc->arity != ARITY_MANY)
          {
@@ -1852,33 +1905,33 @@ dump_parser (int persistent)
 
 
   /* Now deal with the /ALGORITHM subcommand implicit to all commands */
-  dump(1,"else if ( get_syntax() != COMPATIBLE && lex_match_id(lexer, \"ALGORITHM\"))");
+  dump(1,"else if ( settings_get_syntax () != COMPATIBLE && lex_match_id(lexer, \"ALGORITHM\"))");
   dump(1,"{");
 
-  dump (0, "lex_match (lexer, '=');");
+  dump (0, "lex_match (lexer, T_EQUALS);");
 
   dump(1,"if (lex_match_id(lexer, \"COMPATIBLE\"))");
-  dump(0,"set_cmd_algorithm(COMPATIBLE);");
+  dump(0,"settings_set_cmd_algorithm (COMPATIBLE);");
   outdent();
   dump(1,"else if (lex_match_id(lexer, \"ENHANCED\"))");
-  dump(0,"set_cmd_algorithm(ENHANCED);");
+  dump(0,"settings_set_cmd_algorithm (ENHANCED);");
 
   dump (-1, "}");
   outdent ();
 
 
 
-  dump (1, "if (!lex_match (lexer, '/'))");
+  dump (1, "if (!lex_match (lexer, T_SLASH))");
   dump (0, "break;");
   dump (-2, "}");
   outdent ();
-  dump (0, nullstr);
-  dump (1, "if (lex_token (lexer) != '.')");
+  dump_blank_line (0);
+  dump (1, "if (lex_token (lexer) != T_ENDCMD)");
   dump (1, "{");
   dump (0, "lex_error (lexer, _(\"expecting end of command\"));");
   dump (0, "goto lossage;");
   dump (-1, "}");
-  dump (0, nullstr);
+  dump_blank_line (0);
 
   outdent ();
 
@@ -1897,19 +1950,19 @@ dump_parser (int persistent)
                  sbc->name);
            dump (0, "goto lossage;");
            dump (-1, "}");
-           dump (0, nullstr);
+           dump_blank_line (0);
          }
       }
   }
 
   dump (-1, "return true;");
-  dump (0, nullstr);
+  dump_blank_line (0);
   dump (-1, "lossage:");
   indent ();
   dump (0, "free_%s (p);", make_identifier (cmdname));
   dump (0, "return false;");
   dump (-1, "}");
-  dump (0, nullstr);
+  dump_blank_line (0);
 }
 
 
@@ -1919,7 +1972,7 @@ dump_header (void)
 {
   indent = 0;
   dump (0,   "/* %s\t\t-*- mode: c; buffer-read-only: t -*-", ofn);
-  dump (0, nullstr);
+  dump_blank_line (0);
   dump (0, "   Generated by q2c from %s.", ifn);
   dump (0, "   Do not modify!");
   dump (0, " */");
@@ -2075,21 +2128,24 @@ main (int argc, char *argv[])
          indent = 0;
 
          dump (0, "#include <stdlib.h>");
-         dump (0, "#include <libpspp/alloc.h>");
-         dump (0, "#include <libpspp/assertion.h>");
-         dump (0, "#include <libpspp/message.h>");
-         dump (0, "#include <language/lexer/lexer.h>");
-         dump (0, "#include <language/lexer/variable-parser.h>");
-          dump (0, "#include <data/settings.h>");
-         dump (0, "#include <libpspp/magic.h>");
-         dump (0, "#include <libpspp/str.h>");
-          dump (0, "#include <language/lexer/subcommand-list.h>");
-         dump (0, "#include <data/variable.h>");
-         dump (0, nullstr);
+          dump_blank_line (0);
+
+          dump (0, "#include \"data/settings.h\"");
+         dump (0, "#include \"data/variable.h\"");
+         dump (0, "#include \"language/lexer/lexer.h\"");
+          dump (0, "#include \"language/lexer/subcommand-list.h\"");
+         dump (0, "#include \"language/lexer/variable-parser.h\"");
+         dump (0, "#include \"libpspp/assertion.h\"");
+         dump (0, "#include \"libpspp/message.h\"");
+         dump (0, "#include \"libpspp/str.h\"");
+         dump_blank_line (0);
+
+          dump (0, "#include \"gl/xalloc.h\"");
+         dump_blank_line (0);
 
           dump (0, "#include \"gettext.h\"");
           dump (0, "#define _(msgid) gettext (msgid)");
-         dump (0, nullstr);
+         dump_blank_line (0);
        }
       else if (!strcmp (directive, "declarations"))
        dump_declarations ();