From 86393f33dcb6e6a03d2aa5ba4efdd90aebbf0a0f Mon Sep 17 00:00:00 2001 From: twanvl Date: Sun, 1 Jul 2007 18:11:48 +0000 Subject: [PATCH] Fixed bug in script parser/compiler: "x[if a then b else c]" was incorrectly optimized; Sciript parse errors in include files now get reported for the right file and line number. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@458 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/script/context.cpp | 28 +++++++++--- src/script/parser.cpp | 35 ++++++++++----- src/script/script.cpp | 93 +++++++++++++++++++++------------------ src/script/scriptable.cpp | 19 +++++--- src/util/error.cpp | 8 ++-- src/util/error.hpp | 8 +++- 6 files changed, 118 insertions(+), 73 deletions(-) diff --git a/src/script/context.cpp b/src/script/context.cpp index 81ae6575..680fe7b7 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -130,9 +130,20 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { // skip the stack effect of the arguments themselfs const Instruction* instr_bt = script.backtraceSkip(instr - i.data - 2, i.data); // have we have reached the name - if (instr_bt && instr_bt->instr == I_GET_VAR) { - // this is a valid instruction, it is I_GET_VAR - throw ScriptError(e.what() + _("\n in function: ") + variable_to_string(instr_bt->data)); + if (instr_bt) { + if (instr_bt->instr == I_GET_VAR) { + throw ScriptError(e.what() + _("\n in function: ") + variable_to_string(instr_bt->data)); + } else if (instr_bt->instr == I_MEMBER_C) { + throw ScriptError(e.what() + _("\n in function: ???.") + *script.constants[instr_bt->data]); + } else if (instr_bt->instr == I_BINARY && instr_bt->instr2 == I_MEMBER) { + throw ScriptError(e.what() + _("\n in function: ???[???]")); + } else if (instr_bt->instr == I_BINARY && instr_bt->instr2 == I_ADD) { + throw ScriptError(e.what() + _("\n in function: ??? + ???")); + } else if (instr_bt->instr == I_NOP || instr_bt->instr == I_CALL) { + throw ScriptError(e.what() + _("\n in function: ???(???)")); + } else { + throw ScriptError(e.what() + _("\n in function: ???")); + } } else { throw e; // rethrow } @@ -234,10 +245,15 @@ void instrUnary (UnaryInstructionType i, ScriptValueP& a) { case I_ITERATOR_C: a = a->makeIterator(a); break; - case I_NEGATE: - a = to_script(-(int)*a); + case I_NEGATE: { + ScriptType at = a->type(); + if (at == SCRIPT_DOUBLE) { + a = to_script(-(double)*a); + } else { + a = to_script(-(int)*a); + } break; - case I_NOT: + } case I_NOT: a = to_script(!(bool)*a); break; } diff --git a/src/script/parser.cpp b/src/script/parser.cpp index a5283ecc..fc692adc 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -19,6 +19,8 @@ DECLARE_TYPEOF_COLLECTION(Variable); #define TokenType TokenType_ // some stupid windows header uses our name #endif +String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false); + // ----------------------------------------------------------------------------- : Tokenizing : class enum TokenType @@ -72,6 +74,7 @@ class TokenIterator { private: String input; size_t pos; + String filename; ///< Filename of include files, "" for the main input vector buffer; ///< buffer of unread tokens, front() = current stack open_braces; ///< braces/quotes we entered from script mode bool newline; ///< Did we just pass a newline? @@ -79,6 +82,7 @@ class TokenIterator { struct MoreInput { String input; size_t pos; + String filename; }; stack more; ///< Read tokens from here when we are done with the current input @@ -154,8 +158,9 @@ void TokenIterator::readToken() { if (pos >= input.size()) { // done with input, is there more? if (!more.empty()) { - input = more.top().input; - pos = more.top().pos; + input = more.top().input; + pos = more.top().pos; + filename = more.top().filename; more.pop(); } else { // EOF @@ -176,17 +181,15 @@ void TokenIterator::readToken() { pos += 12; // "nclude file:" size_t eol = input.find_first_of(_("\r\n"), pos); if (eol == String::npos) eol = input.size(); - String filename = trim(input.substr(pos, eol - pos)); + String include_file = trim(input.substr(pos, eol - pos)); // store the current input for later retrieval - MoreInput m = {input, eol}; + MoreInput m = {input, eol, filename}; more.push(m); - // open file - InputStreamP is = packages.openFileFromPackage(filename); - wxTextInputStream tis(*is); // read the entire file, and start at the beginning of it pos = 0; - input.clear(); - while (!is->Eof()) input += tis.ReadLine() + _('\n'); + filename = include_file; + InputStreamP is = packages.openFileFromPackage(include_file); + input = read_utf8_line(*is, true, true); } else if (isAlpha(c)) { // name size_t start = pos - 1; @@ -285,12 +288,22 @@ 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)); + // find line number + int line = 1; + for (size_t i = 0 ; i < input.size() && i < pos ; ++i) { + if (input.GetChar(i) == _('\n')) line++; + } + errors.push_back(ScriptParseError(pos, line, filename, 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)); + // find line number + int line = 1; + for (size_t i = 0 ; i < input.size() && i < error_pos ; ++i) { + if (input.GetChar(i) == _('\n')) line++; + } + errors.push_back(ScriptParseError(error_pos, line, filename, expected, peek(0).value)); } diff --git a/src/script/script.cpp b/src/script/script.cpp index 6362133b..13ebd38f 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -65,11 +65,14 @@ void Script::addInstruction(InstructionType t) { instructions.push_back(i); } void Script::addInstruction(InstructionType t, unsigned int d) { - if (t == I_BINARY && d == I_MEMBER && !instructions.empty() && instructions.back().instr == I_PUSH_CONST) { + // Don't optimize ...I_PUSH_CONST x; I_MEMBER... to I_MEMBER_C + // because the code could be something[if a then "x" else "y"] + // the last instruction before I_MEMBER is I_PUSH_CONST "y", but this is only one branch of the if + /*if (t == I_BINARY && d == I_MEMBER && !instructions.empty() && instructions.back().instr == I_PUSH_CONST) { // optimize: push x ; member --> member_c x instructions.back().instr = I_MEMBER_C; return; - } + }*/ Instruction i = {t, {d}}; instructions.push_back(i); } @@ -97,76 +100,78 @@ unsigned int Script::getLabel() const { DECLARE_TYPEOF_COLLECTION(Instruction); -#if 0 // debugging +#ifdef _DEBUG // debugging String Script::dumpScript() const { String ret; int pos = 0; FOR_EACH_CONST(i, instructions) { - ret += dumpInstr(pos++, i) + "\n"; + wxLogDebug(dumpInstr(pos, i)); + ret += dumpInstr(pos++, i) + _("\n"); } return ret; } String Script::dumpInstr(unsigned int pos, Instruction i) const { - String ret = lexical_cast(pos) + ":\t"; + String ret = String::Format(_("%d:\t"),pos); // instruction switch (i.instr) { - case I_NOP: ret += "nop"; break; - case I_PUSH_CONST: ret += "push"; break; - case I_POP: ret += "pop"; break; - case I_JUMP: ret += "jump"; break; - case I_JUMP_IF_NOT: ret += "jnz"; break; - case I_GET_VAR: ret += "get"; break; - case I_SET_VAR: ret += "set"; break; - case I_MEMBER_C: ret += "member_c"; break; - case I_LOOP: ret += "loop"; break; - case I_CALL: ret += "call"; break; - case I_RET: ret += "ret"; break; - case I_UNARY: ret += "unary\t"; + case I_NOP: ret += _("nop"); break; + case I_PUSH_CONST: ret += _("push"); break; + case I_POP: ret += _("pop"); break; + case I_JUMP: ret += _("jump"); break; + case I_JUMP_IF_NOT: ret += _("jnz"); break; + case I_GET_VAR: ret += _("get"); break; + case I_SET_VAR: ret += _("set"); break; + case I_MEMBER_C: ret += _("member_c"); break; + case I_LOOP: ret += _("loop"); break; + case I_MAKE_OBJECT: ret += _("make object");break; + case I_CALL: ret += _("call"); break; + case I_RET: ret += _("ret"); break; + case I_UNARY: ret += _("unary\t"); switch (i.instr1) { - case I_ITERATOR_C: ret += "iterator_c";break; - case I_NEGATE: ret += "negate"; break; - case I_NOT: ret += "not"; break; + case I_ITERATOR_C: ret += _("iterator_c"); break; + case I_NEGATE: ret += _("negate"); break; + case I_NOT: ret += _("not"); break; } break; - case I_BINARY: ret += "binary\t"; + case I_BINARY: ret += _("binary\t"); switch (i.instr2) { - case I_ITERATOR_R: ret += "iterator_r";break; - case I_MEMBER: ret += "member"; break; - case I_ADD: ret += "+"; break; - case I_SUB: ret += "-"; break; - case I_MUL: ret += "*"; break; - case I_DIV: ret += "/"; break; - case I_MOD: ret += "*"; break; - case I_AND: ret += "and"; break; - case I_OR: ret += "or"; break; - case I_EQ: ret += "=="; break; - case I_NEQ: ret += "!="; break; - case I_LT: ret += "<"; break; - case I_GT: ret += ">"; break; - case I_LE: ret += "<="; break; - case I_GE: ret += ">="; break; + case I_ITERATOR_R: ret += _("iterator_r"); break; + case I_MEMBER: ret += _("member"); break; + case I_ADD: ret += _("+"); break; + case I_SUB: ret += _("-"); break; + case I_MUL: ret += _("*"); break; + case I_DIV: ret += _("/"); break; + case I_MOD: ret += _("mod"); break; + case I_AND: ret += _("and"); break; + case I_OR: ret += _("or"); break; + case I_EQ: ret += _("=="); break; + case I_NEQ: ret += _("!="); break; + case I_LT: ret += _("<"); break; + case I_GT: ret += _(">"); break; + case I_LE: ret += _("<="); break; + case I_GE: ret += _(">="); break; + case I_OR_ELSE: ret += _("or else"); break; } break; - case I_TERNARY: ret += "ternary\t"; + case I_TERNARY: ret += _("ternary\t"); switch (i.instr3) { - case I_RGB: ret += "rgb"; break; + case I_RGB: ret += _("rgb"); break; } break; } // arg switch (i.instr) { case I_PUSH_CONST: case I_MEMBER_C: // const - ret += "\t" + (String)*constants[i.data]; - ret += "\t(" + constants[i.data]->typeName(); - ret += ", #" + lexical_cast(i.data) + ")"; + ret += _("\t") + constants[i.data]->toString(); + ret += _("\t(") + constants[i.data]->typeName() + _(")"); break; case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_CALL: // int - ret += "\t" + lexical_cast(i.data); + ret += String::Format(_("\t%d"), i.data); break; case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable - ret += "\t" + variable_to_string(i.data) + "\t$" + lexical_cast(i.data); + ret += _("\t") + variable_to_string(i.data); break; } return ret; @@ -243,4 +248,4 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) } } return instr >= &instructions[0] ? instr : nullptr; -} \ No newline at end of file +} diff --git a/src/script/scriptable.cpp b/src/script/scriptable.cpp index 754e5725..04c4126a 100644 --- a/src/script/scriptable.cpp +++ b/src/script/scriptable.cpp @@ -48,13 +48,20 @@ void OptionalScript::parse(Reader& reader, bool string_mode) { vector errors; script = ::parse(unparsed, string_mode, errors); // show parse errors as warnings - FOR_EACH(e, errors) { - // find line number - int line = 0; - for (size_t i = 0 ; i < unparsed.size() && i < e.start ; ++i) { - if (unparsed.GetChar(i) == _('\n')) line++; + String include_warnings; + for (size_t i = 0 ; i < errors.size() ; ++i) { + const ScriptParseError& e = errors[i]; + if (!e.filename.empty()) { + // error in an include file + include_warnings += String::Format(_("\n On line %d:\t "), e.line) + e.ParseError::what(); + if (i + 1 >= errors.size() || errors[i+1].filename != e.filename) { + reader.warning(_("In include file '") + e.filename + _("'") + include_warnings); + include_warnings.clear(); + } + } else { + // use ParseError::what because we don't want e.start in the error message + reader.warning(e.ParseError::what(), e.line - 1); } - reader.warning(e.ParseError::what(), line); // use ParseError::what because we don't want e.start in the error message } } diff --git a/src/util/error.cpp b/src/util/error.cpp index 50baa111..6250272f 100644 --- a/src/util/error.cpp +++ b/src/util/error.cpp @@ -24,12 +24,12 @@ String Error::what() const { // ----------------------------------------------------------------------------- : Parse errors -ScriptParseError::ScriptParseError(size_t pos, const String& error) - : start(pos), end(pos) +ScriptParseError::ScriptParseError(size_t pos, int line, const String& filename, const String& error) + : start(pos), end(pos), line(line), filename(filename) , ParseError(error) {} -ScriptParseError::ScriptParseError(size_t pos, const String& exp, const String& found) - : start(pos), end(pos + found.size()) +ScriptParseError::ScriptParseError(size_t pos, int line, const String& filename, const String& exp, const String& found) + : start(pos), end(pos + found.size()), line(line), filename(filename) , ParseError(_("Expected '") + exp + _("' instead of '") + found + _("'")) {} String ScriptParseError::what() const { diff --git a/src/util/error.hpp b/src/util/error.hpp index 571ccb00..235bd14a 100644 --- a/src/util/error.hpp +++ b/src/util/error.hpp @@ -75,10 +75,14 @@ class FileParseError : public ParseError { /// Parse error in a script class ScriptParseError : public ParseError { public: - ScriptParseError(size_t pos, const String& str); - ScriptParseError(size_t pos, const String& expected, const String& found); + ScriptParseError(size_t pos, int line, const String& filename, const String& str); + ScriptParseError(size_t pos, int line, const String& filename, const String& expected, const String& found); /// Position of the error size_t start, end; + /// Line number of the error (the first line is 1) + int line; + /// Filename the error was in, or an empty string + String filename; /// Return the error message virtual String what() const; };