// -*- mode: c++; c-basic-offset:4 -*-
// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.
// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: James Gallagher <jgallagher@opendap.org>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
// (c) COPYRIGHT URI/MIT 1994-1999
// Please read the full copyright statement in the file COPYRIGHT_URI.
//
// Authors:
// jhrg,jimg James Gallagher <jgallagher@gso.uri.edu>
/*
Grammar for the DAS. This grammar can be used with the bison parser
generator to build a parser for the DAS. It assumes that a scanner called
`daslex()' exists and that the objects DAS and AttrTable also exist.
jhrg 7/12/94
*/
%code requires {
#define YYSTYPE char *
#define ATTR_STRING_QUOTE_FIX
#include "config.h"
#include <string>
#include <vector>
#include "DAS.h"
#include "Error.h"
#include "util.h"
#include "escaping.h"
#include "debug.h"
#include "parser.h"
#include "util.h"
// #include "das.tab.hh"
#ifdef TRACE_NEW
#include "trace_new.h"
#endif
#define yylex daslex
#define yyerror daserror
using namespace std;
using namespace libdap ;
// These macros are used to access the `arguments' passed to the parser. A
// pointer to an error object and a pointer to an integer status variable are
// passed in to the parser within a structure (which itself is passed as a
// pointer). Note that the ERROR macro explicitly casts OBJ to an ERROR *.
// The parser now throws an exception when it encounters an error. 5/23/2002
// jhrg
#define DAS_OBJ(arg) ((DAS *)((parser_arg *)(arg))->_object)
//#define YYPARSE_PARAM arg
extern int das_line_num; /* defined in das.lex */
} // code requires
%code {
// No global static objects. We go through this every so often, I guess I
// should learn... 1/24/2000 jhrg
static string *name; /* holds name in attr_pair rule */
static string *type; /* holds type in attr_pair rule */
static vector<AttrTable *> *attr_tab_stack;
// I use a vector of AttrTable pointers for a stack
#define TOP_OF_STACK (attr_tab_stack->back())
#define PUSH(x) (attr_tab_stack->push_back((x)))
#define POP (attr_tab_stack->pop_back())
#define STACK_LENGTH (attr_tab_stack->size())
#define OUTER_TABLE_ONLY (attr_tab_stack->size() == 1)
#define STACK_EMPTY (attr_tab_stack->empty())
#define TYPE_NAME_VALUE(x) *type << " " << *name << " " << (x)
static const char *ATTR_TUPLE_MSG =
"Expected an attribute type (Byte, Int16, UInt16, Int32, UInt32, Float32,\n\
Float64, String or Url) followed by a name and value.";
static const char *NO_DAS_MSG =
"The attribute object returned from the dataset was null\n\
Check that the URL is correct.";
typedef int checker(const char *);
int daslex(void);
static void daserror(parser_arg *arg, const string &s /*char *s*/);
static void add_attribute(const string &type, const string &name,
const string &value, checker *chk) throw (Error);
static void add_alias(AttrTable *das, AttrTable *current, const string &name,
const string &src) throw (Error);
static void add_bad_attribute(AttrTable *attr, const string &type,
const string &name, const string &value,
const string &msg);
} // code
%require "2.4"
%parse-param {parser_arg *arg}
%name-prefix "das"
%defines
%debug
%verbose
%expect 26
%token SCAN_ATTR
%token SCAN_WORD
%token SCAN_ALIAS
%token SCAN_BYTE
%token SCAN_INT16
%token SCAN_UINT16
%token SCAN_INT32
%token SCAN_UINT32
%token SCAN_FLOAT32
%token SCAN_FLOAT64
%token SCAN_STRING
%token SCAN_URL
%token SCAN_XML
%%
/*
Parser algorithm:
Look for a `variable' name (this can be any identifier, but by convention
it is either the name of a variable in a dataset or the name of a grouping
of global attributes). Create a new attribute table for this identifier and
push the new attribute table onto a stack. If attribute tuples
(type-name-value tuples) are found, intern them in the attribute table
found on the top of the stack. If the start of a new attribute table if
found (before the current table is closed), create the new table and push
*it* on the stack. As attribute tables are closed, pop them off the stack.
This algorithm ensures that we can nest attribute tables to an arbitrary
depth.
Aliases are handled using mfuncs of both the DAS and AttrTable objects. This
is necessary because the first level of a DAS object can contain only
AttrTables, not attribute tuples. Whereas, the subsequent levels can
contain both. Thus the compete definition is split into two objects. In
part this is also a hold over from an older design which did not
have the recursive properties of the current design.
Aliases can be made between attributes within a given lexical level, from
one level to the next within a sub-hierarchy or across hierarchies.
Tokens:
BYTE, INT32, UINT32, FLOAT64, STRING and URL are tokens for the type
keywords. The tokens INT, FLOAT, STR and ID are returned by the scanner to
indicate the type of the value represented by the string contained in the
global DASLVAL. These two types of tokens are used to implement type
checking for the attributes. See the rules `bytes', etc. Additional tokens:
ATTR (indicates the start of an attribute object) and ALIAS (indicates an
alias). */
/* This rule makes sure the objects needed by this parser are built. Because
the DODS DAP library is often used with linkers that are not C++-aware, we
cannot use global objects (because their constructors might never be
called). I had thought this was going to go away... 1/24/2000 jhrg */
attr_start:
{
if (!name) name = new string();
if (!type) type = new string();
if (!attr_tab_stack) attr_tab_stack = new vector<AttrTable *>;
// push outermost AttrTable
PUSH(DAS_OBJ(arg)->get_top_level_attributes());
}
attributes
{
POP; // pop the DAS/AttrTable before stack's dtor
delete name; name = 0;
delete type; type = 0;
delete attr_tab_stack; attr_tab_stack = 0;
}
;
attributes: attribute
| attributes attribute
;
attribute: SCAN_ATTR '{' attr_list '}'
| error
{
parse_error((parser_arg *)arg, NO_DAS_MSG, das_line_num);
}
;
attr_list: /* empty */
| attr_tuple
| attr_list attr_tuple
;
attr_tuple: alias
| SCAN_BYTE { save_str(*type, "Byte", das_line_num); }
name { save_str(*name, $3, das_line_num); }
bytes ';'
| SCAN_INT16 { save_str(*type, "Int16", das_line_num); }
name { save_str(*name, $3, das_line_num); }
int16 ';'
| SCAN_UINT16 { save_str(*type, "UInt16", das_line_num); }
name { save_str(*name, $3, das_line_num); }
uint16 ';'
| SCAN_INT32 { save_str(*type, "Int32", das_line_num); }
name { save_str(*name, $3, das_line_num); }
int32 ';'
| SCAN_UINT32 { save_str(*type, "UInt32", das_line_num); }
name { save_str(*name, $3, das_line_num); }
uint32 ';'
| SCAN_FLOAT32 { save_str(*type, "Float32", das_line_num); }
name { save_str(*name, $3, das_line_num); }
float32 ';'
| SCAN_FLOAT64 { save_str(*type, "Float64", das_line_num); }
name { save_str(*name, $3, das_line_num); }
float64 ';'
| SCAN_STRING { *type = "String"; }
name { *name = $3; }
strs ';'
| SCAN_URL { *type = "Url"; }
name { *name = $3; }
urls ';'
| SCAN_XML { *type = "OtherXML"; }
name { *name = $3; }
xml ';'
| SCAN_WORD
{
DBG(cerr << "Processing ID: " << $1 << endl);
AttrTable *at = TOP_OF_STACK->get_attr_table($1);
if (!at) {
try {
at = TOP_OF_STACK->append_container($1);
}
catch (Error &e) {
// re-throw with line number info
parse_error(e.get_error_message().c_str(),
das_line_num);
}
}
PUSH(at);
DBG(cerr << " Pushed attr_tab: " << at << endl);
}
'{' attr_list
{
/* pop top of stack; store in attr_tab */
DBG(cerr << " Popped attr_tab: " << TOP_OF_STACK << endl);
POP;
}
'}'
| error
{
parse_error(ATTR_TUPLE_MSG, das_line_num, $1);
} ';'
;
bytes: SCAN_WORD
{
add_attribute(*type, *name, $1, &check_byte);
}
| bytes ',' SCAN_WORD
{
add_attribute(*type, *name, $3, &check_byte);
}
;
int16: SCAN_WORD
{
add_attribute(*type, *name, $1, &check_int16);
}
| int16 ',' SCAN_WORD
{
add_attribute(*type, *name, $3, &check_int16);
}
;
uint16: SCAN_WORD
{
add_attribute(*type, *name, $1, &check_uint16);
}
| uint16 ',' SCAN_WORD
{
add_attribute(*type, *name, $3, &check_uint16);
}
;
int32: SCAN_WORD
{
add_attribute(*type, *name, $1, &check_int32);
}
| int32 ',' SCAN_WORD
{
add_attribute(*type, *name, $3, &check_int32);
}
;
uint32: SCAN_WORD
{
add_attribute(*type, *name, $1, &check_uint32);
}
| uint32 ',' SCAN_WORD
{
add_attribute(*type, *name, $3, &check_uint32);
}
;
float32: float_or_int
{
add_attribute(*type, *name, $1, &check_float32);
}
| float32 ',' float_or_int
{
add_attribute(*type, *name, $3, &check_float32);
}
;
float64: float_or_int
{
add_attribute(*type, *name, $1, &check_float64);
}
| float64 ',' float_or_int
{
add_attribute(*type, *name, $3, &check_float64);
}
;
strs: str_or_id
{
string attr = remove_quotes($1);
add_attribute(*type, *name, attr, 0);
}
| strs ',' str_or_id
{
string attr = remove_quotes($3);
add_attribute(*type, *name, attr, 0);
}
;
urls: url
{
add_attribute(*type, *name, $1, &check_url);
}
| urls ',' url
{
add_attribute(*type, *name, $3, &check_url);
}
;
xml: SCAN_WORD
{
// XML must be quoted in the DAS but the quotes are an
// artifact of the DAS syntax so they are not part of the
// value.
string xml = unescape_double_quotes($1);
if (is_quoted(xml))
add_attribute(*type, *name, remove_quotes(xml), 0);
else
add_attribute(*type, *name, xml, 0);
}
;
url: SCAN_WORD
;
str_or_id: SCAN_WORD
;
float_or_int: SCAN_WORD
;
name: SCAN_WORD | SCAN_ATTR | SCAN_ALIAS | SCAN_BYTE | SCAN_INT16
| SCAN_UINT16 | SCAN_INT32 | SCAN_UINT32 | SCAN_FLOAT32
| SCAN_FLOAT64 | SCAN_STRING | SCAN_URL | SCAN_XML
;
alias: SCAN_ALIAS SCAN_WORD
{
*name = $2;
}
SCAN_WORD
{
add_alias( DAS_OBJ(arg)->get_top_level_attributes(),
TOP_OF_STACK, *name, string($4) ) ;
}
';'
;
%%
// This function is required for linking, but DODS uses its own error
// reporting mechanism.
static void
daserror(parser_arg *, const string &)
{
}
static string
a_or_an(const string &subject)
{
string first_char(1, subject[0]);
string::size_type pos = first_char.find_first_of("aeiouAEIOUyY");
if (pos == string::npos)
return "a";
else
return "an";
}
// This code used to throw an exception when a bad attribute value came
// along; now it dumps the errant value(s) into a sub container called *_DODS
// and stores the parser's error message in a string attribute named
// `explanation.'
static void
add_attribute(const string &type, const string &name, const string &value,
checker *chk) throw (Error)
{
DBG(cerr << "Adding: " << type << " " << name << " " << value \
<< " to Attrtable: " << TOP_OF_STACK << endl);
if (chk && !(*chk)(value.c_str())) {
string msg = "`";
msg += value + "' is not " + a_or_an(type) + " " + type + " value.";
add_bad_attribute(TOP_OF_STACK, type, name, value, msg);
return;
}
if (STACK_EMPTY) {
string msg = "Whoa! Attribute table stack empty when adding `" ;
msg += name + ".' ";
parse_error(msg, das_line_num);
}
try {
#if 0
// Special treatment for XML: remove the double quotes that were
// included in the value by this parser.
if (type == OtherXML && is_quoted(value))
TOP_OF_STACK->append_attr(name, type, value.substr(1, value.size()-2));
else
#endif
TOP_OF_STACK->append_attr(name, type, value);
}
catch (Error &e) {
// re-throw with line number
parse_error(e.get_error_message().c_str(), das_line_num);
}
}
static void
add_alias(AttrTable *das, AttrTable *current, const string &name,
const string &src) throw (Error)
{
DBG(cerr << "Adding an alias: " << name << ": " << src << endl);
AttrTable *table = das->get_attr_table(src);
if (table) {
try {
current->add_container_alias(name, table);
}
catch (Error &e) {
parse_error(e.get_error_message().c_str(), das_line_num);
}
}
else {
try {
current->add_value_alias(das, name, src);
}
catch (Error &e) {
parse_error(e.get_error_message().c_str(), das_line_num);
}
}
}
static void
add_bad_attribute(AttrTable *attr, const string &type, const string &name,
const string &value, const string &msg)
{
// First, if this bad value is already in a *_dods_errors container,
// then just add it. This can happen when the server side processes a DAS
// and then hands it off to a client which does the same.
// Make a new container. Call it <attr's name>_errors. If that container
// already exists, use it.
// Add the attribute.
// Add the error string to an attribute in the container called
// `<name_explanation.'.
if (attr->get_name().find("_dods_errors") != string::npos) {
attr->append_attr(name, type, value);
}
else {
string error_cont_name = attr->get_name() + "_dods_errors";
AttrTable *error_cont = attr->get_attr_table(error_cont_name);
if (!error_cont)
error_cont = attr->append_container(error_cont_name);
error_cont->append_attr(name, type, value);
#ifndef ATTR_STRING_QUOTE_FIX
error_cont->append_attr(name + "_explanation", "String",
"\"" + msg + "\"");
#else
error_cont->append_attr(name + "_explanation", "String", msg);
#endif
}
}