//{{{  Banner                                   

//============================================================================
//
//     parse.cxx
//
//     Miscellaneous parsing routines
//
//============================================================================
//####COPYRIGHTBEGIN####
//                                                                          
// ----------------------------------------------------------------------------
// Copyright (C) 1999, 2000 Red Hat, Inc.
//
// This file is part of the eCos host tools.
//
// 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 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., 
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ----------------------------------------------------------------------------
//                                                                          
//####COPYRIGHTEND####
//============================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):   bartv
// Contact(s):  bartv
// Date:        1999/02/23
// Version:     0.02
//
//####DESCRIPTIONEND####
//============================================================================

//}}}
//{{{  #include's                               

// ----------------------------------------------------------------------------
#include "cdlconfig.h"

// Get the infrastructure types, assertions, tracing and similar
// facilities.
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/cyg_trac.h>

// <cdlcore.hxx> defines everything implemented in this module.
// It implicitly supplies <string>, <vector> and <map> because
// the class definitions rely on these headers.
#include <cdlcore.hxx>

//}}}

//{{{  Description                              

// ----------------------------------------------------------------------------
// All CDL data is read via a Tcl interpreter, so the parsing is done by
// procedures that appear as Tcl commands. This has obvious advantages in
// terms of expressive power, but does result in a little bit of confusion
// when producing diagnostics.
//
// Errors should not bypass the Tcl interpreter, to ensure that that
// stays in a consistent state. In particular it is not possible to let
// arbitrary C++ exceptions to go straight through the Tcl interpreter,
// this is likely to result in a corrupted interpreter.
// 
// Also, it is not a good idea to abort parsing as soon as anything
// goes wrong. Instead there should be an error handling callback associated
// with the interpreter, which can be used to report errors. This
// callback may choose to raise an exception.
//
// Consider the case of parsing a property (which accounts for most of
// the code in this module). A property does not exist in isolation,
// only within the context of a suitable CDL entity such as an option.
// If parsing succeeds and a property object is created then it must
// be added to the current CDL entity.
//
// Therefore a typical parse routine looks like this:
//
//  1) get the current CDL node from the interpreter
//  2) do basic parsing of the data. Any errors should be reported
//     via the callback.
//  3) create a suitable CdlProperty object
//  4) add the property to the current entity
//
// std::bad_alloc and CdlStringException exceptions can be thrown, they
// will be intercepted by the CdlInterpreter class.

//}}}
//{{{  Statics                                  

// ----------------------------------------------------------------------------
// The string "property " is needed in various places. Provide it as a static
// to cut down the number of times the string constructor has to run.
static std::string property_string = "property ";

//}}}
//{{{  Generic parsing-related utilities        

//{{{  argv manipulation                        

// ----------------------------------------------------------------------------
// Some of the properties have aliases in the CDL data, so argv[0] has to be
// used to work out what is actually being parsed. However the Tcl interpreter
// may prefix the command name with :: to indicate the global namespace.
std::string
CdlParse::get_tcl_cmd_name(std::string name)
{
    std::string result;
    
    if ((name[0] == ':') && (name[1] == ':')) {
        result = std::string(name, 2, name.size() - 2);
    } else {
        result = name;
    }
    return result;
}

// Given a list of arguments, concatenate them together into a C++ string.
// This makes expression parsing easier. The final argument should be an
// index into the array, typically the result of a call to skip_argv_options().
std::string
CdlParse::concatenate_argv(int argc, char** argv, int index)
{
    CYG_REPORT_FUNCNAME("CdlParse::concatenate_argv");

    std::string result = "";
    for ( int i = index ; i < argc; i++) {
        if (i > index) {
            result += ' ';
        }
        result += std::string(argv[i]);
    }
    
    CYG_REPORT_RETURN();
    return result;
}

// ----------------------------------------------------------------------------
// Option parsing.
//
// Some properties accept modifiers in their argument list. For example,
// by default a "compile" property is just a list of source files that
// should be compiled and added to the current library. It is possible
// to specify an alternative library via a modifier:
//
//    ...
//    compile -library=libextras.a dummy.cxx
//    ...
//
// The rules applied for such options are intended to follow common Tcl
// practice:
//
// 1) any initial arguments beginning with a - are assumed to be
//    options.
//
// 2) the first argument which does not begin with a - stops option
//    processing.
//
// 3) option processing can also be terminated with a -- argument.
//    This may occasionally be required, e.g. to have -ve constants
//    in an expression.
//
// 4) the parsing code does not distinguish between single and double
//    hyphens. If there happens to be a second - in an option then this
//    is just ignored.
//
// 5) it is not necessary to supply the whole option name, as long as
//    what is provided is unambiguous. For example -lib=libextras.a is
//    acceptable (for now anyway, conceivably it would break in future).
//    Option processing is case sensitive.
//
// 6) name/value pairs can take the form -name=value or the form
//    -name value, whichever is appropriate.
//
// The parse_options() function takes the current interpreter (so that
// it can generate diagnostics), a prefix such as `property
// "requires"' or `package CYGPKG_HAL', details of the options to be
// parsed, an argc/argv list, an index, and a reference to a vector.
// The parsed options get placed in the vector, and the index argument
// gets updated to point at the first non-option argument. It will
// report problems via report_warning().
//
// The options description consists of a pointer to an array of
// C strings (allowing static initialization). Passing a zero
// argument indicates that the property takes no options. Otherwise
// the array should be zero terminated.
//
// Each entry in the array takes the form "name:flags". The optional
// flags consist of zero or more characters indicating how the option
// should be interpreted. Valid flags are:
//
// f    - this option takes no data, it is just a boolean flag. The
//        default behaviour assumes name/value pairs.
//
// m    - this option can occur multiple times. The default behaviour
//        is that each option can only occur once.

// Utility function to get hold of an option name without the colon
// or terminating flags.

static std::string
get_option_string(char* name)
{
    std::string result = "";
    while ((*name != ':') && (*name != '\0')) {
        result += *name++;
    }
    return result;
}

int
CdlParse::parse_options(CdlInterpreter interp, std::string diag_prefix, char** options,
                                 int argc, char** argv, int index,
                                 std::vector<std::pair<std::string,std::string> >& result)
{
    CYG_REPORT_FUNCNAMETYPE("CdlParse::parse_options", "final index %d");
    CYG_REPORT_FUNCARG4XV(interp, options, argc, argv);
    CYG_PRECONDITION_CLASSC(interp);
    CYG_PRECONDITIONC(argc > 0);        // The property name must be present.
    // It is possible for some of the arguments to have been processed already.
    CYG_PRECONDITIONC((index > 0) && (index <= argc));

    while((index < argc) && ('-' == argv[index][0])) {

        std::string name  = "";
        std::string value = "";

        // The sequence -- should always terminate option processing.
        if (0 == strcmp(argv[index], "--")) {
            index++;
            break;
        }

        char* arg_ptr       = argv[index];
        // Skip the initial -, and the second one as well if it is present.
        if ('-' == *++arg_ptr) {
            arg_ptr++;
        }

        // Construct the option name. This is the current argument up
        // to but not including the '=' character or EOD.
        while (('=' != *arg_ptr) && ('\0' != *arg_ptr)) {
            name += *arg_ptr++;
        }

        if ("" == name) {
            // One of "-", "-=xxx", or "--=x"
            CdlParse::report_warning(interp, diag_prefix, std::string("Invalid option string `") + argv[index] + "'.");
        }

        // Do not try to extract the value unless we are sure there
        // should be one. Instead try to match the option name. The
        // current value of name should be a unique substring of
        // one of the known option strings.
        //
        // Note that the supplied options descriptor can be NULL,
        // since most properties do not yet take any options.
        // In that case opt_index will remain at -1, and we should
        // get an "invalid option" diagnostic.
        unsigned int i;
        int opt_index = -1;
        if (0 != options) {
            for (i = 0; 0 != options[i]; i++) {
                if (0 == strncmp(name.c_str(), options[i], name.size())) {
                    if (-1 != opt_index) {
                        CdlParse::report_warning(interp, diag_prefix,
                                                 std::string("Ambiguous option name `") + name + "'.\n" +
                                                 "It can match `" + get_option_string(options[opt_index]) + "'\n" +
                                                 "or `" + get_option_string(options[i]) + "'.");
                        index++;
                        break;
                    } else {
                        opt_index = i;
                    }
                }
            }
        }
        if (-1 == opt_index) {
            CdlParse::report_warning(interp, diag_prefix, std::string("Invalid option `") + name + "'.");
            index++;
            break;
        }

        // The option has been identified successfully. Extract the flags.
        bool    flag_flag       = false;
        bool    multiple_flag   = false;
        char*   tmp = options[opt_index];
        while (('\0' != *tmp) && (':' != *tmp)) {
            tmp++;
        }
        if (':' == *tmp) {
            do {
                tmp++;
                if ('f' == *tmp) {
                    flag_flag = true;
                } else if ('m' == *tmp) {
                    multiple_flag = true;
                } else if ('\0' != *tmp) {
                    CYG_FAIL("Invalid property option");
                }
            } while ('\0' != *tmp);
        }

        // We now know the full option name. Use it for future diagnostics.
        name = get_option_string(options[opt_index]);

        // Take care of the value.
        if (flag_flag) {
            // There should not be a value. If the current argument is of the
            // form x=y then this is an error.
            if ('=' == *arg_ptr) {
                CdlParse::report_warning(interp, diag_prefix,  std::string("Option `") + name + "' does not take any data.");
            }
            // Leave index pointing at the next argument to be processed.
            index++;
        } else {
            if ('=' == *arg_ptr) {
                value = std::string(++arg_ptr);
            } else if (++index == argc) {
                CdlParse::report_warning(interp, diag_prefix,  std::string("Missing data for option `") + name + "'.");
            } else {
                value = argv[index];
            }
            index++;
        }
        // At this stage index points at the next argument to be processed, and should not
        // be updated again.
        
        // Unless the option can occur multiple times, make sure that it is not already
        // present in the options vector.
        if (!multiple_flag) {
            for (i = 0; i < result.size(); i++) {
                if (name == result[i].first) {
                    CdlParse::report_warning(interp, diag_prefix, std::string("Option `") + name + "' can only be used once.");
                    break;
                }
            }
        }

        // The name/value pair is valid, so add it to the result vector.
        result.push_back(std::make_pair(name, value));
    }
    
    CYG_REPORT_RETVAL(index);
    return index;
}

//}}}
//{{{  Diagnostic construction                  

// Construct a suitable diagnostic for a parsing error. This may occur
// when reading in a CDL script, a savefile, a database, or anything
// similar. 
//
// A diagnostic should take the following form:
//
//     <context> <linenumber> [, <node identifier>] [, <extra identifier>] : [<classification>, ] <message>
//
// The context should be set in the Tcl interpreter. Typically it
// will be a filename.
//
// In practice generating the line number is not really feasible at
// present, the Tcl interpreter does not keep track of sufficient
// information. At least, not in the public data structures, there is
// a termOffset field in the internal data structures which might
// be used to do the right thing. I do not want to start relying
// on Tcl internals just yet, or add support to the Tcl core for
// keeping track of line numbers.
//
// For many data files there will the concept of a current node,
// e.g. an option whose properties or savefile information are
// being processed. The CdlInterpreter class keeps track of the
// current node, so if it is defined then the node's class and
// name can be part of the message. This happens automatically,
// no effort is required on the part of calling code.
//
// There may also be additional information, for example
// identifying the specific property where the error was detected.
// This is handled by an extra argument.
//
// The classification is likely to be something like "warning",
// "error", or "internal error". It is controlled by the calling
// code, but typically it is provided by calling via report_warning()
// etc.
//
// The message should identify the actual error. It should be
// a proper sentence, i.e. begin with a capital error and end with
// a full stop, unless the last word is an identifier or filename
// or something similarly special in which case the trailing
// dot will be discarded. The message should not end with a
// newline character, and the result string will not end with one
// either. That is left to higher level code.

std::string
CdlParse::construct_diagnostic(CdlInterpreter interp, std::string classification, std::string sub_id, std::string message)
{
    CYG_REPORT_FUNCNAME("CdlParse::construct_diagnostic");
    CYG_PRECONDITION_CLASSC(interp);

    std::string context      = interp->get_context();
    CdlNode     current_node = interp->get_node();

    std::string result;
    if ("" == context) {
        result = "<unknown context>";
    } else {
        result = context;
    }
    if (0 != current_node) {
        result += ", " + current_node->get_class_name() + " " + current_node->get_name();
    }
    if ("" != sub_id) {
        result += ", " + sub_id;
    }
    result += ": " + classification;
    
    // Now it is time to start worrying about layout, indenting
    // subsequent lines, and so on.
    int index        = result.length();
    int message_len  = message.length();
    int message_index;
    bool indent_needed = false;

    // Find out how many characters there are in the message up to the first newline
    for (message_index = 0; (message_index < message_len) && ('\n' != message[message_index]); message_index++) {
        ;
    }

    // Should the message start on the next line, suitably indented?
    // This depends in part on whether or not there was a classification.
    if ("" == classification) {
        // The current result ends with a colon and a space.
        if ((index + message_index) <= 72) {
            // The first line of the message can still fit. No need to do anything.
        } else {
            // Start indenting immediately, do not add anything else to the current line.
            indent_needed = true;
        }
    } else {
        // We may want a comma and a space after the classification
        if ((index + 2 + message_index) <= 72) {
            result += ", ";
        } else {
            indent_needed = true;
        }
    }

    // Now we can process the message one character at a time, adding
    // newlines and indentation just in time.
    for (message_index = 0; message_index < message_len; message_index++) {
        if (indent_needed) {
            result += "\n    ";
            indent_needed = false;
        }

        if ('\n' == message[message_index]) {
            indent_needed = true;
        } else {
            result += message[message_index];
        }
    }

    CYG_REPORT_RETURN();
    return result;
}

//}}}
//{{{  Error count tracking                     

// Keep track of the number of errors that have occurred while doing some
// parsing. This functionality is not provided directly by the CdlInterpreter
// class, instead it is implemented using assoc data.

static const char       error_count_key[]       = "CdlErrorCount";

static void
error_count_delproc(ClientData data, Tcl_Interp* interp)
{
    CYG_REPORT_FUNCNAME("CdlParse::error_count_delproc");
    int* newed_ptr = static_cast<int*>(data);
    delete newed_ptr;
    CYG_REPORT_RETURN();
}

void
CdlParse::clear_error_count(CdlInterpreter interp)
{
    CYG_REPORT_FUNCNAME("CdlParse::clear_error_count");
    CYG_REPORT_FUNCARG1("interp %p", interp);
    CYG_PRECONDITION_CLASSC(interp);

    int*        newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key));
    if (0 != newed_ptr) {
        *newed_ptr = 0;
    }

    CYG_REPORT_RETURN();
}

void
CdlParse::incr_error_count(CdlInterpreter interp, int how_much)
{
    CYG_REPORT_FUNCNAME("CdlParse::incr_error_counter");
    CYG_REPORT_FUNCARG2("interp %p, how_much %d", interp, how_much);
    CYG_PRECONDITION_CLASSC(interp);
    CYG_PRECONDITION(how_much > 0, "previous errors cannot be undone");
    
    int* newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key));
    if (0 == newed_ptr) {
        newed_ptr = new int(how_much);
        interp->set_assoc_data(error_count_key, static_cast<void*>(newed_ptr), &error_count_delproc);
    } else {
        CYG_ASSERT((*newed_ptr + how_much) > *newed_ptr, "number of parsing errors should not overflow");
        *newed_ptr += how_much;
    }

    CYG_REPORT_RETURN();
}

int
CdlParse::get_error_count(CdlInterpreter interp)
{
    CYG_REPORT_FUNCNAMETYPE("CdlParse::get_error_count", "count %d");
    CYG_REPORT_FUNCARG1("interp %p", interp);
    CYG_PRECONDITION_CLASSC(interp);

    int result = 0;
    int* newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key));
    if (0 != newed_ptr) {
        result = *newed_ptr;
    }

    CYG_REPORT_RETVAL(result);
    return result;
}

//}}}
//{{{  Error and warning reporting              

// Report errors and warnings. These will be called during parsing
// operations, both of CDL and similar data scripts and for savefiles.
// The parsing involves running a Tcl interpreter extended with the
// appropriate set of commands. Typically the call graph will look
// something like this:
//
//     libcdl C++ code such as load_package()
//     libcdl CdlInterpreter::eval()
//     Tcl interpreter
//     libcdl parsing code
//     report_error()
//     
// If the Tcl script is invalid then parsing errors may get reported
// at the higher level code as well.
//
// There are two classes of diagnostic: errors and warnings.
// Additional levels may be added in future, but there does not seem
// to be an urgent need for them. Client code should provide callback
// functions so that the messages can be displayed to the user, and
// these callbacks will be registered with the current CdlInterpreter.
//
// If no error callback is defined then a ParseException will be
// raised instead, and the rest of the current script will not be
// processed. Alternatively the error callback itself can raise a
// ParseException. Care is taken to ensure that the exception does not
// go straight through the Tcl interpreter, since that would prevent
// the Tcl code from cleaning up appropriately. If no exception is
// raised then the library keeps track of the number of errors, and
// this information is accessible once the script has been fully
// processed. This allows multiple errors to be reported in a single
// run.
//
// If no warning callback is provided then warnings are ignored.

void
CdlParse::report_error(CdlInterpreter interp, std::string sub_id, std::string message)
{
    CYG_REPORT_FUNCNAME("CdlParse::report_error");
    CYG_REPORT_FUNCARG1("interp %p", interp);
    CYG_PRECONDITION_CLASSC(interp);

    incr_error_count(interp);

    std::string full_message = construct_diagnostic(interp, "error", sub_id, message);

    // Now, either invoke the callback if it is provided, or throw the exception.
    CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr();
    if (0 == fn) {
        throw CdlParseException(full_message);
    } else {
        (*fn)(full_message);
    }
    
    CYG_REPORT_RETURN();
}

void
CdlParse::report_warning(CdlInterpreter interp, std::string sub_id, std::string message)
{
    CYG_REPORT_FUNCNAME("CdlParse::report_warning");
    CYG_REPORT_FUNCARG1("interp %p", interp);
    CYG_PRECONDITION_CLASSC(interp);

    // If there is no warning callback, do nothing. This is really a
    // bug in the calling application.
    CdlDiagnosticFnPtr fn = interp->get_warning_fn_ptr();
    if (0 != fn) {
        std::string full_message = construct_diagnostic(interp, "warning", sub_id, message);
        (*fn)(full_message);
    }

    CYG_REPORT_RETURN();
}

//}}}
//{{{  The "unknown" command                    

// ----------------------------------------------------------------------------
// This routine should be installed in interpreters that get used for
// parsing CDL scripts. It gets invoked when the CDL script contains
// an unrecognised command, e.g. because of a typo, and makes sure that
// the usual diagnostics process is observed.
//
// This routine should be uninstalled after the parsing is complete,
// to avoid e.g. a ParseException when it is not expected.
int
CdlParse::unknown_command(CdlInterpreter interp, int argc, char** argv)
{
    CYG_REPORT_FUNCNAME("CdlParse::unknown_command");
    CYG_REPORT_FUNCARG3XV(interp, argc, argv);
    CYG_PRECONDITIONC(2 <= argc);
    CYG_PRECONDITION_CLASSC(interp);

    report_error(interp, "", std::string("Unknown command `") + argv[1] + "'.");
    CYG_UNUSED_PARAM(int, argc);
    
    return TCL_OK;
}

//}}}

//}}}
//{{{  Property-related parser utilities        

// ----------------------------------------------------------------------------
// Utilities related to parsing properties, rather than more general parsing.

// A variant of report_parse_error() which also adds the property prefix.
void
CdlParse::report_property_parse_error(CdlInterpreter interp, std::string argv0, std::string msg)
{
    CYG_REPORT_FUNCNAME("CdlPase::report_property_parse_error");

    incr_error_count(interp);
    
    std::string diag = construct_diagnostic(interp, "error",
                                            std::string("property ") + CdlParse::get_tcl_cmd_name(argv0),
                                            msg);

    // Now, either invoke the callback if it is provided, or throw the exception.
    CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr();
    if (0 == fn) {
        throw CdlParseException(diag);
    } else {
        (*fn)(diag);
    }
    
    CYG_REPORT_RETURN();
}

void
CdlParse::report_property_parse_error(CdlInterpreter interp, CdlProperty prop, std::string msg)
{
    CYG_REPORT_FUNCNAME("CdlParse::report_property_parse_error");
    report_property_parse_error(interp, (prop->get_argv())[0], msg);
    CYG_REPORT_RETURN();
}

// Repeat for warnings
void
CdlParse::report_property_parse_warning(CdlInterpreter interp, std::string argv0, std::string msg)
{
    CYG_REPORT_FUNCNAME("CdlPase::report_property_parse_warning");

    CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr();
    if (0 != fn) {
        std::string diag = construct_diagnostic(interp, "error",
                                                std::string("property ") + CdlParse::get_tcl_cmd_name(argv0),
                                                msg);
        (*fn)(diag);
    }
    
    CYG_REPORT_RETURN();
}

void
CdlParse::report_property_parse_warning(CdlInterpreter interp, CdlProperty prop, std::string msg)
{
    CYG_REPORT_FUNCNAME("CdlParse::report_property_parse_warning");
    report_property_parse_warning(interp, (prop->get_argv())[0], msg);
    CYG_REPORT_RETURN();
}

//}}}
//{{{  Generic property parsers                 

// ----------------------------------------------------------------------------
// Generic parsers
//
// These routines provide some more generic property parsing routines. argv[0]
// generally provides sufficient information to allow for sensible error messages.
// The command-specific parsers have to provide a property name. In addition it is
// possible to provide a function to handle per-command options, and another
// function that performs a final sanity check before the property gets added
// to the current entity.

//{{{  parse_minimal_property()         

// ----------------------------------------------------------------------------
// A minimal property takes no arguments.

int
CdlParse::parse_minimal_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                 char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_Minimal))
{
    CYG_REPORT_FUNCNAME("parse_minimal_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_Minimal new_property = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);
        
        if (data_index < argc) {
            CdlParse::report_property_parse_error(interp, argv[0], std::string("Unexpected data `") + argv[data_index] + "'.");
        } else {
        
            // The command is valid, turn it into a property.
            // The property has been parsed successfully. Add it to the current node
            CdlNode current_node = interp->get_node();
            CYG_ASSERTC(0 != current_node);
            new_property = CdlProperty_MinimalBody::make(current_node, name, argc, argv, options);
            if (0 != final_parser) {
                (*final_parser)(interp, new_property);
            }
        }
    } catch(...) {
        
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    return TCL_OK;
}

//}}}
//{{{  parse_string_property()          

// ----------------------------------------------------------------------------

int
CdlParse::parse_string_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_String))
{
    CYG_REPORT_FUNCNAME("parse_string_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_String new_property = 0;
    
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);

        if (data_index == argc) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing argument.");
        } else if ((data_index + 1) < argc) {
            CdlParse::report_property_parse_error(interp, argv[0], std::string("Too many arguments, expecting just one."));
        } else {
        
            CdlNode current_node = interp->get_node();
            CYG_ASSERTC(0 != current_node);
            new_property = CdlProperty_StringBody::make(current_node, name, argv[data_index], argc, argv, options);
            if (0 != final_parser) {
                (*final_parser)(interp, new_property);
            }
        }
    } catch(...) {
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    return TCL_OK;
}

//}}}
//{{{  parse_tclcode_property()         

// ----------------------------------------------------------------------------

int
CdlParse::parse_tclcode_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                 char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_TclCode))
{
    CYG_REPORT_FUNCNAME("parse_tclcode_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_TclCode new_property = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index      = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);
        
        if (data_index == argc) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing Tcl code.");
        } else if ((data_index + 1) < argc) {
            CdlParse::report_property_parse_error(interp, argv[0], std::string("Invalid number of arguments.\n") +
                                         "Expecting one argument, a Tcl code fragment.");
        } else if (!Tcl_CommandComplete(argv[data_index])) {
            CdlParse::report_property_parse_error(interp, argv[0], "Incomplete Tcl code fragment.");
        } else {
        
            CdlNode current_node = interp->get_node();
            CYG_ASSERTC(0 != current_node);
            new_property = CdlProperty_TclCodeBody::make(current_node, name, argv[data_index], argc, argv, options);
            if (0 != final_parser) {
                (*final_parser)(interp, new_property);
            }
        }
     } catch(...) {
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    return TCL_OK;
}

//}}}
//{{{  parse_stringvector_property()    

// ----------------------------------------------------------------------------

int
CdlParse::parse_stringvector_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                      char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_StringVector),
                                      bool allow_empty)
{
    CYG_REPORT_FUNCNAME("parse_tclcode_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_StringVector new_property = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index      = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);
        
        if (!allow_empty && (data_index == argc)) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing arguments.");
        } else {

            // Creating the property requires a vector of strings.
            std::vector<std::string>  strings;
            for ( ; data_index < argc; data_index++) {
                strings.push_back(argv[data_index]);
            }
            CdlNode current_node = interp->get_node();
            CYG_ASSERTC(0 != current_node);
            new_property = CdlProperty_StringVectorBody::make(current_node, name, strings, argc, argv, options);
            if (0 != final_parser) {
                (*final_parser)(interp, new_property);
            }
        }
    } catch(...) {

        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    return TCL_OK;
}

//}}}
//{{{  parse_reference_property()       

// ----------------------------------------------------------------------------

int
CdlParse::parse_reference_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                   char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_Reference),
                                   CdlUpdateHandler update_handler)
{
    CYG_REPORT_FUNCNAME("parse_reference_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_Reference new_property = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);
        
        if (data_index == argc) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing argument.");
        } else if ((data_index + 1) < argc) {
            CdlParse::report_property_parse_error(interp, argv[0], "Too many arguments, expecting just one.");
        } else {
            std::string refname = argv[data_index];
            if (!Cdl::is_valid_cdl_name(refname)) {
                CdlParse::report_property_parse_error(interp, argv[0], "`" + refname + "' is not a valid CDL name");
            } else {
                CdlNode current_node = interp->get_node();
                CYG_ASSERTC(0 != current_node);
                new_property = CdlProperty_ReferenceBody::make(current_node, name, refname,
                                                               update_handler, argc, argv, options);
                if (0 != final_parser) {
                    (*final_parser)(interp, new_property);
                }
            }
        }
    } catch(...) {
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    return TCL_OK;
}

//}}}
//{{{  parse_expression_property()      

// ----------------------------------------------------------------------------

int
CdlParse::parse_expression_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                    char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_Expression),
                                    CdlUpdateHandler update_handler)
{
    CYG_REPORT_FUNCNAME("parse_expression_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_Expression new_property = 0;
    CdlExpression expr = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);
        
        std::string all_args = CdlParse::concatenate_argv(argc, argv, data_index);
        if ("" == all_args) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing expression data.");
        } else {
        
            // The CdlExpression class has its own parsing routine. This
            // will raise an exception if there are any problems. It is
            // desirable to catch the exception and report the error via
            // the normal reporting mechanisms, which may allow parsing to
            // continue.
            try {
                expr = CdlExpressionBody::parse(all_args);
            } catch(CdlParseException e) {
                CdlParse::report_property_parse_error(interp, argv[0], e.get_message());
            }
            if (0 != expr) {
                CdlNode current_node = interp->get_node();
                CYG_ASSERTC(0 != current_node);
                new_property = CdlProperty_ExpressionBody::make(current_node, name, expr, update_handler, argc, argv, options);
                if (0 != final_parser) {
                    (*final_parser)(interp, new_property);
                }
            }
        }
    } catch(...) {
        if (0 != expr) {
            delete expr;
        }
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    
    if (0 != expr) {
        delete expr;
    }
    return TCL_OK;
}

//}}}
//{{{  parse_list_expression_property() 

// ----------------------------------------------------------------------------

int
CdlParse::parse_listexpression_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                        char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_ListExpression),
                                        CdlUpdateHandler update_handler)
{
    CYG_REPORT_FUNCNAME("parse_list_expression_property");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_ListExpression new_property = 0;
    CdlListExpression expr = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);

        std::string all_args = CdlParse::concatenate_argv(argc, argv, data_index);
        if ("" == all_args) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing list expression data.");
        } else {
        
            try {
                expr = CdlListExpressionBody::parse(all_args);
            } catch(CdlParseException e) {
                CdlParse::report_property_parse_error(interp, argv[0], e.get_message());
            }
            if (0 != expr) {
                CdlNode current_node = interp->get_node();
                CYG_ASSERTC(0 != current_node);
                new_property = CdlProperty_ListExpressionBody::make(current_node, name, expr, update_handler,
                                                                    argc, argv, options);
                if (0 != final_parser) {
                    (*final_parser)(interp, new_property);
                }
            }
        }
    } catch(...) {
        if (0 != expr) {
            delete expr;
        }
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    if (0 != expr) {
        delete expr;
    }
    return TCL_OK;
}

//}}}
//{{{  parse_goalexpression_property()  

// ----------------------------------------------------------------------------

int
CdlParse::parse_goalexpression_property(CdlInterpreter interp, int argc, char** argv, std::string name,
                                        char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_GoalExpression),
                                        CdlUpdateHandler update_handler)
{
    CYG_REPORT_FUNCNAMETYPE("parse_goal_expression_property", "result %d");
    CYG_PRECONDITION_CLASSC(interp);
    
    CdlProperty_GoalExpression new_property = 0;
    CdlGoalExpression expr = 0;
    try {
        std::vector<std::pair<std::string,std::string> > options;
        int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options);

        std::string all_args = CdlParse::concatenate_argv(argc, argv, data_index);
        if ("" == all_args) {
            CdlParse::report_property_parse_error(interp, argv[0], "Missing goal expression data.");
        } else {

            try {
                expr = CdlGoalExpressionBody::parse(all_args);
            } catch(CdlParseException e) {
                CdlParse::report_property_parse_error(interp, argv[0], e.get_message());
            }
            if (0 != expr) {
                CdlNode current_node = interp->get_node();
                CYG_ASSERTC(0 != current_node);
                new_property = CdlProperty_GoalExpressionBody::make(current_node, name, expr, update_handler,
                                                                    argc, argv, options);
                if (0 != final_parser) {
                    (*final_parser)(interp, new_property);
                }
            }
        }
    } catch(...) {

        if (0 != expr) {
            delete expr;
        }
        if (0 != new_property) {
            delete new_property;
        }
        throw;
    }
    if (0 != expr) {
        delete expr;
    }

    return TCL_OK;
}

//}}}

//}}}
