From 8e3049d0eef7954cdc4030d027bf4646c3a39fd7 Mon Sep 17 00:00:00 2001 From: twanvl Date: Mon, 16 Apr 2007 23:45:01 +0000 Subject: [PATCH] improved error reporting for the keyword editor git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@260 0fc631ac-6414-0410-93d0-97cfa31319b6 --- data/en.mse-locale/locale | 5 +- src/data/action/keyword.cpp | 36 ++++++++--- src/data/action/keyword.hpp | 3 +- src/render/text/compound.cpp | 20 ++++++ src/render/text/element.cpp | 20 ++++-- src/render/text/element.hpp | 7 +++ src/script/parser.cpp | 115 ++++++++++++++++++++++++++--------- src/script/parser.hpp | 14 ++++- src/util/error.cpp | 28 +++++++++ src/util/error.hpp | 15 ++++- 10 files changed, 213 insertions(+), 50 deletions(-) diff --git a/data/en.mse-locale/locale b/data/en.mse-locale/locale index 2a38ed29..cd558502 100644 --- a/data/en.mse-locale/locale +++ b/data/en.mse-locale/locale @@ -60,7 +60,7 @@ menu: set info tab: &Set Information F6 style tab: St&yle F7 keywords tab: &Keywords F8 - stats tab: S&tatistics F8 + stats tab: S&tatistics F9 help: &Help index: &Index... F1 @@ -331,6 +331,9 @@ label: mode: Mode uses: Uses reminder: Reminder text + standard keyword: + This is a standard %s keyword, you can not edit it. + If you make a copy of the keyword your copy will take precedent. # Open dialogs all files All files diff --git a/src/data/action/keyword.cpp b/src/data/action/keyword.cpp index 39d81d41..c807e8c5 100644 --- a/src/data/action/keyword.cpp +++ b/src/data/action/keyword.cpp @@ -84,27 +84,30 @@ void KeywordReminderTextValue::store() { retrieve(); return; } - // Re-highlight + // new value String new_value = untag(value); - highlight(new_value); // Try to parse the script - try { - ScriptP new_script = parse(new_value, true); + vector parse_errors; + ScriptP new_script = parse(new_value, true, parse_errors); + if (parse_errors.empty()) { // parsed okay, assign errors.clear(); keyword.reminder.getScriptP() = new_script; keyword.reminder.getUnparsed() = new_value; - } catch (const Error& e) { + } else { // parse errors, report - errors = e.what(); // TODO + errors = ScriptParseErrors(parse_errors).what(); } + // re-highlight input, show errors + highlight(new_value, parse_errors); } void KeywordReminderTextValue::retrieve() { - highlight(*underlying); + vector no_errors; + highlight(*underlying, no_errors); } -void KeywordReminderTextValue::highlight(const String& code) { +void KeywordReminderTextValue::highlight(const String& code, const vector& errors) { // Add tags to indicate code / syntax highlight // i.e. bla {if code "x" } bla // becomes: @@ -112,7 +115,24 @@ void KeywordReminderTextValue::highlight(const String& code) { String new_value; int in_brace = 0; bool in_string = true; + vector::const_iterator error = errors.begin(); for (size_t pos = 0 ; pos < code.size() ; ) { + // error underlining + while (error != errors.end() && error->start == error->end) ++error; + if (error != errors.end()) { + if (error->start == pos) { + new_value += _(""); + } + if (error->end == pos) { + ++error; + if (error == errors.end() || error->start > pos) { + new_value += _(""); + } else { + // immediatly open again + } + } + } + // process a character Char c = code.GetChar(pos); if (c == _('<')) { new_value += _('\1'); // escape diff --git a/src/data/action/keyword.hpp b/src/data/action/keyword.hpp index 1d925159..96f3c368 100644 --- a/src/data/action/keyword.hpp +++ b/src/data/action/keyword.hpp @@ -16,6 +16,7 @@ #include #include +#include #include class Set; @@ -93,7 +94,7 @@ class KeywordReminderTextValue : public KeywordTextValue { virtual void retrieve(); /// Syntax highlight, and store in value - void highlight(const String& code); + void highlight(const String& code, const vector& errors); }; // ----------------------------------------------------------------------------- : EOF diff --git a/src/render/text/compound.cpp b/src/render/text/compound.cpp index 0d06011f..daffa750 100644 --- a/src/render/text/compound.cpp +++ b/src/render/text/compound.cpp @@ -33,3 +33,23 @@ void AtomTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, co } CompoundTextElement::draw(dc, scale, rect, xs, what, start, end); } + +// ----------------------------------------------------------------------------- : ErrorTextElement + +void ErrorTextElement::draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const { + // Draw wavy underline + dc.SetPen(*wxRED_PEN); + RealPoint pos = rect.bottomLeft() - dc.trInvS(RealSize(0,2)); + RealSize dx(dc.trInvS(2), 0), dy(0, dc.trInvS(1)); + while (pos.x + 1 < rect.right()) { + dc.DrawLine(pos - dy, pos + dx + dy); + pos += dx; + dy = -dy; + } + if (pos.x < rect.right()) { + // final piece + dc.DrawLine(pos - dy, pos + dx / 2); + } + // Draw the contents + CompoundTextElement::draw(dc, scale, rect, xs, what, start, end); +} \ No newline at end of file diff --git a/src/render/text/element.cpp b/src/render/text/element.cpp index e233cf9e..5da7a8e7 100644 --- a/src/render/text/element.cpp +++ b/src/render/text/element.cpp @@ -73,18 +73,19 @@ struct TextElementsFromString { // What formatting is enabled? int bold, italic, symbol; int soft, kwpph, param, line; - int code, code_kw, code_string, param_ref; + int code, code_kw, code_string, param_ref, error; int param_id; bool bracket; TextElementsFromString() : bold(0), italic(0), symbol(0), soft(0), kwpph(0), param(0), line(0) - , code(0), code_kw(0), code_string(0), param_ref(0) + , code(0), code_kw(0), code_string(0), param_ref(0), error(0) , param_id(0), bracket(false) {} // read TextElements from a string void fromString(TextElements& te, const String& text, size_t start, size_t end, const TextStyle& style, Context& ctx) { te.elements.clear(); + end = min(end, text.size()); // for each character... for (size_t pos = start ; pos < end ; ) { Char c = text.GetChar(pos); @@ -126,11 +127,18 @@ struct TextElementsFromString { else if (is_substr(text, tag_start, _(" e(new AtomTextElement(text, pos, end)); - fromString(e->elements, text, pos, end, style, ctx); + size_t end_tag = min(end, match_close_tag(text, tag_start)); + shared_ptr e(new AtomTextElement(text, pos, end_tag)); + fromString(e->elements, text, pos, end_tag, style, ctx); te.elements.push_back(e); - pos = skip_tag(text, end); + pos = skip_tag(text, end_tag); + } else if (is_substr(text, tag_start, _( " e(new ErrorTextElement(text, pos, end_tag)); + fromString(e->elements, text, pos, end_tag, style, ctx); + te.elements.push_back(e); + pos = skip_tag(text, end_tag); } else { // ignore other tags } diff --git a/src/render/text/element.hpp b/src/render/text/element.hpp index 578b00c1..6377928b 100644 --- a/src/render/text/element.hpp +++ b/src/render/text/element.hpp @@ -164,6 +164,13 @@ class AtomTextElement : public CompoundTextElement { virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; }; +/// A TextElement drawn using a red wavy underline +class ErrorTextElement : public CompoundTextElement { + public: + ErrorTextElement(const String& text, size_t start ,size_t end) : CompoundTextElement(text, start, end) {} + + virtual void draw(RotatedDC& dc, double scale, const RealRect& rect, const double* xs, DrawWhat what, size_t start, size_t end) const; +}; // ----------------------------------------------------------------------------- : Other text elements /* diff --git a/src/script/parser.cpp b/src/script/parser.cpp index fece513d..45d40bb5 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -51,10 +51,11 @@ enum OpenBrace , BRACE_PAREN // (, [, { }; -/// Iterator over a string, one token at a time +/// Iterator over a string, one token at a time. +/** Also stores errors found when tokenizing or parsing */ class TokenIterator { public: - TokenIterator(const String& str, bool string_mode); + TokenIterator(const String& str, bool string_mode, vector& errors); /// Peek at the next token, doesn't move to the one after that /** Can peek further forward by using higher values of offset. @@ -87,6 +88,14 @@ class TokenIterator { void readToken(); /// Read the next token which is a string (after the opening ") void readStringToken(); + + public: + /// All errors found + vector& errors; + /// Add an error message + void add_error(const String& message); + /// Expected some token instead of what was found + void expected(const String& exp); }; // ----------------------------------------------------------------------------- : Characters @@ -102,10 +111,11 @@ bool isLongOper(const String& s) { return s==_(":=") || s==_("==") || s==_("!=") // ----------------------------------------------------------------------------- : Tokenizing -TokenIterator::TokenIterator(const String& str, bool string_mode) +TokenIterator::TokenIterator(const String& str, bool string_mode, vector& errors) : input(str) , pos(0) , newline(false) + , errors(errors) { if (string_mode) { open_braces.push(BRACE_STRING_MODE); @@ -221,7 +231,8 @@ void TokenIterator::readToken() { // comment untill end of line while (pos < input.size() && input[pos] != _('\n')) ++pos; } else { - throw ScriptParseError(_("Unknown character in script: '") + String(1,c) + _("'")); + add_error(_("Unknown character in script: '") + String(1,c) + _("'")); + // just skip the character } } @@ -234,7 +245,10 @@ void TokenIterator::readStringToken() { addToken(TOK_STRING, str); return; } else { - throw ScriptParseError(_("Unexpected end of input in string constant")); + add_error(_("Unexpected end of input in string constant")); + // fix up + addToken(TOK_STRING, str); + return; } } Char c = input.GetChar(pos++); @@ -246,7 +260,12 @@ void TokenIterator::readStringToken() { return; } else if (c == _('\\')) { // escape - if (pos >= input.size()) throw ScriptParseError(_("Unexpected end of input in string constant")); + if (pos >= input.size()) { + add_error(_("Unexpected end of input in string constant")); + // fix up + addToken(TOK_STRING, str); + return; + } c = input.GetChar(pos++); if (c == _('n')) str += _('\n'); else if (c == _('<')) str += _('\1'); // escape for < @@ -264,8 +283,20 @@ void TokenIterator::readStringToken() { } +void TokenIterator::add_error(const String& message) { + if (!errors.empty() && errors.back().start == pos) return; // already an error here + errors.push_back(ScriptParseError(pos, message)); +} +void TokenIterator::expected(const String& expected) { + size_t error_pos = pos - peek(0).value.size(); + if (!errors.empty() && errors.back().start == pos) return; // already an error here + errors.push_back(ScriptParseError(error_pos, expected, peek(0).value)); +} + + // ----------------------------------------------------------------------------- : Parsing + /// Precedence levels for parsing, higher = tighter enum Precedence { PREC_ALL @@ -300,22 +331,43 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec); */ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith = I_NOP, int closeWithData = 0); -ScriptP parse(const String& s, bool string_mode) { - TokenIterator input(s, string_mode); + +ScriptP parse(const String& s, bool string_mode, vector& errors_out) { + errors_out.clear(); + // parse + TokenIterator input(s, string_mode, errors_out); ScriptP script(new Script); parseOper(input, *script, PREC_ALL, I_RET); - if (input.peek() != TOK_EOF) { - throw ScriptParseError(_("end of input"), input.peek().value); - } else { + Token eof = input.read(); + if (eof != TOK_EOF) { + input.expected(_("end of input")); + } + // were there errors? + if (errors_out.empty()) { return script; + } else { + return ScriptP(); } } -// Expect a token, throws if it is not found -void expectToken(TokenIterator& input, const Char* expect) { +ScriptP parse(const String& s, bool string_mode) { + vector errors; + ScriptP script = parse(s, string_mode, errors); + if (!errors.empty()) { + throw ScriptParseErrors(errors); + } + return script; +} + + +// Expect a token, adds an error if it is not found +bool expectToken(TokenIterator& input, const Char* expect, const Char* name_in_error = nullptr) { Token token = input.read(); - if (token != expect) { - throw ScriptParseError(expect, token.value); + if (token == expect) { + return true; + } else { + input.expected(name_in_error ? name_in_error : expect); + return false; } } @@ -367,7 +419,9 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { // for each AAA in BBB do CCC input.read(); // each Token name = input.read(); // AAA - if (name != TOK_NAME) throw ScriptParseError(_("name"), name.value); + if (name != TOK_NAME) { + input.expected(_("name")); + } expectToken(input, _("in")); // in parseOper(input, script, PREC_SET); // BBB script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection @@ -428,7 +482,8 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { } else if (token == TOK_STRING) { script.addInstruction(I_PUSH_CONST, to_script(token.value)); } else { - throw ScriptParseError(_("Unexpected token '") + token.value + _("'")); + input.expected(_("expression")); + return; } break; } @@ -459,11 +514,10 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc // not an expression. Remove that instruction. Instruction instr = script.getInstructions().back(); if (instr.instr != I_GET_VAR) { - throw ScriptParseError(_("Can only assign to variables")); - } else { - script.getInstructions().pop_back(); - parseOper(input, script, PREC_SET, I_SET_VAR, instr.data); + input.add_error(_("Can only assign to variables")); } + script.getInstructions().pop_back(); + parseOper(input, script, PREC_SET, I_SET_VAR, instr.data); } else if (minPrec <= PREC_AND && token==_("and")) parseOper(input, script, PREC_CMP, I_BINARY, I_AND); else if (minPrec <= PREC_AND && token==_("or" )) parseOper(input, script, PREC_CMP, I_BINARY, I_OR); @@ -484,7 +538,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc if (token == TOK_NAME || token == TOK_INT || token == TOK_DOUBLE || token == TOK_STRING) { script.addInstruction(I_MEMBER_C, token.value); } else { - throw ScriptParseError(_("name"), input.peek().value); + input.expected(_("name")); } } else if (minPrec <= PREC_FUN && token==_("[")) { // get member by expr parseOper(input, script, PREC_ALL, I_BINARY, I_MEMBER); @@ -528,14 +582,15 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc } else { parseOper(input, script, PREC_ALL, I_BINARY, I_ADD); // e } - expectToken(input, _("}\"")); - parseOper(input, script, PREC_NONE); // y - // optimize: e + "" -> e - i = script.getInstructions().back(); - if (i.instr == I_PUSH_CONST && script.getConstants()[i.data]->toString().empty()) { - script.getInstructions().pop_back(); - } else { - script.addInstruction(I_BINARY, I_ADD); + if (expectToken(input, _("}\""), _("}"))) { + parseOper(input, script, PREC_NONE); // y + // optimize: e + "" -> e + i = script.getInstructions().back(); + if (i.instr == I_PUSH_CONST && script.getConstants()[i.data]->toString().empty()) { + script.getInstructions().pop_back(); + } else { + script.addInstruction(I_BINARY, I_ADD); + } } } else if (minPrec <= PREC_NEWLINE && token.newline) { // newline functions as ; diff --git a/src/script/parser.hpp b/src/script/parser.hpp index 850b20a6..1423f183 100644 --- a/src/script/parser.hpp +++ b/src/script/parser.hpp @@ -10,13 +10,25 @@ // ----------------------------------------------------------------------------- : Includes #include +#include #include