📄 parse.cxx
字号:
// present, the Tcl interpreter does not keep track of sufficient// information. At least, not in the public data structures, there is// a termOffset field in the internal data structures which might// be used to do the right thing. I do not want to start relying// on Tcl internals just yet, or add support to the Tcl core for// keeping track of line numbers.//// For many data files there will the concept of a current node,// e.g. an option whose properties or savefile information are// being processed. The CdlInterpreter class keeps track of the// current node, so if it is defined then the node's class and// name can be part of the message. This happens automatically,// no effort is required on the part of calling code.//// There may also be additional information, for example// identifying the specific property where the error was detected.// This is handled by an extra argument.//// The classification is likely to be something like "warning",// "error", or "internal error". It is controlled by the calling// code, but typically it is provided by calling via report_warning()// etc.//// The message should identify the actual error. It should be// a proper sentence, i.e. begin with a capital error and end with// a full stop, unless the last word is an identifier or filename// or something similarly special in which case the trailing// dot will be discarded. The message should not end with a// newline character, and the result string will not end with one// either. That is left to higher level code.std::stringCdlParse::construct_diagnostic(CdlInterpreter interp, std::string classification, std::string sub_id, std::string message){ CYG_REPORT_FUNCNAME("CdlParse::construct_diagnostic"); CYG_PRECONDITION_CLASSC(interp); std::string context = interp->get_context(); CdlNode current_node = interp->get_node(); std::string result; if ("" == context) { result = "<unknown context>"; } else { result = context; } if (0 != current_node) { result += ", " + current_node->get_class_name() + " " + current_node->get_name(); } if ("" != sub_id) { result += ", " + sub_id; } result += ": " + classification; // Now it is time to start worrying about layout, indenting // subsequent lines, and so on. int index = result.length(); int message_len = message.length(); int message_index; bool indent_needed = false; // Find out how many characters there are in the message up to the first newline for (message_index = 0; (message_index < message_len) && ('\n' != message[message_index]); message_index++) { ; } // Should the message start on the next line, suitably indented? // This depends in part on whether or not there was a classification. if ("" == classification) { // The current result ends with a colon and a space. if ((index + message_index) <= 72) { // The first line of the message can still fit. No need to do anything. } else { // Start indenting immediately, do not add anything else to the current line. indent_needed = true; } } else { // We may want a comma and a space after the classification if ((index + 2 + message_index) <= 72) { result += ", "; } else { indent_needed = true; } } // Now we can process the message one character at a time, adding // newlines and indentation just in time. for (message_index = 0; message_index < message_len; message_index++) { if (indent_needed) { result += "\n "; indent_needed = false; } if ('\n' == message[message_index]) { indent_needed = true; } else { result += message[message_index]; } } CYG_REPORT_RETURN(); return result;}//}}}//{{{ Error count tracking // Keep track of the number of errors that have occurred while doing some// parsing. This functionality is not provided directly by the CdlInterpreter// class, instead it is implemented using assoc data.static const char error_count_key[] = "CdlErrorCount";static voiderror_count_delproc(ClientData data, Tcl_Interp* interp){ CYG_REPORT_FUNCNAME("CdlParse::error_count_delproc"); int* newed_ptr = static_cast<int*>(data); delete newed_ptr; CYG_REPORT_RETURN();}voidCdlParse::clear_error_count(CdlInterpreter interp){ CYG_REPORT_FUNCNAME("CdlParse::clear_error_count"); CYG_REPORT_FUNCARG1("interp %p", interp); CYG_PRECONDITION_CLASSC(interp); int* newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key)); if (0 != newed_ptr) { *newed_ptr = 0; } CYG_REPORT_RETURN();}voidCdlParse::incr_error_count(CdlInterpreter interp, int how_much){ CYG_REPORT_FUNCNAME("CdlParse::incr_error_counter"); CYG_REPORT_FUNCARG2("interp %p, how_much %d", interp, how_much); CYG_PRECONDITION_CLASSC(interp); CYG_PRECONDITION(how_much > 0, "previous errors cannot be undone"); int* newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key)); if (0 == newed_ptr) { newed_ptr = new int(how_much); interp->set_assoc_data(error_count_key, static_cast<void*>(newed_ptr), &error_count_delproc); } else { CYG_ASSERT((*newed_ptr + how_much) > *newed_ptr, "number of parsing errors should not overflow"); *newed_ptr += how_much; } CYG_REPORT_RETURN();}intCdlParse::get_error_count(CdlInterpreter interp){ CYG_REPORT_FUNCNAMETYPE("CdlParse::get_error_count", "count %d"); CYG_REPORT_FUNCARG1("interp %p", interp); CYG_PRECONDITION_CLASSC(interp); int result = 0; int* newed_ptr = static_cast<int*>(interp->get_assoc_data(error_count_key)); if (0 != newed_ptr) { result = *newed_ptr; } CYG_REPORT_RETVAL(result); return result;}//}}}//{{{ Error and warning reporting // Report errors and warnings. These will be called during parsing// operations, both of CDL and similar data scripts and for savefiles.// The parsing involves running a Tcl interpreter extended with the// appropriate set of commands. Typically the call graph will look// something like this://// libcdl C++ code such as load_package()// libcdl CdlInterpreter::eval()// Tcl interpreter// libcdl parsing code// report_error()// // If the Tcl script is invalid then parsing errors may get reported// at the higher level code as well.//// There are two classes of diagnostic: errors and warnings.// Additional levels may be added in future, but there does not seem// to be an urgent need for them. Client code should provide callback// functions so that the messages can be displayed to the user, and// these callbacks will be registered with the current CdlInterpreter.//// If no error callback is defined then a ParseException will be// raised instead, and the rest of the current script will not be// processed. Alternatively the error callback itself can raise a// ParseException. Care is taken to ensure that the exception does not// go straight through the Tcl interpreter, since that would prevent// the Tcl code from cleaning up appropriately. If no exception is// raised then the library keeps track of the number of errors, and// this information is accessible once the script has been fully// processed. This allows multiple errors to be reported in a single// run.//// If no warning callback is provided then warnings are ignored.voidCdlParse::report_error(CdlInterpreter interp, std::string sub_id, std::string message){ CYG_REPORT_FUNCNAME("CdlParse::report_error"); CYG_REPORT_FUNCARG1("interp %p", interp); CYG_PRECONDITION_CLASSC(interp); incr_error_count(interp); std::string full_message = construct_diagnostic(interp, "error", sub_id, message); // Now, either invoke the callback if it is provided, or throw the exception. CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr(); if (0 == fn) { throw CdlParseException(full_message); } else { (*fn)(full_message); } CYG_REPORT_RETURN();}voidCdlParse::report_warning(CdlInterpreter interp, std::string sub_id, std::string message){ CYG_REPORT_FUNCNAME("CdlParse::report_warning"); CYG_REPORT_FUNCARG1("interp %p", interp); CYG_PRECONDITION_CLASSC(interp); // If there is no warning callback, do nothing. This is really a // bug in the calling application. CdlDiagnosticFnPtr fn = interp->get_warning_fn_ptr(); if (0 != fn) { std::string full_message = construct_diagnostic(interp, "warning", sub_id, message); (*fn)(full_message); } CYG_REPORT_RETURN();}//}}}//{{{ The "unknown" command // ----------------------------------------------------------------------------// This routine should be installed in interpreters that get used for// parsing CDL scripts. It gets invoked when the CDL script contains// an unrecognised command, e.g. because of a typo, and makes sure that// the usual diagnostics process is observed.//// This routine should be uninstalled after the parsing is complete,// to avoid e.g. a ParseException when it is not expected.intCdlParse::unknown_command(CdlInterpreter interp, int argc, char** argv){ CYG_REPORT_FUNCNAME("CdlParse::unknown_command"); CYG_REPORT_FUNCARG3XV(interp, argc, argv); CYG_PRECONDITIONC(2 <= argc); CYG_PRECONDITION_CLASSC(interp); report_error(interp, "", std::string("Unknown command `") + argv[1] + "'."); CYG_UNUSED_PARAM(int, argc); return TCL_OK;}//}}}//}}}//{{{ Property-related parser utilities // ----------------------------------------------------------------------------// Utilities related to parsing properties, rather than more general parsing.// A variant of report_parse_error() which also adds the property prefix.voidCdlParse::report_property_parse_error(CdlInterpreter interp, std::string argv0, std::string msg){ CYG_REPORT_FUNCNAME("CdlPase::report_property_parse_error"); incr_error_count(interp); std::string diag = construct_diagnostic(interp, "error", std::string("property ") + CdlParse::get_tcl_cmd_name(argv0), msg); // Now, either invoke the callback if it is provided, or throw the exception. CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr(); if (0 == fn) { throw CdlParseException(diag); } else { (*fn)(diag); } CYG_REPORT_RETURN();}voidCdlParse::report_property_parse_error(CdlInterpreter interp, CdlProperty prop, std::string msg){ CYG_REPORT_FUNCNAME("CdlParse::report_property_parse_error"); report_property_parse_error(interp, (prop->get_argv())[0], msg); CYG_REPORT_RETURN();}// Repeat for warningsvoidCdlParse::report_property_parse_warning(CdlInterpreter interp, std::string argv0, std::string msg){ CYG_REPORT_FUNCNAME("CdlPase::report_property_parse_warning"); CdlDiagnosticFnPtr fn = interp->get_error_fn_ptr(); if (0 != fn) { std::string diag = construct_diagnostic(interp, "error", std::string("property ") + CdlParse::get_tcl_cmd_name(argv0), msg); (*fn)(diag); } CYG_REPORT_RETURN();}voidCdlParse::report_property_parse_warning(CdlInterpreter interp, CdlProperty prop, std::string msg){ CYG_REPORT_FUNCNAME("CdlParse::report_property_parse_warning"); report_property_parse_warning(interp, (prop->get_argv())[0], msg); CYG_REPORT_RETURN();}//}}}//{{{ Generic property parsers // ----------------------------------------------------------------------------// Generic parsers//// These routines provide some more generic property parsing routines. argv[0]// generally provides sufficient information to allow for sensible error messages.// The command-specific parsers have to provide a property name. In addition it is// possible to provide a function to handle per-command options, and another// function that performs a final sanity check before the property gets added// to the current entity.//{{{ parse_minimal_property() // ----------------------------------------------------------------------------// A minimal property takes no arguments.intCdlParse::parse_minimal_property(CdlInterpreter interp, int argc, char** argv, std::string name, char** options_desc, void (*final_parser)(CdlInterpreter, CdlProperty_Minimal)){ CYG_REPORT_FUNCNAME("parse_minimal_property"); CYG_PRECONDITION_CLASSC(interp); CdlProperty_Minimal new_property = 0; try { std::vector<std::pair<std::string,std::string> > options; int data_index = CdlParse::parse_options(interp, property_string + argv[0], options_desc, argc, argv, 1, options); if (data_index < argc) { CdlParse::report_property_parse_error(interp, argv[0], std::string("Unexpected data `") + argv[data_index] + "'.");
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -