Commit 1a2b1b2e authored by twanvl's avatar twanvl

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.
parent 9b613ee3
...@@ -130,9 +130,20 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { ...@@ -130,9 +130,20 @@ ScriptValueP Context::eval(const Script& script, bool useScope) {
// skip the stack effect of the arguments themselfs // skip the stack effect of the arguments themselfs
const Instruction* instr_bt = script.backtraceSkip(instr - i.data - 2, i.data); const Instruction* instr_bt = script.backtraceSkip(instr - i.data - 2, i.data);
// have we have reached the name // have we have reached the name
if (instr_bt && instr_bt->instr == I_GET_VAR) { if (instr_bt) {
// this is a valid instruction, it is I_GET_VAR if (instr_bt->instr == I_GET_VAR) {
throw ScriptError(e.what() + _("\n in function: ") + variable_to_string(instr_bt->data)); 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 { } else {
throw e; // rethrow throw e; // rethrow
} }
...@@ -234,10 +245,15 @@ void instrUnary (UnaryInstructionType i, ScriptValueP& a) { ...@@ -234,10 +245,15 @@ void instrUnary (UnaryInstructionType i, ScriptValueP& a) {
case I_ITERATOR_C: case I_ITERATOR_C:
a = a->makeIterator(a); a = a->makeIterator(a);
break; break;
case I_NEGATE: case I_NEGATE: {
a = to_script(-(int)*a); ScriptType at = a->type();
if (at == SCRIPT_DOUBLE) {
a = to_script(-(double)*a);
} else {
a = to_script(-(int)*a);
}
break; break;
case I_NOT: } case I_NOT:
a = to_script(!(bool)*a); a = to_script(!(bool)*a);
break; break;
} }
......
...@@ -19,6 +19,8 @@ DECLARE_TYPEOF_COLLECTION(Variable); ...@@ -19,6 +19,8 @@ DECLARE_TYPEOF_COLLECTION(Variable);
#define TokenType TokenType_ // some stupid windows header uses our name #define TokenType TokenType_ // some stupid windows header uses our name
#endif #endif
String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false);
// ----------------------------------------------------------------------------- : Tokenizing : class // ----------------------------------------------------------------------------- : Tokenizing : class
enum TokenType enum TokenType
...@@ -72,6 +74,7 @@ class TokenIterator { ...@@ -72,6 +74,7 @@ class TokenIterator {
private: private:
String input; String input;
size_t pos; size_t pos;
String filename; ///< Filename of include files, "" for the main input
vector<Token> buffer; ///< buffer of unread tokens, front() = current vector<Token> buffer; ///< buffer of unread tokens, front() = current
stack<OpenBrace> open_braces; ///< braces/quotes we entered from script mode stack<OpenBrace> open_braces; ///< braces/quotes we entered from script mode
bool newline; ///< Did we just pass a newline? bool newline; ///< Did we just pass a newline?
...@@ -79,6 +82,7 @@ class TokenIterator { ...@@ -79,6 +82,7 @@ class TokenIterator {
struct MoreInput { struct MoreInput {
String input; String input;
size_t pos; size_t pos;
String filename;
}; };
stack<MoreInput> more; ///< Read tokens from here when we are done with the current input stack<MoreInput> more; ///< Read tokens from here when we are done with the current input
...@@ -154,8 +158,9 @@ void TokenIterator::readToken() { ...@@ -154,8 +158,9 @@ void TokenIterator::readToken() {
if (pos >= input.size()) { if (pos >= input.size()) {
// done with input, is there more? // done with input, is there more?
if (!more.empty()) { if (!more.empty()) {
input = more.top().input; input = more.top().input;
pos = more.top().pos; pos = more.top().pos;
filename = more.top().filename;
more.pop(); more.pop();
} else { } else {
// EOF // EOF
...@@ -176,17 +181,15 @@ void TokenIterator::readToken() { ...@@ -176,17 +181,15 @@ void TokenIterator::readToken() {
pos += 12; // "nclude file:" pos += 12; // "nclude file:"
size_t eol = input.find_first_of(_("\r\n"), pos); size_t eol = input.find_first_of(_("\r\n"), pos);
if (eol == String::npos) eol = input.size(); 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 // store the current input for later retrieval
MoreInput m = {input, eol}; MoreInput m = {input, eol, filename};
more.push(m); more.push(m);
// open file
InputStreamP is = packages.openFileFromPackage(filename);
wxTextInputStream tis(*is);
// read the entire file, and start at the beginning of it // read the entire file, and start at the beginning of it
pos = 0; pos = 0;
input.clear(); filename = include_file;
while (!is->Eof()) input += tis.ReadLine() + _('\n'); InputStreamP is = packages.openFileFromPackage(include_file);
input = read_utf8_line(*is, true, true);
} else if (isAlpha(c)) { } else if (isAlpha(c)) {
// name // name
size_t start = pos - 1; size_t start = pos - 1;
...@@ -285,12 +288,22 @@ void TokenIterator::readStringToken() { ...@@ -285,12 +288,22 @@ void TokenIterator::readStringToken() {
void TokenIterator::add_error(const String& message) { void TokenIterator::add_error(const String& message) {
if (!errors.empty() && errors.back().start == pos) return; // already an error here 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) { void TokenIterator::expected(const String& expected) {
size_t error_pos = pos - peek(0).value.size(); size_t error_pos = pos - peek(0).value.size();
if (!errors.empty() && errors.back().start == pos) return; // already an error here 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));
} }
......
...@@ -65,11 +65,14 @@ void Script::addInstruction(InstructionType t) { ...@@ -65,11 +65,14 @@ void Script::addInstruction(InstructionType t) {
instructions.push_back(i); instructions.push_back(i);
} }
void Script::addInstruction(InstructionType t, unsigned int d) { 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 // optimize: push x ; member --> member_c x
instructions.back().instr = I_MEMBER_C; instructions.back().instr = I_MEMBER_C;
return; return;
} }*/
Instruction i = {t, {d}}; Instruction i = {t, {d}};
instructions.push_back(i); instructions.push_back(i);
} }
...@@ -97,76 +100,78 @@ unsigned int Script::getLabel() const { ...@@ -97,76 +100,78 @@ unsigned int Script::getLabel() const {
DECLARE_TYPEOF_COLLECTION(Instruction); DECLARE_TYPEOF_COLLECTION(Instruction);
#if 0 // debugging #ifdef _DEBUG // debugging
String Script::dumpScript() const { String Script::dumpScript() const {
String ret; String ret;
int pos = 0; int pos = 0;
FOR_EACH_CONST(i, instructions) { FOR_EACH_CONST(i, instructions) {
ret += dumpInstr(pos++, i) + "\n"; wxLogDebug(dumpInstr(pos, i));
ret += dumpInstr(pos++, i) + _("\n");
} }
return ret; return ret;
} }
String Script::dumpInstr(unsigned int pos, Instruction i) const { String Script::dumpInstr(unsigned int pos, Instruction i) const {
String ret = lexical_cast<String>(pos) + ":\t"; String ret = String::Format(_("%d:\t"),pos);
// instruction // instruction
switch (i.instr) { switch (i.instr) {
case I_NOP: ret += "nop"; break; case I_NOP: ret += _("nop"); break;
case I_PUSH_CONST: ret += "push"; break; case I_PUSH_CONST: ret += _("push"); break;
case I_POP: ret += "pop"; break; case I_POP: ret += _("pop"); break;
case I_JUMP: ret += "jump"; break; case I_JUMP: ret += _("jump"); break;
case I_JUMP_IF_NOT: ret += "jnz"; break; case I_JUMP_IF_NOT: ret += _("jnz"); break;
case I_GET_VAR: ret += "get"; break; case I_GET_VAR: ret += _("get"); break;
case I_SET_VAR: ret += "set"; break; case I_SET_VAR: ret += _("set"); break;
case I_MEMBER_C: ret += "member_c"; break; case I_MEMBER_C: ret += _("member_c"); break;
case I_LOOP: ret += "loop"; break; case I_LOOP: ret += _("loop"); break;
case I_CALL: ret += "call"; break; case I_MAKE_OBJECT: ret += _("make object");break;
case I_RET: ret += "ret"; break; case I_CALL: ret += _("call"); break;
case I_UNARY: ret += "unary\t"; case I_RET: ret += _("ret"); break;
case I_UNARY: ret += _("unary\t");
switch (i.instr1) { switch (i.instr1) {
case I_ITERATOR_C: ret += "iterator_c";break; case I_ITERATOR_C: ret += _("iterator_c"); break;
case I_NEGATE: ret += "negate"; break; case I_NEGATE: ret += _("negate"); break;
case I_NOT: ret += "not"; break; case I_NOT: ret += _("not"); break;
} }
break; break;
case I_BINARY: ret += "binary\t"; case I_BINARY: ret += _("binary\t");
switch (i.instr2) { switch (i.instr2) {
case I_ITERATOR_R: ret += "iterator_r";break; case I_ITERATOR_R: ret += _("iterator_r"); break;
case I_MEMBER: ret += "member"; break; case I_MEMBER: ret += _("member"); break;
case I_ADD: ret += "+"; break; case I_ADD: ret += _("+"); break;
case I_SUB: ret += "-"; break; case I_SUB: ret += _("-"); break;
case I_MUL: ret += "*"; break; case I_MUL: ret += _("*"); break;
case I_DIV: ret += "/"; break; case I_DIV: ret += _("/"); break;
case I_MOD: ret += "*"; break; case I_MOD: ret += _("mod"); break;
case I_AND: ret += "and"; break; case I_AND: ret += _("and"); break;
case I_OR: ret += "or"; break; case I_OR: ret += _("or"); break;
case I_EQ: ret += "=="; break; case I_EQ: ret += _("=="); break;
case I_NEQ: ret += "!="; break; case I_NEQ: ret += _("!="); break;
case I_LT: ret += "<"; break; case I_LT: ret += _("<"); break;
case I_GT: ret += ">"; break; case I_GT: ret += _(">"); break;
case I_LE: ret += "<="; break; case I_LE: ret += _("<="); break;
case I_GE: ret += ">="; break; case I_GE: ret += _(">="); break;
case I_OR_ELSE: ret += _("or else"); break;
} }
break; break;
case I_TERNARY: ret += "ternary\t"; case I_TERNARY: ret += _("ternary\t");
switch (i.instr3) { switch (i.instr3) {
case I_RGB: ret += "rgb"; break; case I_RGB: ret += _("rgb"); break;
} }
break; break;
} }
// arg // arg
switch (i.instr) { switch (i.instr) {
case I_PUSH_CONST: case I_MEMBER_C: // const case I_PUSH_CONST: case I_MEMBER_C: // const
ret += "\t" + (String)*constants[i.data]; ret += _("\t") + constants[i.data]->toString();
ret += "\t(" + constants[i.data]->typeName(); ret += _("\t(") + constants[i.data]->typeName() + _(")");
ret += ", #" + lexical_cast<String>(i.data) + ")";
break; break;
case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_CALL: // int case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_CALL: // int
ret += "\t" + lexical_cast<String>(i.data); ret += String::Format(_("\t%d"), i.data);
break; break;
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
ret += "\t" + variable_to_string(i.data) + "\t$" + lexical_cast<String>(i.data); ret += _("\t") + variable_to_string(i.data);
break; break;
} }
return ret; return ret;
...@@ -243,4 +248,4 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) ...@@ -243,4 +248,4 @@ const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip)
} }
} }
return instr >= &instructions[0] ? instr : nullptr; return instr >= &instructions[0] ? instr : nullptr;
} }
\ No newline at end of file
...@@ -48,13 +48,20 @@ void OptionalScript::parse(Reader& reader, bool string_mode) { ...@@ -48,13 +48,20 @@ void OptionalScript::parse(Reader& reader, bool string_mode) {
vector<ScriptParseError> errors; vector<ScriptParseError> errors;
script = ::parse(unparsed, string_mode, errors); script = ::parse(unparsed, string_mode, errors);
// show parse errors as warnings // show parse errors as warnings
FOR_EACH(e, errors) { String include_warnings;
// find line number for (size_t i = 0 ; i < errors.size() ; ++i) {
int line = 0; const ScriptParseError& e = errors[i];
for (size_t i = 0 ; i < unparsed.size() && i < e.start ; ++i) { if (!e.filename.empty()) {
if (unparsed.GetChar(i) == _('\n')) line++; // 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
} }
} }
......
...@@ -24,12 +24,12 @@ String Error::what() const { ...@@ -24,12 +24,12 @@ String Error::what() const {
// ----------------------------------------------------------------------------- : Parse errors // ----------------------------------------------------------------------------- : Parse errors
ScriptParseError::ScriptParseError(size_t pos, const String& error) ScriptParseError::ScriptParseError(size_t pos, int line, const String& filename, const String& error)
: start(pos), end(pos) : start(pos), end(pos), line(line), filename(filename)
, ParseError(error) , ParseError(error)
{} {}
ScriptParseError::ScriptParseError(size_t pos, const String& exp, const String& found) ScriptParseError::ScriptParseError(size_t pos, int line, const String& filename, const String& exp, const String& found)
: start(pos), end(pos + found.size()) : start(pos), end(pos + found.size()), line(line), filename(filename)
, ParseError(_("Expected '") + exp + _("' instead of '") + found + _("'")) , ParseError(_("Expected '") + exp + _("' instead of '") + found + _("'"))
{} {}
String ScriptParseError::what() const { String ScriptParseError::what() const {
......
...@@ -75,10 +75,14 @@ class FileParseError : public ParseError { ...@@ -75,10 +75,14 @@ class FileParseError : public ParseError {
/// Parse error in a script /// Parse error in a script
class ScriptParseError : public ParseError { class ScriptParseError : public ParseError {
public: public:
ScriptParseError(size_t pos, const String& str); ScriptParseError(size_t pos, int line, const String& filename, const String& str);
ScriptParseError(size_t pos, const String& expected, const String& found); ScriptParseError(size_t pos, int line, const String& filename, const String& expected, const String& found);
/// Position of the error /// Position of the error
size_t start, end; 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 /// Return the error message
virtual String what() const; virtual String what() const;
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment