CTABLES
[pspp] / src / language / lexer / format-parser.c
index aa4729b063ffaa780da433b92d7a9e979cf8121d..aa30852d11327b3ddcf2d3fc8488f04649fc73f7 100644 (file)
-/* PSPP - computes sample statistics.
-   Copyright (C) 1997-9, 2000 Free Software Foundation, Inc.
-   Written by Ben Pfaff <blp@gnu.org>.
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 1997-9, 2000, 2006, 2010, 2011, 2012 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 <config.h>
-#include <data/format.h>
+
+#include "language/lexer/format-parser.h"
+
 #include <ctype.h>
-#include <libpspp/message.h>
+#include <stdint.h>
 #include <stdlib.h>
-#include <libpspp/message.h>
-#include "lexer.h"
-#include <libpspp/misc.h>
-#include <libpspp/str.h>
-#include <data/variable.h>
+
+#include "data/format.h"
+#include "data/variable.h"
+#include "language/lexer/lexer.h"
+#include "libpspp/message.h"
+#include "libpspp/misc.h"
+#include "libpspp/str.h"
 
 #include "gettext.h"
 #define _(msgid) gettext (msgid)
 
-
-/* Parses the alphabetic prefix of the current token as a format
-   specifier name.  Returns the corresponding format specifier
-   type if successful, or -1 on failure.  If ALLOW_XT is zero,
-   then X and T format specifiers are not allowed.  If CP is
-   nonzero, then *CP is set to the first non-alphabetic character
-   in the current token on success or to a null pointer on
-   failure. */
-int
-parse_format_specifier_name (const char **cp, enum fmt_parse_flags flags)
+/* Parses a token taking the form of a format specifier and
+   returns true only if successful.  Emits an error message on
+   failure.  Stores a null-terminated string representing the
+   format type in TYPE, and the width and number of decimal
+   places in *WIDTH and *DECIMALS.
+
+   TYPE is not checked as to whether it is really the name of a
+   format.  Both width and decimals are considered optional.  If
+   missing, *WIDTH or *DECIMALS or both will be set to 0. */
+bool
+parse_abstract_format_specifier__ (struct lexer *lexer,
+                                   char type[FMT_TYPE_LEN_MAX + 1],
+                                   uint16_t *width, uint8_t *decimals)
 {
-  char *sp, *ep;
-  int idx;
-
-  sp = ep = ds_cstr (&tokstr);
-  while (isalpha ((unsigned char) *ep))
-    ep++;
-
-  if (sp != ep) 
+  struct substring s;
+  struct substring type_ss, width_ss, decimals_ss;
+  bool has_decimals;
+
+  if (lex_token (lexer) != T_ID && lex_token (lexer) != T_STRING)
+    goto error;
+
+  /* Extract pieces. */
+  s = ss_cstr (lex_tokcstr (lexer));
+  ss_get_bytes (&s, ss_span (s, ss_cstr (CC_LETTERS)), &type_ss);
+  ss_get_bytes (&s, ss_span (s, ss_cstr (CC_DIGITS)), &width_ss);
+  if (ss_match_byte (&s, '.'))
     {
-      /* Find format. */
-      for (idx = 0; idx < FMT_NUMBER_OF_FORMATS; idx++)
-        if (strlen (formats[idx].name) == ep - sp
-            && !buf_compare_case (formats[idx].name, sp, ep - sp))
-          break;
-
-      /* Check format. */
-      if (idx < FMT_NUMBER_OF_FORMATS)
-        {
-          if (!(flags & FMTP_ALLOW_XT) && (idx == FMT_T || idx == FMT_X)) 
-            {
-              if (!(flags & FMTP_SUPPRESS_ERRORS))
-                msg (SE, _("X and T format specifiers not allowed here."));
-              idx = -1; 
-            }
-        }
-      else 
-        {
-          /* No match. */
-          if (!(flags & FMTP_SUPPRESS_ERRORS))
-            msg (SE, _("%.*s is not a valid data format."),
-                 (int) (ep - sp), ds_cstr (&tokstr));
-          idx = -1; 
-        }
+      has_decimals = true;
+      ss_get_bytes (&s, ss_span (s, ss_cstr (CC_DIGITS)), &decimals_ss);
     }
-  else 
-    {
-      lex_error ("expecting data format");
-      idx = -1;
-    }
-      
-  if (cp != NULL) 
-    {
-      if (idx != -1)
-        *cp = ep;
-      else
-        *cp = NULL;
-    }
-
-  return idx;
+  else
+    has_decimals = false;
+
+  /* Check pieces. */
+  if (ss_is_empty (type_ss) || ss_length (type_ss) > FMT_TYPE_LEN_MAX)
+    goto error;
+  if (has_decimals && ss_is_empty (decimals_ss))
+    goto error;
+  if (!ss_is_empty (s))
+    goto error;
+
+  /* Return pieces.
+     These uses of strtol are valid only because we know that
+     their substrings are followed by non-digit characters. */
+  str_copy_buf_trunc (type, FMT_TYPE_LEN_MAX + 1,
+                      ss_data (type_ss), ss_length (type_ss));
+  *width = strtol (ss_data (width_ss), NULL, 10);
+  *decimals = has_decimals ? strtol (ss_data (decimals_ss), NULL, 10) : 0;
+
+  return true;
+
+error:
+  lex_error (lexer, _("expecting valid format specifier"));
+  return false;
 }
 
+/* Like parse_abstract_format_specifier__(), but additionally advanced past
+   the token if successful. */
+bool
+parse_abstract_format_specifier (struct lexer *lexer,
+                                 char type[FMT_TYPE_LEN_MAX + 1],
+                                 uint16_t *width, uint8_t *decimals)
+{
+  bool ok = parse_abstract_format_specifier__ (lexer, type, width, decimals);
+  if (ok)
+    lex_get (lexer);
+  return ok;
+}
 
-/* Parses a format specifier from the token stream and returns
-   nonzero only if successful.  Emits an error message on
-   failure.  Allows X and T format specifiers only if ALLOW_XT is
-   nonzero.  The caller should call check_input_specifier() or
-   check_output_specifier() on the parsed format as
+/* Parses a format specifier from the token stream and returns true only if
+   successful.  Emits an error message on failure.  The caller should call
+   fmt_check_input() or fmt_check_output() on the parsed format as
    necessary.  */
-int
-parse_format_specifier (struct fmt_spec *input, enum fmt_parse_flags flags)
+bool
+parse_format_specifier (struct lexer *lexer, struct fmt_spec *format)
 {
-  struct fmt_spec spec;
-  struct fmt_desc *f;
-  const char *cp;
-  char *cp2;
-  int type, w, d;
+  char type[FMT_TYPE_LEN_MAX + 1];
 
-  if (token != T_ID)
-    {
-      if (!(flags & FMTP_SUPPRESS_ERRORS))
-        msg (SE, _("Format specifier expected."));
-      return 0;
-    }
-  type = parse_format_specifier_name (&cp, flags);
-  if (type == -1)
-    return 0;
-  f = &formats[type];
+  if (!parse_abstract_format_specifier__ (lexer, type, &format->w, &format->d))
+    return false;
 
-  w = strtol (cp, &cp2, 10);
-  if (cp2 == cp && type != FMT_X)
+  if (!fmt_from_name (type, &format->type))
     {
-      if (!(flags & FMTP_SUPPRESS_ERRORS))
-        msg (SE, _("Data format %s does not specify a width."),
-             ds_cstr (&tokstr));
-      return 0;
+      msg (SE, _("Unknown format type `%s'."), type);
+      return false;
     }
-  if ( w > MAX_STRING )
+
+  if (format->w == 0 && !strchr (lex_tokcstr (lexer), '0'))
     {
-      msg (SE, _("String variable width may not exceed %d"), MAX_STRING);
-      return 0;
+      msg (SE, _("Format specifier `%s' lacks required width."),
+           lex_tokcstr (lexer));
+      return false;
     }
 
-  cp = cp2;
-  if (f->n_args > 1 && *cp == '.')
+  lex_get (lexer);
+  return true;
+}
+
+/* Parses a token containing just the name of a format type and
+   returns true if successful. */
+bool
+parse_format_specifier_name (struct lexer *lexer, enum fmt_type *type)
+{
+  if (lex_token (lexer) != T_ID)
     {
-      cp++;
-      d = strtol (cp, &cp2, 10);
-      cp = cp2;
+      lex_error (lexer, _("expecting format type"));
+      return false;
     }
-  else
-    d = 0;
-
-  if (*cp)
+  if (!fmt_from_name (lex_tokcstr (lexer), type))
     {
-      if (!(flags & FMTP_SUPPRESS_ERRORS))
-        msg (SE, _("Data format %s is not valid."), ds_cstr (&tokstr));
-      return 0;
+      msg (SE, _("Unknown format type `%s'."), lex_tokcstr (lexer));
+      return false;
     }
-  lex_get ();
-
-  spec.type = type;
-  spec.w = w;
-  spec.d = d;
-  *input = spec;
-
-  return 1;
+  lex_get (lexer);
+  return true;
 }
-