Mercurial > ecos-v2_0-branch
diff host/libcdl/expr.cxx @ 76:435cced73e2f ecos-v1_3_1-release
eCos v1.3.1 merged from eCos master repository on 2000-03-27-23:22:51-BST
| author | jlarmour |
|---|---|
| date | Tue, 28 Mar 2000 14:10:45 +0000 |
| parents | |
| children | 6736c52df507 |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/host/libcdl/expr.cxx @@ -0,0 +1,3145 @@ +//{{{ Banner + +//============================================================================ +// +// expr.cxx +// +// Implementation of the various CDL expression classes. +// +//============================================================================ +//####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/02 +// 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> + +//}}} + +//{{{ Statics + +// ---------------------------------------------------------------------------- +CYGDBG_DEFINE_MEMLEAK_COUNTER(CdlEvalContext); +CYGDBG_DEFINE_MEMLEAK_COUNTER(CdlExpressionBody); +CYGDBG_DEFINE_MEMLEAK_COUNTER(CdlListExpressionBody); +CYGDBG_DEFINE_MEMLEAK_COUNTER(CdlGoalExpressionBody); + +//}}} +//{{{ CdlEvalContext + +// ---------------------------------------------------------------------------- +// A utility class to keep track of the context in which expression +// evaluation is happening. + +CdlEvalContext::CdlEvalContext(CdlTransaction transaction_arg, CdlNode node_arg, CdlProperty property_arg, + CdlToplevel toplevel_arg) +{ + CYG_REPORT_FUNCNAME("CdlEvalContext::constructor"); + CYG_REPORT_FUNCARG4XV(this, transaction_arg, node_arg, property_arg); + + transaction = transaction_arg; + node = node_arg; + property = property_arg; + if (0 == toplevel_arg) { + if (0 != transaction) { + toplevel = transaction->get_toplevel(); + } else if (0 != node) { + toplevel = node->get_toplevel(); + } else { + toplevel = toplevel_arg; + } + } else { + toplevel = toplevel_arg; + } + + cdlevalcontext_cookie = CdlEvalContext_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +CdlEvalContext::~CdlEvalContext() +{ + CYG_REPORT_FUNCNAME("CdlEvalContext::destructor"); + CYG_PRECONDITION_THISC(); + + cdlevalcontext_cookie = CdlEvalContext_Invalid; + transaction = 0; + node = 0; + property = 0; + toplevel = 0; + CYGDBG_MEMLEAK_DESTRUCTOR(); + + CYG_REPORT_RETURN(); +} + +bool +CdlEvalContext::check_this(cyg_assert_class_zeal zeal) const +{ + if (CdlEvalContext_Magic != cdlevalcontext_cookie) { + return false; + } + CYGDBG_MEMLEAK_CHECKTHIS(); + + if ((0 != transaction) && !transaction->check_this(zeal)) { + return false; + } + if ((0 != toplevel) && !toplevel->check_this(zeal)) { + return false; + } + if ((0 != node) && !node->check_this(zeal)) { + return false; + } + if ((0 != property) && !property->check_this(zeal)) { + return false; + } + return true; +} + +//}}} +//{{{ Expression parsing + +//{{{ Description + +// ---------------------------------------------------------------------------- +// There are a number of different entry points related to expression parsing, +// largely to support list and goal expressions. All of these eventually +// end up calling the function +// continue_parse(expr, data, index, token, token_end) +// +// The expr argument holds an existing expression object that needs to be +// updated. If token is Invalid then we are at the start of an expression +// (but not necessarily at the start of the string). +// +// The data string holds all of the expression that should be parsed. +// It is formed by concatenating all non-option arguments to the +// appropriate property command, with spaces between them. +// +// index is an input/output variable. On input it indicates where in +// the string parsing should continue. On output it indicates the +// location within the string where the terminating token began. +// +// token is an input/output variable. On input it can have the values +// Invalid or And. The former means that we are parsing a completely +// new expression. The latter is used for goal expressions: it is +// necessary to parse a new expression and then combine it with the +// existing one. +// +// token_end is an output variable. It indicates the location within +// the string where the terminating token ended. This is useful for +// e.g. ranges in a list expression. +// +// A conventional recursive descent parser is used. + +//}}} +//{{{ Tokenization + +// ---------------------------------------------------------------------------- +// Tokenization. + +//{{{ token enum + +// A separate token enum is necessary, rather than re-using the CdlExprOp +// enum. Some tokens may correspond to several operators, and some tokens +// such as close-bracket do not correspond directly to an operator at all. +enum token { + T_Invalid = -2, + + T_EOD = -1, + T_Reference = 1, // CYGPKG_HAL + T_String = 2, // "hello" + T_Integer = 3, // 123 + T_Double = 4, // 3.1415 + T_Range = 5, // to + T_OpenBracket = 6, // ( + T_CloseBracket = 7, // ) + T_Minus = 8, // - + T_Plus = 9, // + + T_Times = 10, // * + T_Divide = 11, // / + T_Exclamation = 12, // ! + T_Tilde = 13, // ~ + T_Questionmark = 14, // ? + T_Remainder = 15, // % + T_LeftShift = 16, // << + T_RightShift = 17, // >> + T_LessThan = 18, // < + T_LessEqual = 19, // <= + T_GreaterThan = 20, // > + T_GreaterEqual = 21, // >= + T_Equal = 22, // == + T_NotEqual = 23, // != + T_BitAnd = 24, // & + T_BitXor = 25, // ^ + T_BitOr = 26, // | + T_And = 27, // && + T_Or = 28, // || + T_Colon = 29 // : (in a conditional) +}; + +//}}} +//{{{ Statics + +// Statics to keep track of the current state. +static std::string current_data = ""; +static unsigned int current_index = 0; +static unsigned int token_start = 0; +static int current_char = EOF; +static token current_token = T_Invalid; +static std::string current_string = ""; +static std::string current_reference = ""; +static cdl_int current_int = 0; +static double current_double = 0.0; +static CdlValueFormat current_format = CdlValueFormat_Default; + +//}}} +//{{{ Character access + +// ---------------------------------------------------------------------------- +// Individual character access. +// Note that current_index is one character past current_char. + +// Return the next character in the string, or EOF +static void +next_char() +{ + if (current_index >= current_data.size()) { + current_char = EOF; + } else { + current_char = current_data[current_index++]; + } +} + +// Go back a character. This is useful when parsing +// strings. It is the responsibility of the calling code +// to make sure that we are not at the start of the buffer. +static void +backup_char() +{ + CYG_ASSERTC(((EOF == current_char) && (0 < current_index)) || (1 < current_index)); + if (EOF != current_char) { + current_index--; + } + current_char = current_data[current_index - 1]; +} + +//}}} +//{{{ get_error_location() + +// ---------------------------------------------------------------------------- +// Construct part of a diagnostic message, indicating the +// area in the data where the error occurred. This string +// is of the form {...data} ^char^ {data...}. Ideally +// the ^ markers would be on a subsequent line, eliminating +// the need for braces, but there is insufficient control +// of how the message gets presented to the user. +// +// Care has to be taken with EOD. +static std::string +get_error_location() +{ + CYG_REPORT_FUNCNAME("get_error_location"); + std::string result = ""; + + // token_start is probably the best place for centering the error. + // current_index is past the point where the error has occurred. + if (token_start > 1) { + if (token_start > 16) { + result = "{..." + current_data.substr(token_start - 13, 13) + "} "; + } else { + result = "{" + current_data.substr(0, token_start) + "}"; + } + } + + if (current_char == EOF) { + result += " <end of data>"; + } else { + result += " ^" + std::string(1, current_data[token_start]) + "^ "; + } + + if (token_start < current_data.size()) { + if ((token_start + 16) < current_data.size()) { + result += "{" + current_data.substr(token_start + 1, current_data.size() - (token_start+1)) + "}"; + } else { + result += "{" + current_data.substr(token_start, 13) + "...}"; + } + } + + CYG_REPORT_RETURN(); + return result; +} + +//}}} +//{{{ Token translation + +// ---------------------------------------------------------------------------- + +// Convert a token into a binary expression operator +static CdlExprOp +token_to_binary_expr_op() +{ + CYG_REPORT_FUNCNAMETYPE("token_to_expr_op", "op %d"); + CdlExprOp result = CdlExprOp_Invalid; + + switch(current_token) { + case T_Minus: result = CdlExprOp_Subtract; break; + case T_Plus: result = CdlExprOp_Add; break; + case T_Times: result = CdlExprOp_Multiply; break; + case T_Divide: result = CdlExprOp_Divide; break; + case T_Remainder: result = CdlExprOp_Remainder; break; + case T_LeftShift: result = CdlExprOp_LeftShift; break; + case T_RightShift: result = CdlExprOp_RightShift; break; + case T_LessThan: result = CdlExprOp_LessThan; break; + case T_LessEqual: result = CdlExprOp_LessEqual; break; + case T_GreaterThan: result = CdlExprOp_GreaterThan; break; + case T_GreaterEqual: result = CdlExprOp_GreaterEqual; break; + case T_Equal: result = CdlExprOp_Equal; break; + case T_NotEqual: result = CdlExprOp_NotEqual; break; + case T_BitAnd: result = CdlExprOp_BitAnd; break; + case T_BitXor: result = CdlExprOp_BitXor; break; + case T_BitOr: result = CdlExprOp_BitOr; break; + case T_And: result = CdlExprOp_And; break; + case T_Or: result = CdlExprOp_Or; break; + + default: result = CdlExprOp_Invalid; break; + } + + CYG_REPORT_RETVAL(result); + return result; +} + +// Convert a token into an ExprOp. This way the internal token enum does +// not need to be exported in order to define the interface. +// +// In practice the higher level code will only look for a handful of +// cases, mainly EOD and the range operator, but we might as well +// do the job property. +static CdlExprOp +token_to_expr_op() +{ + CYG_REPORT_FUNCNAMETYPE("token_to_expr_op", "expr op %d"); + CdlExprOp result; + + // Many of the tokens are already handled for binary operators. + result = token_to_binary_expr_op(); + if (CdlExprOp_Invalid == result) { + switch(current_token) { + case T_EOD: result = CdlExprOp_EOD; break; + case T_Reference: result = CdlExprOp_Reference; break; + case T_String: result = CdlExprOp_StringConstant; break; + case T_Integer: result = CdlExprOp_IntegerConstant; break; + case T_Double: result = CdlExprOp_DoubleConstant; break; + case T_Range: result = CdlExprOp_Range; break; + case T_Exclamation: result = CdlExprOp_LogicalNot; break; + case T_Tilde: result = CdlExprOp_BitNot; break; + case T_Questionmark: + case T_Colon: result = CdlExprOp_Cond; break; // best guess + + case T_OpenBracket: + case T_CloseBracket: + case T_Invalid: + default: result = CdlExprOp_Invalid; break; + } + } + CYG_REPORT_RETVAL(result); + return result; +} + +// A utility routine to turn the current token back into a string +// This is used for diagnostics. +static std::string +token_to_string() +{ + CYG_REPORT_FUNCNAME("token_to_string"); + std::string result = ""; + + switch(current_token) { + case T_EOD: result = "<end of data>"; break; + case T_Reference: result = "reference to " + current_reference; break; + case T_String: result = "string \"" + current_string + "\""; break; + case T_Integer: + { + std::string tmp; + Cdl::integer_to_string(current_int, tmp, current_format); + result = "integer constant " + tmp; + break; + } + case T_Double: + { + std::string tmp; + Cdl::double_to_string(current_double, tmp, current_format); + result = "double constant " + tmp; + break; + } + case T_Range: result = "range operator \"to\""; break; + case T_OpenBracket: result = "open bracket ("; break; + case T_CloseBracket: result = "close bracket )"; break; + case T_Minus: result = "minus sign -"; break; + case T_Plus: result = "plus sign +"; break; + case T_Times: result = "multiply operator *"; break; + case T_Divide: result = "divide operator /"; break; + case T_Exclamation: result = "not operator !"; break; + case T_Tilde: result = "bitwise not operator ~"; break; + case T_Questionmark: result = "question mark ?"; break; + case T_Remainder: result = "remainder operator %"; break; + case T_LeftShift: result = "left shift operator <<"; break; + case T_RightShift: result = "right shift operator >>"; break; + case T_LessThan: result = "less-than operator <"; break; + case T_LessEqual: result = "less-or-equal operator <="; break; + case T_GreaterThan: result = "greater-than operator >"; break; + case T_GreaterEqual: result = "greater-or-equal operator >="; break; + case T_Equal: result = "equality operator =="; break; + case T_NotEqual: result = "not-equal operator !="; break; + case T_BitAnd: result = "bitwise and operator &"; break; + case T_BitXor: result = "bitwise xor operator ^"; break; + case T_BitOr: result = "bitwise or operator |"; break; + case T_And: result = "and operator &&"; break; + case T_Or: result = "or operator ||"; break; + case T_Colon: result = "colon"; break; + case T_Invalid: + default: result = "<invalid token>"; break; + } + + CYG_REPORT_RETURN(); + return result; +} + +//}}} +//{{{ Literals + +// ---------------------------------------------------------------------------- +//{{{ process_string() + +// The start of a string has been detected. Work out the entire string, +// allowing for backslash escapes. +static void +process_string() + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("process_string"); + CYG_ASSERTC('"' == current_char); + CYG_ASSERTC("" == current_string); + + std::string result = ""; + + // Move past the leading quote mark. + next_char(); + while ('"' != current_char) { + if (EOF == current_char) { + throw CdlParseException("Premature end of data in string constant.\n" + get_error_location()); + } else if ('\\' == current_char) { + // Allow \a, \b, \f, \n, \r, \t, \v, \ddd and \xhh. + // Also copy with \newline space. + // Any other character gets passed through unchanged. + next_char(); + switch(current_char) { + case EOF: + throw CdlParseException("Premature end of data after backslash in string constant.\n" + get_error_location()); + case 'a': + result += '\a'; + break; + case 'b': + result += '\b'; + break; + case 'f': + result += '\f'; + break; + case 'n': + result += '\n'; + break; + case 'r': + result += '\r'; + break; + case 't': + result += '\t'; + break; + case 'v': + result += '\v'; + break; + case 'x': + { + cdl_int tmp = 0; + next_char(); + if (!isxdigit(current_char)) { + throw CdlParseException("Non-hexadecimal digit detected in string \\x escape sequence.\n" + + get_error_location()); + } + // NOTE: there is no overflow detection here. + do { + tmp *= 16; + if (('0' <= current_char) && (current_char <= '9')) { + tmp += (current_char - '0'); + } else if (('a' <= current_char) && (current_char <= 'f')) { + tmp += 10 + (current_char - 'a'); + } else if (('A' <= current_char) && (current_char <= 'F')) { + tmp += 10 + (current_char - 'A'); + } else { + CYG_FAIL("C library error, isxdigit() succeeded on non-hexadecimal character"); + } + next_char(); + } while(isxdigit(current_char)); + backup_char(); + result += (char) tmp; + } + + case '\n': + next_char(); + while ((EOF != current_char) && isspace(current_char)) { + next_char(); + } + // We have gone one too far, back up. + backup_char(); + result += " "; + break; + + default: + if (('0' <= current_char) && (current_char <= '7')) { + // A sequence of octal digits. + cdl_int tmp = 0; + do { + tmp = (8 * tmp) + (current_char - '0'); + next_char(); + } while (('0' <= current_char) && (current_char <= '7')); + backup_char(); + result += (char) tmp; + } else { + // For all other backslash sequences, just add the second character + result += (char) current_char; + } + } + } else { + result += (char) current_char; + } + next_char(); + } + // The closing quote has been reached, move past it. + next_char(); + + // And all done. + current_token = T_String; + current_string = result; + + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ process_number() + +// The start of a number has been detected. This number may be an +// integer or a double. It is necessary to figure out where the number +// ends and invoke the appropriate Cdl:: conversion utility. +// +// Care has to be taken with termination. Consider a token such as +// 134_5. This is not a string because there are no quote marks, nor +// is it a valid reference, and because it begins with a digit it +// should be interpreted as a number. The 134 bit works fine, then +// number processing stops leaving current_char as '_'. If we are +// parsing a list expression then the following _5 will actually +// be interpreted as a reference. To avoid this, here is a utility +// which checks number completion and throws an exception if +// necessary. +static void check_number_termination() + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("check_number_termination"); + + // End-of-data or any whitespace is ok. + if ((EOF != current_char) && !isspace(current_char)) { + // Any valid operator is ok as well, or brackets for that matter. + if (('-' != current_char) && ('+' != current_char) && ('*' != current_char) && + ('/' != current_char) && ('!' != current_char) && ('~' != current_char) && + ('?' != current_char) && ('%' != current_char) && ('<' != current_char) && + ('>' != current_char) && ('=' != current_char) && ('&' != current_char) && + ('^' != current_char) && ('|' != current_char) && (':' != current_char) && + ('(' != current_char) && (')' != current_char)) { + + std::string tmp; + Cdl::integer_to_string(current_int, tmp); + throw CdlParseException("Invalid character detected after number " + tmp + "\n" + get_error_location()); + } + } + + CYG_REPORT_RETURN(); +} + +static void +process_number() + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("process_number"); + + std::string tmp = ""; + bool is_float = false; + + // Detect the special cases of 0x and octal numbers. + if ('0' == current_char) { + next_char(); + if (('x' == current_char) || ('X' == current_char)) { + + next_char(); + if (!isxdigit(current_char)) { + throw CdlParseException("Invalid hexadecimal number, expected at least one hexadecimal digit after 0x.\n" + + get_error_location()); + } + current_int = 0; + do { + current_int *= 16; + if (('0' <= current_char) && (current_char <= '9')) { + current_int += (current_char - '0'); + } else if (('a' <= current_char) && (current_char <= 'f')) { + current_int += 10 + (current_char - 'a'); + } else { + current_int += 10 + (current_char - 'A'); + } + next_char(); + } while(isxdigit(current_char)); + current_token = T_Integer; + current_format = CdlValueFormat_Hex; + check_number_termination(); + CYG_REPORT_RETURN(); + return; + + } else if (('0' <= current_char) && (current_char <= '7')) { + + current_int = 0; + do { + current_int *= 8; + current_int += (current_char - '0'); + next_char(); + } while (('0' <= current_char) && (current_char <= '7')); + current_token = T_Integer; + current_format = CdlValueFormat_Octal; + check_number_termination(); + CYG_REPORT_RETURN(); + return; + + } else if (('8' == current_char) || ('9' == current_char)) { + throw CdlParseException("08... and 09... are not valid octal numbers.\n" + get_error_location()); + } else { + // This could be plain 0, or 0.123 + // Backup, and let the rest of the code take care of things + backup_char(); + } + } + + do { + tmp += (char) current_char; + next_char(); + } while(isdigit(current_char)); + + // If we have found a . then we have a floating point number with a fraction. + if ('.' == current_char) { + tmp += '.'; + next_char(); + if (!isdigit(current_char)) { + throw CdlParseException("Invalid floating point constant, expected a digit for the fractional part.\n" + + get_error_location()); + } + is_float = true; + do { + tmp += (char) current_char; + next_char(); + } while(isdigit(current_char)); + } + + // If we have found e or E then we have a floating point number with an exponent + if (('e' == current_char) || ('E' == current_char)) { + tmp += 'E'; + next_char(); + if (('+' == current_char) || ('-' == current_char)) { + tmp += current_char; + next_char(); + } + if (!isdigit(current_char)) { + throw CdlParseException("Invalid floating point constant, expected a digit for the exponent.\n" + + get_error_location()); + } + is_float = true; + do { + tmp += (char) current_char; + next_char(); + } while(isdigit(current_char)); + } + + if (is_float) { + if (!Cdl::string_to_double(tmp, current_double)) { + throw CdlParseException("Invalid floating point constant `" + tmp + "'.\n" + get_error_location()); + } else { + current_token = T_Double; + } + } else { + if (!Cdl::string_to_integer(tmp, current_int)) { + throw CdlParseException("Invalid integer constant `" + tmp + "'.\n" + get_error_location()); + } else { + current_token = T_Integer; + } + } + + check_number_termination(); + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ process_reference() + +// The start of a reference has been detected. +// A reference name must be a valid C preprocessor name, so the +// only characters allowed are underscore, upper and lower case +// characters, and digits. The first character cannot be a digit, +// but that has been checked already. +// +// Some care has to be taken with locale's, the C library may decide +// that a character is a letter even though the same character is not +// valid as far as the preprocessor is concerned. +static void +process_reference() + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("process_reference"); + + do { + current_reference += (char) current_char; + next_char(); + } while (('_' == current_char) || isdigit(current_char) || + (('a' <= current_char) && (current_char <= 'z')) || + (('A' <= current_char) && (current_char <= 'Z'))); + + current_token = T_Reference; + + CYG_REPORT_RETURN(); +} + +//}}} + +//}}} +//{{{ next_token() + +// ---------------------------------------------------------------------------- +// Work out what the next token is. This includes the handling of +// strings, integers, doubles, and references. +static void +next_token() + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("next_token", "token %d"); + + // Make sure there is no dross left lying around from the previous call. + current_token = T_Invalid; + current_string = ""; + current_reference = ""; + current_int = 0; + current_double = 0.0; + current_format = CdlValueFormat_Default; + + // Skip leading white space. This includes newlines, tabs, etc, + // consider the case of: + // ... + // legal_values { + // 1 + // 2 + // 4 + // .. + // } + // ... + // which is perfectly legitimate. White space inside strings + // is handled by the string literal code, and does not get filtered + // out here. + // + // Exactly which characters are white-space is implementation-defined, + // so a special check for EOF is in order. + while ((EOF != current_char) && isspace(current_char)) { + next_char(); + } + + // Remember the token starting point. next_char() has actually moved + // the index on by one. + token_start = current_index - 1; + + // The simple cases can be handled inline, the more complicated cases + // involve other functions + switch(current_char) { + + case EOF: + current_token = T_EOD; + break; + + case '"': + process_string(); + break; + + case '(': + current_token = T_OpenBracket; + next_char(); + break; + + case ')': + current_token = T_CloseBracket; + next_char(); + break; + + // At this level it is not possible to distinguish between + // unary and binary operators, so no attempt is made to + // turn - and + into part of a number. + case '-': + current_token = T_Minus; + next_char(); + break; + + case '+': + current_token = T_Plus; + next_char(); + break; + + case '*': + current_token = T_Times; + next_char(); + break; + + case '/': + current_token = T_Divide; + next_char(); + break; + + case '!': + next_char(); + if ('=' == current_char) { + current_token = T_NotEqual; + next_char(); + } else { + current_token = T_Exclamation; + } + break; + + case '~': + current_token = T_Tilde; + next_char(); + break; + + case '?': + current_token = T_Questionmark; + next_char(); + break; + + case '%': + current_token = T_Remainder; + next_char(); + break; + + case '<': + next_char(); + if ('<' == current_char) { + current_token = T_LeftShift; + next_char(); + } else if ('=' == current_char) { + current_token = T_LessEqual; + next_char(); + } else { + current_token = T_LessThan; + } + break; + + case '>': + next_char(); + if ('>' == current_char) { + current_token = T_RightShift; + next_char(); + } else if ('=' == current_char) { + current_token = T_GreaterEqual; + next_char(); + } else { + current_token = T_GreaterThan; + } + break; + + case '=': + next_char(); + if ('=' != current_char) { + throw CdlParseException(std::string("Incomplete == operator in expression.\n") + get_error_location()); + } else { + current_token = T_Equal; + next_char(); + } + break; + + case '&': + next_char(); + if ('&' == current_char) { + current_token = T_And; + next_char(); + } else { + current_token = T_BitAnd; + } + break; + + case '^': + current_token = T_BitXor; + next_char(); + break; + + case '|': + next_char(); + if ('|' == current_char) { + current_token = T_Or; + next_char(); + } else { + current_token = T_BitOr; + } + break; + + case ':': + current_token = T_Colon; + next_char(); + break; + + default: + // String constants have been handled already. The only + // valid tokens that are left are numbers, references and + // the rang eoperator. + // + // Numbers should begin with a digit (plus and minus are + // tokenized separately). + // + // References must be valid C preprocessor symbols, i.e. + // they must begin with either a letter or an underscore. + // The range operator is handled most conveniently as + // a special case of a reference. + if (isdigit(current_char)) { + process_number(); + } else if (('_' == current_char) || + (('a' <= current_char) && (current_char <= 'z')) || + (('A' <= current_char) && (current_char <= 'Z'))) { + process_reference(); + if ("to" == current_reference) { + current_reference = ""; + current_token = T_Range; + } else { + current_token = T_Reference; + } + } else { + std::string msg = "Unexpected character '"; + msg += (char) current_char; + msg += "' in expression.\n"; + msg += get_error_location(); + throw CdlParseException(msg); + } + break; + } + + CYG_REPORT_RETVAL(current_token); +} + +//}}} +//{{{ initialise_tokenisation() + +// ---------------------------------------------------------------------------- +// This is called at the start of expression parsing. It +// sets up the appropriate statics, and provides initial +// values for current_char and current_token. +static void +initialise_tokenisation(std::string data, int index) +{ + CYG_REPORT_FUNCNAME("initialise_tokenization"); + + current_data = data; + current_index = static_cast<unsigned int>(index); + token_start = current_index; + next_char(); + next_token(); + + CYG_REPORT_RETURN(); +} + +//}}} + +//}}} +//{{{ Syntactic analysis + +// ---------------------------------------------------------------------------- +// Syntactic analysis. +// +// The BNF of CDL expressions is something like this: +// +// <expression> ::= <conditional> +// <conditional> ::= <or> ? <conditional> : <conditional> | <or> +// <or> ::= <and> [<or op> <and>] || +// <and> ::= <bitor> [<and op> <bitor>] ?? +// <bitor> ::= <bitxor> [<bitor op> <bitxor>] | +// <bitxor> ::= <bitand> [<bitxor op> <bitand>] ^ +// <bitand> ::= <eq> [<bitand op> <eq>] & +// <eq> ::= <comp> [<eq op> <comp>] == != +// <comp> ::= <shift> [<comp op> <shift>] < <= > >= +// <shift> ::= <add> [<shift op> <add>] << >> +// <add> ::= <mult> [<add op> <mult>] + - +// <mult> ::= <unary> [<mult op> <unary>] * / % +// <unary> ::= -<unary> | +<unary> | !<unary> | *<unary> | ?<unary> | +// <string constant> | <integer constant> | +// <double constant> | <reference> | +// ( <expression> ) +// +// There are separate functions for each of these terms. + +// A forward declaration, needed for bracketed subexpressions. +static void parse_expression(CdlExpression) throw(CdlParseException, std::bad_alloc); + +// A utility to add a reference to the current expression, returning +// the index. +static int +push_reference(CdlExpression expr, const std::string& reference) +{ + CYG_REPORT_FUNCNAMETYPE("push_reference", "new index %d"); + CYG_PRECONDITION_CLASSC(expr); + + CdlReference ref(reference); + expr->references.push_back(ref); + int result = (int) expr->references.size() - 1; + + CYG_REPORT_RETVAL(result); + return result; +} + +// A utility to add a subexpression, returning its index. +static void +push_subexpression(CdlExpression expr, const CdlSubexpression& subexpr) +{ + CYG_REPORT_FUNCNAME("push_subexpression"); + CYG_PRECONDITION_CLASSC(expr); + + expr->sub_expressions.push_back(subexpr); + expr->first_subexpression = ((int) expr->sub_expressions.size()) - 1; + + CYG_REPORT_RETURN(); +} + +// Another utility to hold of the most recent subexpression +static CdlSubexpression& +current_subexpression(CdlExpression expr) +{ + CYG_REPORT_FUNCNAME("current_subexpression"); + + CdlSubexpression& result = expr->sub_expressions[expr->first_subexpression]; + + CYG_REPORT_RETURN(); + return result; +} + +static void +parse_unary(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_operand"); + CYG_REPORT_FUNCARG1XV(expr); + CYG_PRECONDITION_CLASSC(expr); + + CdlSubexpression subexpr; + + switch(current_token) { + case T_EOD : + { + // This warrants a special case + throw CdlParseException("End of expression reached when expecting an operand.\n" + get_error_location()); + } + + case T_Reference : + { + subexpr.op = CdlExprOp_Reference; + subexpr.reference_index = push_reference(expr, current_reference); + push_subexpression(expr, subexpr); + next_token(); + break; + } + + case T_String : + { + subexpr.op = CdlExprOp_StringConstant; + subexpr.constants = current_string; + push_subexpression(expr, subexpr); + next_token(); + break; + } + + case T_Integer : + { + subexpr.op = CdlExprOp_IntegerConstant; + subexpr.constants.set_integer_value(current_int, current_format); + push_subexpression(expr, subexpr); + next_token(); + break; + } + + case T_Double : + { + subexpr.op = CdlExprOp_DoubleConstant; + subexpr.constants.set_double_value(current_double, current_format); + push_subexpression(expr, subexpr); + next_token(); + break; + } + + case T_OpenBracket : + { + next_token(); + parse_expression(expr); + if (T_CloseBracket != current_token) { + throw CdlParseException("Missing close bracket after subexpression.\n" + get_error_location()); + } + next_token(); + break; + } + + case T_Minus : + { + next_token(); + parse_unary(expr); + CdlSubexpression& last_sub = current_subexpression(expr); + if (CdlExprOp_IntegerConstant == last_sub.op) { + // Do the negating inline, no need for another subexpression. + last_sub.constants = last_sub.constants.get_integer_value() * -1; + } else if (CdlExprOp_DoubleConstant == last_sub.op) { + last_sub.constants = last_sub.constants.get_double_value() * -1; + } else { + // We could detect certain cases such as string constants etc. + // For now don't bother. + subexpr.op = CdlExprOp_Negate; + subexpr.lhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + break; + } + + case T_Plus : + { + next_token(); + parse_unary(expr); + CdlSubexpression& last_sub = current_subexpression(expr); + if ((CdlExprOp_IntegerConstant == last_sub.op) || (CdlExprOp_DoubleConstant == last_sub.op)) { + // No need to do anything here. + } else { + subexpr.op = CdlExprOp_Plus; + subexpr.lhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + break; + } + + case T_Times : + { + next_token(); + parse_unary(expr); + subexpr.op = CdlExprOp_Indirect; + subexpr.lhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + break; + } + + case T_Exclamation : + { + next_token(); + parse_unary(expr); + subexpr.op = CdlExprOp_LogicalNot; + subexpr.lhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + break; + } + + case T_Tilde : + { + next_token(); + parse_unary(expr); + subexpr.op = CdlExprOp_BitNot; + subexpr.lhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + break; + } + + case T_Questionmark: + { + // This is the `active' operator, it can only be applied directly to a reference. + next_token(); + parse_unary(expr); + CdlSubexpression& last_sub = current_subexpression(expr); + if (CdlExprOp_Reference != last_sub.op) { + throw CdlParseException("The active operator ? can only be applied directly to a reference.\n" + + get_error_location()); + } + // There is no point in creating a new subexpression object, just modify + // the existing one. This has the useful side effect of avoiding + // reference substitution in the eval code. + last_sub.op = CdlExprOp_Active; + break; + } + default: + { + throw CdlParseException("Unexpected token `" + token_to_string() + "', expecting an operand.\n" + + get_error_location()); + } + } + + CYG_REPORT_RETURN(); +} + +static void +parse_multiply(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_multiply"); + + parse_unary(expr); + while ((T_Times == current_token) || (T_Divide == current_token) || (T_Remainder == current_token)) { + + CdlSubexpression subexpr; + subexpr.op = + (T_Times == current_token) ? CdlExprOp_Multiply : + (T_Divide == current_token) ? CdlExprOp_Divide : CdlExprOp_Remainder; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_unary(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_add(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_add"); + + parse_multiply(expr); + while ((T_Plus == current_token) || (T_Minus == current_token)) { + + CdlSubexpression subexpr; + subexpr.op = (T_Plus == current_token) ? CdlExprOp_Add : CdlExprOp_Subtract; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_multiply(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_shift(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_shift"); + + parse_add(expr); + while ((T_LeftShift == current_token) || (T_RightShift == current_token)) { + + CdlSubexpression subexpr; + subexpr.op = (T_LeftShift == current_token) ? CdlExprOp_LeftShift : CdlExprOp_RightShift; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_add(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_comparison(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_comparison"); + + parse_shift(expr); + while ((T_LessThan == current_token) || (T_LessEqual == current_token) || + (T_GreaterThan == current_token) || (T_GreaterEqual == current_token)) { + + CdlSubexpression subexpr; + subexpr.op = + (T_LessThan == current_token) ? CdlExprOp_LessThan : + (T_LessEqual == current_token) ? CdlExprOp_LessEqual : + (T_GreaterThan == current_token) ? CdlExprOp_GreaterThan : CdlExprOp_GreaterEqual; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_shift(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_equals(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_equals"); + + parse_comparison(expr); + while ((T_Equal == current_token) || (T_NotEqual == current_token)) { + + CdlSubexpression subexpr; + subexpr.op = (T_Equal == current_token) ? CdlExprOp_Equal : CdlExprOp_NotEqual; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_comparison(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_bitand(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_bitand"); + + parse_equals(expr); + while (T_BitAnd == current_token) { + + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_BitAnd; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_equals(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_bitxor(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_bitxor"); + + parse_bitand(expr); + while (T_BitXor == current_token) { + + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_BitXor; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_bitand(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_bitor(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_bitor"); + + parse_bitxor(expr); + while (T_BitOr == current_token) { + + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_BitOr; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_bitxor(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_and(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_and"); + parse_bitor(expr); + while (T_And == current_token) { + + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_And; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_bitor(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_or(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_or"); + + parse_and(expr); + while (T_Or == current_token) { + + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_Or; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_and(expr); + + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_conditional(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_conditional"); + + parse_or(expr); + if (T_Questionmark == current_token) { + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_Cond; + subexpr.lhs_index = expr->first_subexpression; + + next_token(); + parse_conditional(expr); + subexpr.rhs_index = expr->first_subexpression; + + if (T_Colon != current_token) { + throw CdlParseException("Expected colon in conditional expression.\n" + get_error_location()); + } + + next_token(); + parse_conditional(expr); + subexpr.rrhs_index = expr->first_subexpression; + + push_subexpression(expr, subexpr); + } + + CYG_REPORT_RETURN(); +} + +static void +parse_expression(CdlExpression expr) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("parse_expression"); + + parse_conditional(expr); + + CYG_REPORT_RETURN(); +} + +// ---------------------------------------------------------------------------- +// The entry point. +void +CdlExpressionBody::continue_parse(CdlExpression expr, std::string data, int& index, CdlExprOp& token, int& token_end) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlExpression::continue_parse"); + CYG_REPORT_FUNCARG1XV(expr); + CYG_PRECONDITION_CLASSC(expr); + CYG_PRECONDITIONC((CdlExprOp_Invalid == token) || (CdlExprOp_And == token)); + + int current_subexpr = expr->first_subexpression; + initialise_tokenisation(data, index); + parse_expression(expr); + if (CdlExprOp_And == token) { + CdlSubexpression subexpr; + subexpr.op = CdlExprOp_And; + subexpr.lhs_index = current_subexpr; + subexpr.rhs_index = expr->first_subexpression; + push_subexpression(expr, subexpr); + } + token = token_to_expr_op(); + index = token_start; + token_end = current_index; + + CYG_REPORT_RETURN(); +} + +//}}} + +//}}} +//{{{ Expression Evaluation + +// ---------------------------------------------------------------------------- +// Expression evaluation. This always happens in the context of a +// particular toplevel. The parsed expression is held in what amounts +// to a simple tree, so evaluation involves some recursion and a big +// switch statement. + +static void +evaluate_subexpr(CdlEvalContext& context, CdlExpression expr, int subexpr_index, CdlSimpleValue& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("evaluate_subexpr"); + CYG_REPORT_FUNCARG2XV(expr, subexpr_index); + CYG_ASSERTC((subexpr_index >= 0) && ((unsigned int)subexpr_index < expr->sub_expressions.size())); + + const CdlSubexpression& subexpr = expr->sub_expressions[subexpr_index]; + switch(subexpr.op) { + case CdlExprOp_StringConstant : + case CdlExprOp_IntegerConstant : + case CdlExprOp_DoubleConstant : + { + result = subexpr.constants; + break; + } + case CdlExprOp_Reference : + { + // This expression may be happening in the context of a particular + // property. If so then the destination may or may not be resolved, + // and this is significant in the context of loading and unloading. + // Alternatively this expression may be being evaluated inside + // some Tcl code, with no particular context. + CdlNode destination = 0; + if (0 != context.property) { + // There is a property, use the bound/unbound reference. + destination = expr->references[subexpr.reference_index].get_destination(); + } else { + // The destination name can be retrieved, but we still need some + // way of resolving it. + if (0 != context.toplevel) { + std::string destination_name = expr->references[subexpr.reference_index].get_destination_name(); + destination = context.toplevel->lookup(destination_name); + } + } + if (0 == destination) { + // There are two ways of handling this. + // 1) throw an eval exception, which will usually result + // in a new conflict object + // 2) substitute a value of 0. + // There should already be a conflict object for an + // unresolved reference, and having two conflicts for + // essentially the same error is not useful. Using a value + // of 0 allows things to continue for a bit longer. It is + // consistent with active vs. inactive values, gives + // basically the right result for "requires" properties, + // and so on. + // + // For now option (2) has it, but this decision may be + // reversed in future. + result = false; + } else { + CdlValuable valuable = dynamic_cast<CdlValuable>(destination); + if (0 == valuable) { + // This is a serious problem, an exception is warranted. + throw CdlEvalException("The expression references `" + destination->get_class_name() + " " + + destination->get_name() + "' which does not have a value."); + } else { + CdlSimpleValue::eval_valuable(context, valuable, result); + } + } + break; + } + case CdlExprOp_Negate : + { + // Unary -. Evaluate the target. If it is numeric, fine. Otherwise + // an error is warranted. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (result.has_integer_value()) { + result.set_integer_value(-1 * result.get_integer_value()); + } else if (result.has_double_value()) { + result.set_double_value(-1.0 * result.get_double_value()); + } else { + throw CdlEvalException("Attempt to negate non-numeric value `" + result.get_value() + "'."); + } + break; + } + case CdlExprOp_Plus : + { + // Unary +. Essentially this just checks that the current value is numeric. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if ((!result.has_integer_value()) && (!result.has_double_value())) { + throw CdlEvalException("Attempt to apply unary + operator to non-numeric value `" + result.get_value() + "'."); + } + break; + } + case CdlExprOp_LogicalNot : + { + // !x + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (result.get_bool_value()) { + result = false;; + } else { + result = true; + } + result.set_value_format(CdlValueFormat_Default); + break; + } + case CdlExprOp_BitNot : + { + // ~x. The operand must be an integer value. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (result.has_integer_value()) { + cdl_int tmp = result.get_integer_value(); + result = ~tmp; + } else { + throw CdlEvalException("Attempt to apply unary ~ operator to non-integer value `" + result.get_value() + "'."); + } + break; + } + case CdlExprOp_Indirect : + { + // *x. The operand must evaluate to a string, and that string should be + // the name of a CdlValuable object. + CdlNode destination = 0; + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + std::string name = result.get_value(); + + if (0 != context.toplevel) { + destination = context.toplevel->lookup(name); + } else { + CYG_FAIL("This situation should probably never happen."); + } + + if (0 == destination) { + throw CdlEvalException("Attempt to apply unary indirection operator * to `" + name + + "', which is not the name of a known CDL entity."); + } else { + CdlValuable valuable = dynamic_cast<CdlValuable>(destination); + if (0 == valuable) { + throw CdlEvalException("Attempt to apply unary indirection operator * to `" + name + + "', which does not have a value."); + } else { + CdlSimpleValue::eval_valuable(context, valuable, result); + } + } + break; + } + case CdlExprOp_Active : + { + // ?x. If x is currently unresolved then default to 0. + // See the CdlExprOp_Reference code above for a similar case. + CdlNode destination = 0; + if (0 != context.property) { + destination = expr->references[subexpr.reference_index].get_destination(); + } else { + if (0 != context.toplevel) { + std::string destination_name = expr->references[subexpr.reference_index].get_destination_name(); + destination = context.toplevel->lookup(destination_name); + } + } + + bool active = false; + if ((0 != destination) && context.transaction->is_active(destination)) { + active = true; + } + if (active) { + result = true; + } else { + result = false; + } + break; + } + case CdlExprOp_Multiply : + { + // x * y. For now this only makes sense for numerical data, + // but it is possible to mix and match integer and double + // precision data. + // + // Strictly speaking the rhs need only be evaluated if it + // is known that the lhs is numeric. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((!(lhs.has_integer_value() || lhs.has_double_value())) || + (!(rhs.has_integer_value() || rhs.has_double_value()))) { + throw CdlEvalException("Attempt to multiply non-numerical values: `" + lhs.get_value() + "' * `" + + rhs.get_value() + "'."); + } + if (lhs.has_integer_value() && rhs.has_integer_value()) { + result = lhs.get_integer_value() * rhs.get_integer_value(); + } else { + result = lhs.get_double_value() * rhs.get_double_value(); + } + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_Divide : + { + // x / y. Basically the same as multiplication, apart from a check for + // division by zero. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((!(lhs.has_integer_value() || lhs.has_double_value())) || + (!(rhs.has_integer_value() || rhs.has_double_value()))) { + throw CdlEvalException("Attempt to divide non-numerical values: `" + lhs.get_value() + "' / `" + + rhs.get_value() + "'."); + } + if (lhs.has_integer_value() && rhs.has_integer_value()) { + cdl_int rhs_val = rhs.get_integer_value(); + if (0 == rhs_val) { + throw CdlEvalException("Division by zero error: `" + lhs.get_value() + "' / `" + rhs.get_value() + "'."); + } else { + result = lhs.get_integer_value() / rhs_val; + } + } else { + double rhs_val = rhs.get_double_value(); + if (0.0 == rhs_val) { + throw CdlEvalException("Division by zero error: `" + lhs.get_value() + "' / `" + rhs.get_value() + "'."); + } + result = lhs.get_double_value() / rhs_val; + } + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_Remainder : + { + // x % y. Both operands must be integral. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the remainder operator on non integral data: `" + + lhs.get_value() + "' % `" + rhs.get_value() + "'."); + } + cdl_int rhs_val = rhs.get_integer_value(); + if (0 == rhs_val) { + throw CdlEvalException("Division by zero error: `" + lhs.get_value() + "' % `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() % rhs_val; + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_Add : + { + // x + y. For now this only makes sense for numerical data, + // but it is possible to mix and match integer and double + // precision data. Arguably for string data this operator + // should mean concatenation, but it would probably be + // safer to have a separate operator for that. + // + // Strictly speaking the rhs need only be evaluated if it + // is known that the lhs is numeric. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((!(lhs.has_integer_value() || lhs.has_double_value())) || + (!(rhs.has_integer_value() || rhs.has_double_value()))) { + throw CdlEvalException("Attempt to add non-numerical values: `" + lhs.get_value() + "' + `" + + rhs.get_value() + "'."); + } + if (lhs.has_integer_value() && rhs.has_integer_value()) { + result = lhs.get_integer_value() + rhs.get_integer_value(); + } else { + result = lhs.get_double_value() + rhs.get_double_value(); + } + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_Subtract : + { + // x - y. Again only numerical data is supported for now. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((!(lhs.has_integer_value() || lhs.has_double_value())) || + (!(rhs.has_integer_value() || rhs.has_double_value()))) { + throw CdlEvalException("Attempt to subtract non-numerical values: `" + lhs.get_value() + "' - `" + + rhs.get_value() + "'."); + } + if (lhs.has_integer_value() && rhs.has_integer_value()) { + result = lhs.get_integer_value() - rhs.get_integer_value(); + } else { + result = lhs.get_double_value() - rhs.get_double_value(); + } + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_LeftShift : + { + // x << y. Both operands must be integral. For now there is no + // check on the value of y. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the left-shift operator on non integral data: `" + + lhs.get_value() + "' << `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() << rhs.get_integer_value(); + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_RightShift : + { + // x >> y. Both operands must be integral. For now there is no + // check on the value of y. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the right-shift operator on non integral data: `" + + lhs.get_value() + "' >> `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() >> rhs.get_integer_value(); + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_LessThan : + case CdlExprOp_LessEqual : + case CdlExprOp_GreaterThan : + case CdlExprOp_GreaterEqual : + { + // x < y, and similar comparison operators. These share + // sufficient code to warrant a common implementation. Only + // numerical data is supported for now. These operator could + // be interpreted as e.g. substring operations, but arguably + // separate operators would be better for that. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((!(lhs.has_integer_value() || lhs.has_double_value())) || + (!(rhs.has_integer_value() || rhs.has_double_value()))) { + + std::string op_str = + (CdlExprOp_LessThan == subexpr.op) ? "<" : + (CdlExprOp_LessEqual == subexpr.op) ? "<=" : + (CdlExprOp_GreaterThan == subexpr.op) ? ">" : ">="; + + throw CdlEvalException("Attempt to compare non-numerical values: `" + lhs.get_value() + + "' " + op_str + " `" + rhs.get_value() + "'."); + } + bool val = false; + if (lhs.has_integer_value() && rhs.has_integer_value()) { + cdl_int lhs_val = lhs.get_integer_value(); + cdl_int rhs_val = rhs.get_integer_value(); + val = + (CdlExprOp_LessThan == subexpr.op) ? (lhs_val < rhs_val) : + (CdlExprOp_LessEqual == subexpr.op) ? (lhs_val <= rhs_val) : + (CdlExprOp_GreaterThan == subexpr.op) ? (lhs_val > rhs_val) : (lhs_val >= rhs_val); + } else { + double lhs_val = lhs.get_double_value(); + double rhs_val = rhs.get_double_value(); + val = + (CdlExprOp_LessThan == subexpr.op) ? (lhs_val < rhs_val) : + (CdlExprOp_LessEqual == subexpr.op) ? (lhs_val <= rhs_val) : + (CdlExprOp_GreaterThan == subexpr.op) ? (lhs_val > rhs_val) : (lhs_val >= rhs_val); + } + result = val; + break; + } + case CdlExprOp_Equal : + { + // x == y. For numerical data this should be a numerical comparison. + // Otherwise a string comparison has to be used. + bool val = false; + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((lhs.has_integer_value() || lhs.has_double_value()) && + (rhs.has_integer_value() || rhs.has_double_value())) { + + if (lhs.has_integer_value() && rhs.has_integer_value()) { + if (lhs.get_integer_value() == rhs.get_integer_value()) { + val = true; + } else { + val = false; + } + } else { + if (lhs.get_double_value() == rhs.get_double_value()) { + val = true; + } else { + val = false; + } + + } + } else { + // At least one of the two sides is non-numerical. Do a string comparison. + if (lhs.get_value() == rhs.get_value()) { + val = true; + } else { + val = false; + } + } + result = val; + break; + } + case CdlExprOp_NotEqual : + { + // x != y. For numerical data this should be a numerical comparison. + // Otherwise a string comparison has to be used. + bool val = false; + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if ((lhs.has_integer_value() || lhs.has_double_value()) && + (rhs.has_integer_value() || rhs.has_double_value())) { + + if (lhs.has_integer_value() && rhs.has_integer_value()) { + if (lhs.get_integer_value() != rhs.get_integer_value()) { + val = true; + } else { + val = false; + } + } else { + if (lhs.get_double_value() != rhs.get_double_value()) { + val = true; + } else { + val = false; + } + + } + } else { + // At least one of the two sides is non-numerical. Do a string comparison. + if (lhs.get_value() != rhs.get_value()) { + val = true; + } else { + val = false; + } + } + result = val; + break; + } + case CdlExprOp_BitAnd : + { + // x & y. Only integer data is supported. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the bitwise and operator on non integral data: `" + + lhs.get_value() + "' & `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() & rhs.get_integer_value(); + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_BitXor : + { + // x ^ y. Only integer data is supported. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the bitwise xor operator on non integral data: `" + + lhs.get_value() + "' ^ `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() ^ rhs.get_integer_value(); + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_BitOr : + { + // x | y. Only integer data is supported. + CdlSimpleValue lhs; + CdlSimpleValue rhs; + evaluate_subexpr(context, expr, subexpr.lhs_index, lhs); + evaluate_subexpr(context, expr, subexpr.rhs_index, rhs); + if (!(lhs.has_integer_value() && rhs.has_integer_value())) { + throw CdlEvalException("Attempt to use the bitwise or operator on non integral data: `" + + lhs.get_value() + "' | `" + rhs.get_value() + "'."); + } + result = lhs.get_integer_value() | rhs.get_integer_value(); + result.set_value_format(lhs, rhs); + break; + } + case CdlExprOp_And : + { + // x && y. Both sides should be interpreted as boolean values, + // and "y" should only be evaluated if necessary. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (!result.get_bool_value()) { + result = false; + } else { + evaluate_subexpr(context, expr, subexpr.rhs_index, result); + if (result.get_bool_value()) { + result = true; + } else { + result = false; + } + } + break; + } + case CdlExprOp_Or : + { + // x || y. Both sides should be interpreted as boolean values, + // and "y" should only be evaluated if necessary. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (result.get_bool_value()) { + result = true; + } else { + evaluate_subexpr(context, expr, subexpr.rhs_index, result); + if (result.get_bool_value()) { + result = true; + } else { + result = false; + } + } + break; + } + case CdlExprOp_Cond : + { + // x ? a : b. + // First evaluate the condition. Then evaluate either the second + // or third argument, as appropriate. + evaluate_subexpr(context, expr, subexpr.lhs_index, result); + if (result.get_bool_value()) { + evaluate_subexpr(context, expr, subexpr.rhs_index, result); + } else { + evaluate_subexpr(context, expr, subexpr.rrhs_index, result); + } + break; + } + + default: + break; + } + + CYG_REPORT_RETURN(); +} + +// ---------------------------------------------------------------------------- +void +CdlExpressionBody::eval_internal(CdlEvalContext& context, CdlSimpleValue& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlExpression::eval_internal)"); + CYG_REPORT_FUNCARG3XV(this, &context, &result); + CYG_INVARIANT_THISC(CdlExpressionBody); + CYG_PRECONDITION_CLASSOC(context); + + evaluate_subexpr(context, this, first_subexpression, result); + + CYG_REPORT_RETURN(); +} + +//}}} + +//{{{ CdlExpression + +//{{{ Construction + +// ---------------------------------------------------------------------------- +// Ordinary expressions. +// +// The default constructor is private and does very little. Expressions +// are created primarily by means of the parse() member function. There +// is an argument for having constructors that take the same arguments +// as the parse() member functions and relying on exception handling, +// but that gets tricky for goal expressions and continue_parse(). +// +// The copy constructor is protected and is used when creating e.g. +// a default_value property object, which inherits from the ordinary +// expression class. Again it might be better to do the parsing in +// the constructor itself. +// +// The assignment operator is private and illegal. + +CdlExpressionBody::CdlExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlExpression:: default constructor"); + CYG_REPORT_FUNCARG1XV(this); + + expression_string = ""; + first_subexpression = -1; + + cdlexpressionbody_cookie = CdlExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +CdlExpressionBody::CdlExpressionBody(const CdlExpressionBody& original) +{ + CYG_REPORT_FUNCNAME("CdlExpression:: copy constructor"); + CYG_REPORT_FUNCARG2XV(this, &original); + CYG_INVARIANT_CLASSOC(CdlExpressionBody, original); + + // Sub-expressions are simple structs, so this should result in a bit-wise + // copy of each vector element + sub_expressions = original.sub_expressions; + + // Simple scalar + first_subexpression = original.first_subexpression; + + // The CdlReference class has a valid copy constructor and assignment + // operator, provided that the reference is not yet bound. This should + // be true when this copy constructor gets invoked, after parsing + // and during the construction of a derived property object. + references = original.references; + expression_string = original.expression_string; + + cdlexpressionbody_cookie = CdlExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ check_this() + +// ---------------------------------------------------------------------------- +// check_this(). Expression objects can exist before any parsing has +// happened, not to mention in the middle of parsing. The +// first_subexpression field can be used to detect this. + +bool +CdlExpressionBody::check_this(cyg_assert_class_zeal zeal) const +{ + if (CdlExpressionBody_Magic != cdlexpressionbody_cookie) { + return false; + } + CYGDBG_MEMLEAK_CHECKTHIS(); + + if (-1 == first_subexpression) { + return true; + } + + switch(zeal) { + case cyg_system_test : + case cyg_extreme : + case cyg_thorough : + { + for (std::vector<CdlReference>::const_iterator i = references.begin(); i != references.end(); i++) { + if (!i->check_this(cyg_quick)) { + return false; + } + } + } + case cyg_quick : + if ((unsigned)first_subexpression >= sub_expressions.size()) { + return false; + } + case cyg_trivial : + case cyg_none : + break; + } + + return true; +} + +//}}} +//{{{ Destruction + +CdlExpressionBody::~CdlExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlExpression::destructor"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + cdlexpressionbody_cookie = CdlExpressionBody_Invalid; + first_subexpression = -1; + sub_expressions.clear(); + expression_string = ""; + + // This assumes that all references have been unbound already by + // higher-level destructors. + references.clear(); + + CYGDBG_MEMLEAK_DESTRUCTOR(); + + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ Parsing - exported interface + +// ---------------------------------------------------------------------------- +// parse(string) invokes parse(string, ...) and checks that the expression +// has terminated with EOD. Parsing of list expressions etc. can terminate +// with some other token. +// +// parse(string, ...) allocates the expression object and invokes +// continue_parse(). +// +// continue_parse() is supposed to do all the hard work. + +CdlExpression +CdlExpressionBody::parse(std::string data) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlExpression::parse", "result %p"); + + CdlExpression result = 0; + int index = 0; + CdlExprOp next_op = CdlExprOp_Invalid; + int end_index; + + result = parse(data, index, next_op, end_index); + + // Either there has already been a parsing or out-of-memory + // exception, or we should be at the end of the expression string. + if (CdlExprOp_EOD != next_op) { + delete result; + throw CdlParseException("unexpected data at end of expression.\n" + get_error_location()); + } + + // Keep a copy of the original string for diagnostics purposes. + result->expression_string = data; + + CYG_REPORT_RETVAL(result); + return result; +} + +CdlExpression +CdlExpressionBody::parse(std::string data, int& index, CdlExprOp& next_token, int& token_end) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlExpression::parse", "result %d"); + + CdlExpression result = new CdlExpressionBody; + + try { + continue_parse(result, data, index, next_token, token_end); + } + catch (...) { + delete result; + throw; + } + + CYG_REPORT_RETVAL(result); + return result; +} + +//}}} +//{{{ update() + +// ---------------------------------------------------------------------------- +// There has been a change in the toplevel which involves entities being +// created or destroyed, and reference resolution is required. + +bool +CdlExpressionBody::update(CdlTransaction transaction, CdlNode source, CdlProperty source_prop, CdlNode dest, CdlUpdate change) +{ + CYG_REPORT_FUNCNAMETYPE("CdlExpression::update", "result %d"); + CYG_REPORT_FUNCARG6XV(this, transaction, source, source_prop, dest, change); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSC(source); + CYG_PRECONDITION_CLASSC(source_prop); + + CdlToplevel toplevel = source->get_toplevel(); + bool result = false; + std::vector<CdlReference>::iterator ref_i; + + switch(change) { + case CdlUpdate_Loaded: + { + // The source package has just been loaded. Try to resolve every + // reference, creating CdlConflict objects where necessary. + CYG_ASSERTC(0 == dest); + for (ref_i = references.begin(); ref_i != references.end(); ref_i++) { + dest = toplevel->lookup(ref_i->get_destination_name()); + if (0 == dest) { + CdlConflict_UnresolvedBody::make(transaction, source, source_prop, ref_i->get_destination_name()); + } else { + ref_i->bind(source, source_prop, dest); + } + } + result = true; + break; + } + + case CdlUpdate_Unloading: + { + // The source package is being unloaded. Unbind all currently bound references. + // Also destroy any unresolved conflicts. + CYG_ASSERTC(0 == dest); + for (ref_i = references.begin(); ref_i != references.end(); ref_i++) { + dest = ref_i->get_destination(); + if (0 != dest) { + ref_i->unbind(source, source_prop); + } + } + result = true; + break; + } + + case CdlUpdate_Created : + { + + // A previously unresolved reference can now be resolved. + // It is necessary to search the vector for an unresolved + // reference with the desired name, and do the binding. + // This search may fail in the case of list expressions. + CYG_ASSERT_CLASSC(dest); + std::string dest_name = dest->get_name(); + for (ref_i = references.begin(); !result && (ref_i != references.end()); ref_i++) { + if ((dest_name == ref_i->get_destination_name()) && (0 == ref_i->get_destination())) { + ref_i->bind(source, source_prop, dest); + result = true; + + std::vector<CdlConflict> conflicts; + std::vector<CdlConflict>::iterator conf_i; + transaction->get_structural_conflicts(source, source_prop, &CdlConflict_UnresolvedBody::test, conflicts); + for (conf_i = conflicts.begin(); conf_i != conflicts.end(); conf_i++) { + CdlConflict_Unresolved real_conf = dynamic_cast<CdlConflict_Unresolved>(*conf_i); + CYG_ASSERTC(0 != real_conf); + if (dest_name == real_conf->get_target_name()) { + transaction->clear_conflict(real_conf); + break; + } + } + CYG_ASSERTC(conf_i != conflicts.end()); + } + } + break; + } + + case CdlUpdate_Destroyed : + { + // A previously resolved reference is about to become illegal. + // Search the vector for a resolved reference object matching + // the destination, and unbind it. Also create a conflict + // object. The search can fail in the case of list expressions + CYG_ASSERT_CLASSC(dest); + for (ref_i = references.begin(); !result && (ref_i != references.end()); ref_i++) { + if (dest == ref_i->get_destination()) { + ref_i->unbind(source, source_prop); + CdlConflict_UnresolvedBody::make(transaction, source, source_prop, ref_i->get_destination_name()); + result = true; + } + } + break; + } + + default : + CYG_FAIL("Illegal change type passed to CdlExpression::update"); + break; + } + + CYG_REPORT_RETVAL(result); + return result; +} + +//}}} +//{{{ Evaluation + +// ---------------------------------------------------------------------------- +// Expression evaluation. At the end of the day everything filters through +// to eval_internal() which should all the hard work. +// +// The eval() member function handles EvalException conflicts. The +// eval_internal() member function does not, and is used for list +// and goal expressions as well. + +void +CdlExpressionBody::eval(CdlEvalContext& context, CdlSimpleValue& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlExpression::eval"); + + try { + + eval_internal(context, result); + + // Evaluation has succeeded, so if there was an EvalException + // conflict get rid of it. This can only happen in the context + // of a transaction. + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + context.transaction->clear_conflicts(context.node, context.property, &CdlConflict_EvalExceptionBody::test); + } + + } catch(CdlEvalException e) { + + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + + CdlConflict conflict = context.transaction->get_conflict(context.node, context.property, + &CdlConflict_EvalExceptionBody::test); + if (0 == conflict) { + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } else { + + CdlConflict_EvalException eval_conf = dynamic_cast<CdlConflict_EvalException>(conflict); + CYG_ASSERTC(0 != eval_conf); + if (eval_conf->get_explanation() != e.get_message()) { + + // Replace the conflict object. That way higher level code gets informed + // there has been a change. + context.transaction->clear_conflict(conflict); + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } + } + } + + throw; + } +} + +//}}} +//{{{ Misc + +// ---------------------------------------------------------------------------- + +std::string +CdlExpressionBody::get_original_string() const +{ + CYG_REPORT_FUNCNAME("CdlExpression::get_original_string"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + CYG_REPORT_RETURN(); + return expression_string; +} + +//}}} + +//}}} +//{{{ CdlListExpression + +//{{{ Construction + +// ---------------------------------------------------------------------------- +// The normal sequence of events is: +// +// 1) higher level code calls CdlListExpressionbody::parse() +// 2) this static member creates a new and empty list expression object. +// The constructor need not do very much. +// 3) the parse() member then fills in the newly created object +// 4) the object is returned to higher-level code +// 5) usually the list expression will now become part of +// a property object by means of a copy constructor. +// +// The only complication is that a list expression contains a vector +// of CdlExpression pointers which must be freed during the destructor. +// The copy constructor does not make duplicates of the individual +// expression objects, instead ownership is transferred. + +CdlListExpressionBody::CdlListExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlListExpression:: default constructor"); + CYG_REPORT_FUNCARG1XV(this); + + expression_string = ""; + + cdllistexpressionbody_cookie = CdlListExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +CdlListExpressionBody::CdlListExpressionBody(const CdlListExpressionBody& original) +{ + CYG_REPORT_FUNCNAME("CdlListExpression:: copy constructor"); + CYG_REPORT_FUNCARG2XV(this, &original); + CYG_INVARIANT_CLASSOC(CdlListExpressionBody, original); + + expression_string = original.expression_string; + + // These copy across the pointers + data = original.data; + ranges = original.ranges; + + // And this clears out the pointers, but leaves the expression objects lying around + CdlListExpression tmp = const_cast<CdlListExpression>(&original); + tmp->data.clear(); + tmp->ranges.clear(); + + cdllistexpressionbody_cookie = CdlListExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ Destruction + +CdlListExpressionBody::~CdlListExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlListExpression:: destructor"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + cdllistexpressionbody_cookie = CdlListExpressionBody_Invalid; + expression_string = ""; + + for (std::vector<CdlExpression>::iterator i = data.begin(); i != data.end(); i++) { + delete *i; + *i = 0; + } + for (std::vector<std::pair<CdlExpression, CdlExpression> >::iterator j = ranges.begin(); j != ranges.end(); j++) { + delete j->first; + delete j->second; + j->first = 0; + j->second = 0; + } + data.clear(); + ranges.clear(); + CYGDBG_MEMLEAK_DESTRUCTOR(); + + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ check_this() + +// ---------------------------------------------------------------------------- +bool +CdlListExpressionBody::check_this(cyg_assert_class_zeal zeal) const +{ + if (CdlListExpressionBody_Magic != cdllistexpressionbody_cookie) { + return false; + } + CYGDBG_MEMLEAK_CHECKTHIS(); + switch(zeal) { + case cyg_system_test : + case cyg_extreme : + case cyg_thorough : + { + for (std::vector<CdlExpression>::const_iterator i = data.begin(); i != data.end(); i++) { + if (!(*i)->check_this(cyg_quick)) { + return false; + } + } + for (std::vector<std::pair<CdlExpression,CdlExpression> >::const_iterator j = ranges.begin(); + j != ranges.end(); + j++) { + if (!(j->first->check_this(cyg_quick)) || !(j->second->check_this(cyg_quick))) { + return false; + } + } + } + case cyg_quick : + case cyg_trivial : + case cyg_none : + default : + break; + } + + return true; +} + +//}}} +//{{{ Parsing + +// ---------------------------------------------------------------------------- +// Parsing a list expression involves repeated parsing of ordinary +// expressions until an EOD token is reached. + +CdlListExpression +CdlListExpressionBody::parse(std::string data) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::parse", "result %p"); + + // Allocate an expression object that can then be filled in. + CdlListExpression result = new CdlListExpressionBody; + + // Do the parsing in a try/catch statement to make sure the + // allocated expression gets freed on a parse error. + try { + int index = 0; + int end_index = 0; + CdlExprOp op = CdlExprOp_Invalid; + CdlExpression expr1 = 0; + + do { + // Try to parse the next expression in the list + op = CdlExprOp_Invalid; + expr1 = CdlExpressionBody::parse(data, index, op, end_index); + + // There should now be a valid expression, failure would have + // resulted in an exception. + CYG_ASSERT_CLASSC(expr1); + + // Allow for ranges. + if (CdlExprOp_Range != op) { + // A simple expression, just add it to the current data vector + // "index" will contain the appropriate value. + result->data.push_back(expr1); + } else { + // A range expression. Get the other end of the range. + // This requires manipulating index a bit. + CdlExpression expr2 = 0; + index = end_index; + op = CdlExprOp_Invalid; + try { + expr2 = CdlExpressionBody::parse(data, index, op, end_index); + } + catch (...) { + delete expr1; + throw; + } + result->ranges.push_back(std::make_pair(expr1, expr2)); + } + } while (CdlExprOp_EOD != op); + } + catch (...) { + delete result; + throw; + } + + // Keep track of the original string for diagnostics purposes + result->expression_string = data; + + CYG_REPORT_RETVAL(result); + return result; +} + +//}}} +//{{{ update() + +// ---------------------------------------------------------------------------- +// This code is invoked when it is necessary to update the references +// for the list expression. There are four situations in which this +// can happen: the package has just been loaded; the package is being +// unloaded; a referenced target is being created; a referenced target is +// being destroyed. +// +// The first two cases simply involve processing every expression that +// makes up the overall list expression. The last two cases involve +// searching through the expressions until an applicable one is found. +// Note that an expression may contain multiple references to another +// object, resulting in multiple calls to this function. + +bool +CdlListExpressionBody::update(CdlTransaction transact, CdlNode source, CdlProperty source_prop, CdlNode dest, CdlUpdate change) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::update", "result %d"); + CYG_REPORT_FUNCARG6XV(this, transact, source, source_prop, dest, change); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSC(source); + CYG_PRECONDITION_CLASSC(source_prop); + + bool result = false; + + if ((CdlUpdate_Loaded == change) || (CdlUpdate_Unloading == change)) { + + std::vector<CdlExpression>::const_iterator expr_i; + std::vector<std::pair<CdlExpression, CdlExpression> >::const_iterator pair_i; + + for (expr_i = data.begin(); expr_i != data.end(); expr_i++) { + bool handled = (*expr_i)->update(transact, source, source_prop, dest, change); + CYG_ASSERTC(handled); + CYG_UNUSED_PARAM(bool, handled); + } + for (pair_i = ranges.begin(); pair_i != ranges.end(); pair_i++) { + bool handled = pair_i->first->update(transact, source, source_prop, dest, change); + CYG_ASSERTC(handled); + handled = pair_i->second->update(transact, source, source_prop, dest, change); + CYG_ASSERTC(handled); + } + + result = true; + + } else { + CYG_ASSERTC((CdlUpdate_Created == change) || (CdlUpdate_Destroyed == change)); + + std::vector<CdlExpression>::const_iterator expr_i; + std::vector<std::pair<CdlExpression, CdlExpression> >::const_iterator pair_i; + + for (expr_i = data.begin(); !result && (expr_i != data.end()); expr_i++) { + result = (*expr_i)->update(transact, source, source_prop, dest, change); + } + for (pair_i = ranges.begin(); !result && (pair_i != ranges.end()); pair_i++) { + result = pair_i->first->update(transact, source, source_prop, dest, change); + if (!result) { + result = pair_i->second->update(transact, source, source_prop, dest, change); + } + } + } + + CYG_REPORT_RETVAL(result); + return result; +} + +//}}} +//{{{ Evaluation + +// ---------------------------------------------------------------------------- +// Evaluation. The hard work is actually done in eval_internal() + +void +CdlListExpressionBody::eval(CdlEvalContext& context, CdlListValue& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlListExpression::eval"); + CYG_REPORT_FUNCARG3XV(this, &context, &result); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + this->eval_internal(context, result); + + CYG_REPORT_RETURN(); +} + +// ---------------------------------------------------------------------------- +// This requires evaluating each expression in the data and ranges +// vectors and adding the result to the appropriate vector in result. +// Various error conditions are possible. + +void +CdlListExpressionBody::eval_internal(CdlEvalContext& context, CdlListValue& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlListExpression::eval_internal"); + CYG_REPORT_FUNCARG2XV(this, &context); + + result.table.clear(); + result.integer_ranges.clear(); + result.double_ranges.clear(); + + CdlSimpleValue val1; + CdlSimpleValue val2; + + try { + for (std::vector<CdlExpression>::const_iterator i = data.begin(); i != data.end(); i++) { + (*i)->eval_internal(context, val1); + if ("" != val1.get_value()) { + result.table.push_back(val1); + } + } + for (std::vector<std::pair<CdlExpression,CdlExpression> >::const_iterator j = ranges.begin(); j != ranges.end(); j++) { + j->first->eval_internal(context, val1); + j->second->eval_internal(context, val2); + + if (val1.has_integer_value() && val2.has_integer_value()) { + cdl_int x1 = val1.get_integer_value(); + cdl_int x2 = val2.get_integer_value(); + if (x1 > x2) { + cdl_int tmp = x1; + x1 = x2; + x2 = tmp; + } + result.integer_ranges.push_back(std::make_pair(x1, x2)); + } else if (val1.has_double_value() && val2.has_double_value()) { + double x1 = val1.get_double_value(); + double x2 = val2.get_double_value(); + if (x1 > x2) { + double tmp = x1; + x1 = x2; + x2 = tmp; + } + result.double_ranges.push_back(std::make_pair(x1, x2)); + } else { + throw CdlEvalException("range expression involves non-numerical limits"); + } + } + + // Any problems would have resulted in an exception. If there + // was a previous EvalExeption for this property, it is no + // longer applicable + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + context.transaction->clear_conflicts(context.node, context.property, &CdlConflict_EvalExceptionBody::test); + } + + } catch(CdlEvalException e) { + + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + + CdlConflict conflict = context.transaction->get_conflict(context.node, context.property, + &CdlConflict_EvalExceptionBody::test); + if (0 == conflict) { + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } else { + CdlConflict_EvalException eval_conf = dynamic_cast<CdlConflict_EvalException>(conflict); + CYG_ASSERTC(0 != eval_conf); + if (eval_conf->get_explanation() != e.get_message()) { + + // Replace the conflict object. Higher level will be informed about this. + context.transaction->clear_conflict(conflict); + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } + } + } + + throw; + } + + CYG_REPORT_RETURN(); +} + +//}}} +//{{{ is_member() + +// ---------------------------------------------------------------------------- + +bool +CdlListExpressionBody::is_member(CdlEvalContext& context, CdlSimpleValue& val) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::is_member (value)", "result %d"); + CYG_REPORT_FUNCARG3XV(this, &context, &val); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + bool result = false; + CdlListValue list_val; + eval_internal(context, list_val); + result = list_val.is_member(val); + + CYG_REPORT_RETVAL(result); + return result; +} + +bool +CdlListExpressionBody::is_member(CdlEvalContext& context, std::string val) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::is_member (string)", "result %d"); + CYG_REPORT_FUNCARG2XV(this, &context); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + bool result = false; + CdlListValue list_val; + eval_internal(context, list_val); + result = list_val.is_member(val); + + CYG_REPORT_RETVAL(result); + return result; +} + +bool +CdlListExpressionBody::is_member(CdlEvalContext& context, cdl_int val) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::is_member (int)", "result %d"); + CYG_REPORT_FUNCARG3XV(this, &context, (int) val); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + bool result = false; + CdlListValue list_val; + eval_internal(context, list_val); + result = list_val.is_member(val); + + CYG_REPORT_RETVAL(result); + return result; +} + +bool +CdlListExpressionBody::is_member(CdlEvalContext& context, double val) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlListExpression::is_member (double)", "result %d"); + CYG_REPORT_FUNCARG2XV(this, &context); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + bool result = false; + CdlListValue list_val; + eval_internal(context, list_val); + result = list_val.is_member(val); + + CYG_REPORT_RETVAL(result); + return result; +} + +//}}} +//{{{ Misc + +// ---------------------------------------------------------------------------- + +std::string +CdlListExpressionBody::get_original_string() const +{ + CYG_REPORT_FUNCNAME("CdlListExpression::get_original_string"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + CYG_REPORT_RETURN(); + return expression_string; +} + +//}}} + +//}}} +//{{{ CdlGoalExpression + +// ---------------------------------------------------------------------------- +// Constructors etc. are pretty much as per ordinary and list +// expressions. Most of the work is done in the private base class. + +CdlGoalExpressionBody::CdlGoalExpressionBody() + : CdlExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression::default_constructor"); + CYG_REPORT_FUNCARG1XV(this); + + expression_string = ""; + cdlgoalexpressionbody_cookie = CdlGoalExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +CdlGoalExpressionBody::CdlGoalExpressionBody(const CdlGoalExpressionBody& original) + : CdlExpressionBody(original) +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression:: copy constructor"); + CYG_REPORT_FUNCARG2XV(this, &original); + CYG_INVARIANT_CLASSOC(CdlGoalExpressionBody, original); + + expression_string = original.expression_string; + cdlgoalexpressionbody_cookie = CdlGoalExpressionBody_Magic; + CYGDBG_MEMLEAK_CONSTRUCTOR(); + + CYG_POSTCONDITION_THISC(); + CYG_REPORT_RETURN(); +} + +CdlGoalExpressionBody::~CdlGoalExpressionBody() +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression:: destructor"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + cdlgoalexpressionbody_cookie = CdlGoalExpressionBody_Invalid; + expression_string = ""; + CYGDBG_MEMLEAK_DESTRUCTOR(); + + CYG_REPORT_RETURN(); +} + +// ---------------------------------------------------------------------------- +// Parsing. A goal expression acts a bit like a list expression with +// implicit && operators between the various expressions. It could be +// implemented as a vector of expressions (which might make diagnostics +// easier) but it is almost as easy to derive a goal expression from +// an ordinary one. + +CdlGoalExpression +CdlGoalExpressionBody::parse(std::string data) + throw(CdlParseException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlGoalExpression::parse", "result %p"); + + CdlGoalExpression result = new CdlGoalExpressionBody; + + try { + int index = 0; + CdlExprOp op = CdlExprOp_Invalid; + int end_index = 0; + + // Parse the first expression in the data. + CdlExpressionBody::continue_parse(result, data, index, op, end_index); + + // At this stage we have reached end-of-data or we should be + // at the start of another expression - any binary or ternary + // operands would have been subsumed in the previous expression. + // We need to keep adding && operators and new expressions until + // end-of-data. + while (CdlExprOp_EOD != op) { + op = CdlExprOp_And; + CdlExpressionBody::continue_parse(result, data, index, op, end_index); + } + } + catch(...) { + delete result; + throw; + } + + // Keep track of the original expression string for diagnostics purposes + result->expression_string = data; + CYG_REPORT_RETVAL(result); + return result; +} + +// ---------------------------------------------------------------------------- +void +CdlGoalExpressionBody::eval(CdlEvalContext& context, bool& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression::eval"); + CYG_REPORT_FUNCARG2XV(this, &context); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + eval_internal(context, result); + + CYG_REPORT_RETURN(); +} + +bool +CdlGoalExpressionBody::eval(CdlEvalContext& context) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAMETYPE("CdlGoalExpression::eval", "result %d"); + CYG_REPORT_FUNCARG2XV(this, &context); + CYG_PRECONDITION_THISC(); + CYG_PRECONDITION_CLASSOC(context); + + bool result; + eval_internal(context, result); + + CYG_REPORT_RETVAL(result); + return result; +} + +// ---------------------------------------------------------------------------- +// Provide access to the underlying CdlExpression object. This allows the +// inference engine etc. to work out why a goal expression is failing + +CdlExpression +CdlGoalExpressionBody::get_expression() +{ + CYG_REPORT_FUNCNAMETYPE("CdlGoalExpression::get_expression", "result %p"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + CdlExpression result = this; + CYG_REPORT_RETVAL(result); + return result; +} + +// ---------------------------------------------------------------------------- + +bool +CdlGoalExpressionBody::check_this(cyg_assert_class_zeal zeal) const +{ + if (CdlGoalExpressionBody_Magic != cdlgoalexpressionbody_cookie) { + return false; + } + CYGDBG_MEMLEAK_CHECKTHIS(); + + // There is no data specific to a goal expression, just let the + // underlying check_this() member do its stuff. + + return inherited::check_this(zeal); +} + +// ---------------------------------------------------------------------------- + +std::string +CdlGoalExpressionBody::get_original_string() const +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression::get_original_string"); + CYG_REPORT_FUNCARG1XV(this); + CYG_PRECONDITION_THISC(); + + CYG_REPORT_RETURN(); + return expression_string; +} + +// ---------------------------------------------------------------------------- + +void +CdlGoalExpressionBody::eval_internal(CdlEvalContext& context, bool& result) + throw(CdlEvalException, std::bad_alloc) +{ + CYG_REPORT_FUNCNAME("CdlGoalExpression::eval_internal"); + CYG_REPORT_FUNCARG2XV(this, &context); + // The assertions are all done in the calling code + + // Start by evaluating the underlying expression + CdlSimpleValue val; + try { + inherited::eval_internal(context, val); + + // The evaluation succeeded. Do we have an integer, a string, ...? + if (val.has_integer_value()) { + result = (0 != val.get_integer_value()); + } else if (val.has_double_value()) { + result = (0.0 != val.get_double_value()); + } else { + result = ("" != val.get_value()); + } + + // If there is an EvalException conflict for this property, it is no longer applicable + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + context.transaction->clear_conflicts(context.node, context.property, + &CdlConflict_EvalExceptionBody::test); + } + + } catch(CdlEvalException e) { + if ((0 != context.transaction) && (0 != context.node) && (0 != context.property)) { + CdlConflict conflict = context.transaction->get_conflict(context.node, context.property, + &CdlConflict_EvalExceptionBody::test); + if (0 == conflict) { + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } else { + CdlConflict_EvalException eval_conf = dynamic_cast<CdlConflict_EvalException>(conflict); + CYG_ASSERTC(0 != eval_conf); + if (eval_conf->get_explanation() != e.get_message()) { + // Replace the conflict object. Higher level can detect this. + context.transaction->clear_conflict(conflict); + CdlConflict_EvalExceptionBody::make(context.transaction, context.node, context.property, e.get_message()); + } + } + throw; + } + } + + CYG_REPORT_RETURN(); +} + +//}}}
