diff --git a/src/script/context.cpp b/src/script/context.cpp index 82ef9a96..3b59c6a7 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -74,7 +74,7 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { // Get a variable case I_GET_VAR: { ScriptValueP value = variables[i.data].value; - if (!value) throw ScriptError(_("Variable not set: ") + variable_to_string((Variable)i.data)); + if (!value) throw ScriptErrorNoVariable(variable_to_string((Variable)i.data)); stack.push_back(value); break; } @@ -124,7 +124,7 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { // Function call case I_CALL: { // new scope - size_t scope = openScope(); + LocalScope local_scope(*this); // prepare arguments for (unsigned int j = 0 ; j < i.data ; ++j) { setVariable((Variable)instr[i.data - j - 1].data, stack.back()); @@ -135,7 +135,6 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { // get function and call stack.back() = stack.back()->eval(*this); } catch (const Error& e) { - closeScope(scope); // try to determine what named function was called // the instructions for this look like: // I_GET_VAR name of function @@ -152,8 +151,6 @@ ScriptValueP Context::eval(const Script& script, bool useScope) { throw e; // rethrow } } - // restore scope - closeScope(scope); break; } @@ -241,7 +238,7 @@ void Context::setVariable(Variable name, const ScriptValueP& value) { ScriptValueP Context::getVariable(const String& name) { ScriptValueP value = variables[string_to_variable(name)].value; - if (!value) throw ScriptError(_("Variable not set: ") + name); + if (!value) throw ScriptErrorNoVariable(name); return value; } @@ -250,7 +247,7 @@ ScriptValueP Context::getVariableOpt(const String& name) { } ScriptValueP Context::getVariable(Variable var) { if (variables[var].value) return variables[var].value; - throw ScriptError(_("Variable not set: ") + variable_to_string(var)); + throw ScriptErrorNoVariable(variable_to_string(var)); } ScriptValueP Context::getVariableInScopeOpt(Variable var) { if (variables[var].level == level) return variables[var].value; diff --git a/src/script/functions/basic.cpp b/src/script/functions/basic.cpp index d854b952..d7f9ba3a 100644 --- a/src/script/functions/basic.cpp +++ b/src/script/functions/basic.cpp @@ -110,14 +110,14 @@ SCRIPT_FUNCTION(to_int) { } else if (str.empty()) { result = 0; } else { - return delayError(_ERROR_3_("can't convert value", str, input->typeName(), _TYPE_("integer"))); + return delay_error(ScriptErrorConversion(str, input->typeName(), _TYPE_("integer"))); } } else { result = (int)*input; } SCRIPT_RETURN(result); } catch (const ScriptError& e) { - return new_intrusive1(e); + return delay_error(e); } } @@ -136,14 +136,14 @@ SCRIPT_FUNCTION(to_real) { if (str.empty()) { result = 0.0; } else if (!str.ToDouble(&result)) { - return delayError(_ERROR_3_("can't convert value", str, input->typeName(), _TYPE_("double"))); + return delay_error(ScriptErrorConversion(str, input->typeName(), _TYPE_("double"))); } } else { result = (double)*input; } SCRIPT_RETURN(result); } catch (const ScriptError& e) { - return new_intrusive1(e); + return delay_error(e); } } @@ -170,11 +170,11 @@ SCRIPT_FUNCTION(to_number) { } else if (str.empty()) { SCRIPT_RETURN(0); } else { - return delayError(_ERROR_3_("can't convert value", str, input->typeName(), _TYPE_("double"))); + return delay_error(ScriptErrorConversion(str, input->typeName(), _TYPE_("double"))); } } } catch (const ScriptError& e) { - return new_intrusive1(e); + return delay_error(e); } } @@ -190,7 +190,7 @@ SCRIPT_FUNCTION(to_boolean) { } SCRIPT_RETURN(result); } catch (const ScriptError& e) { - return new_intrusive1(e); + return delay_error(e); } } @@ -199,7 +199,7 @@ SCRIPT_FUNCTION(to_color) { SCRIPT_PARAM_C(AColor, input); SCRIPT_RETURN(input); } catch (const ScriptError& e) { - return new_intrusive1(e); + return delay_error(e); } } diff --git a/src/script/functions/image.cpp b/src/script/functions/image.cpp index 6ffd8f8c..5f6e8b10 100644 --- a/src/script/functions/image.cpp +++ b/src/script/functions/image.cpp @@ -124,7 +124,7 @@ SCRIPT_FUNCTION(symbol_variation) { if (value) { filename = value->filename; } else if (valueO) { - throw ScriptError(_ERROR_2_("can't convert", valueO->typeName(), _TYPE_("symbol" ))); + throw ScriptErrorConversion(valueO->typeName(), _TYPE_("symbol" )); } else { filename = from_script(symbol); } diff --git a/src/script/functions/regex.cpp b/src/script/functions/regex.cpp index c282aa05..51534741 100644 --- a/src/script/functions/regex.cpp +++ b/src/script/functions/regex.cpp @@ -192,6 +192,38 @@ SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(break_text) { return ScriptValueP(); } +// ----------------------------------------------------------------------------- : Rules : regex split + +SCRIPT_FUNCTION_WITH_SIMPLIFY(split_text) { + SCRIPT_PARAM_C(String, input); + SCRIPT_PARAM_C(ScriptRegexP, match); + SCRIPT_PARAM_DEFAULT_N(bool, _("include empty"), include_empty, true); + ScriptCustomCollectionP ret(new ScriptCustomCollection); + // find all matches + while (match->regex.Matches(input)) { + // match, append to result + size_t start, len; + bool ok = match->regex.GetMatch(&start, &len, 0); + assert(ok); + if (include_empty || start > 0) { + ret->value.push_back(to_script(input.substr(0,start))); + } + input = input.substr(start + len); // everything after the match + } + if (include_empty || !input.empty()) { + ret->value.push_back(to_script(input)); + } + return ret; +} +SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(split_text) { + FOR_EACH(b, closure.bindings) { + if (b.first == SCRIPT_VAR_match) { + b.second = regex_from_script(b.second); // pre-compile + } + } + return ScriptValueP(); +} + // ----------------------------------------------------------------------------- : Rules : regex match SCRIPT_FUNCTION_WITH_SIMPLIFY(match) { @@ -214,6 +246,7 @@ void init_script_regex_functions(Context& ctx) { ctx.setVariable(_("replace"), script_replace); ctx.setVariable(_("filter text"), script_filter_text); ctx.setVariable(_("break text"), script_break_text); + ctx.setVariable(_("split text"), script_split_text); ctx.setVariable(_("match"), script_match); ctx.setVariable(_("replace rule"), new_intrusive1(script_replace)); ctx.setVariable(_("filter rule"), new_intrusive1(script_filter_text)); diff --git a/src/script/parser.cpp b/src/script/parser.cpp index b7b77044..598e5d13 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -625,7 +625,7 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) { void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) { size_t added = script.getInstructions().size(); // number of instructions added parseExpr(input, script, minPrec); // first argument - added -= script.getInstructions().size(); + added = script.getInstructions().size() - added; // read any operators after an expression // EBNF: expr = expr | expr oper expr // without left recursion: expr = expr (oper expr)* @@ -649,7 +649,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc // We made a mistake, the part before the := should be a variable name, // not an expression. Remove that instruction. Instruction& instr = script.getInstructions().back(); - if (added == 1 && instr.instr != I_GET_VAR) { + if (added != 1 || instr.instr != I_GET_VAR) { input.add_error(_("Can only assign to variables")); } script.getInstructions().pop_back(); @@ -695,9 +695,9 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc } else { input.expected(_("name")); } - } else if (minPrec <= PREC_FUN && token==_("[")) { // get member by expr + } else if (minPrec <= PREC_FUN && token==_("[") && !token.newline) { // get member by expr size_t before = script.getInstructions().size(); - parseOper(input, script, PREC_ALL); + parseOper(input, script, PREC_SET); if (script.getInstructions().size() == before + 1 && script.getInstructions().back().instr == I_PUSH_CONST) { // optimize: // PUSH_CONST x @@ -709,7 +709,7 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc script.addInstruction(I_BINARY, I_MEMBER); } expectToken(input, _("]"), &token); - } else if (minPrec <= PREC_FUN && token==_("(")) { + } else if (minPrec <= PREC_FUN && token==_("(") && !token.newline) { // function call, read arguments vector arguments; parseCallArguments(input, script, arguments); diff --git a/src/script/to_value.hpp b/src/script/to_value.hpp index b318bf29..d90d00b4 100644 --- a/src/script/to_value.hpp +++ b/src/script/to_value.hpp @@ -79,9 +79,12 @@ class ScriptDelayedError : public ScriptValue { ScriptError error; // the error message }; -inline ScriptValueP delayError(const String& m) { +inline ScriptValueP delay_error(const String& m) { return new_intrusive1(ScriptError(m)); } +inline ScriptValueP delay_error(const ScriptError& error) { + return new_intrusive1(error); +} // ----------------------------------------------------------------------------- : Iterators @@ -93,6 +96,7 @@ struct ScriptIterator : public ScriptValue { /// Return the next item for this iterator, or ScriptValueP() if there is no such item virtual ScriptValueP next(ScriptValueP* key_out = nullptr) = 0; + virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const; }; // make an iterator over a range @@ -161,7 +165,7 @@ ScriptValueP get_member(const map& m, const String& name) { if (it != m.end()) { return to_script(it->second); } else { - return delayError(_ERROR_2_("has no member", _TYPE_("collection"), name)); + return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name)); } } @@ -171,7 +175,7 @@ ScriptValueP get_member(const IndexMap& m, const String& name) { if (it != m.end()) { return to_script(*it); } else { - return delayError(_ERROR_2_("has no member", _TYPE_("collection"), name)); + return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name)); } } @@ -389,7 +393,7 @@ inline ScriptValueP to_script(const Defaultable& v) { return to_script(v()); template inline T from_script (const ScriptValueP& value) { ScriptObject* o = dynamic_cast*>(value.get()); if (!o) { - throw ScriptError(_ERROR_2_("can't convert", value->typeName(), _TYPE_("object" ))); + throw ScriptErrorConversion(value->typeName(), _TYPE_("object" )); } return o->getValue(); } diff --git a/src/script/value.cpp b/src/script/value.cpp index e135024f..6971ae4e 100644 --- a/src/script/value.cpp +++ b/src/script/value.cpp @@ -19,16 +19,16 @@ DECLARE_TYPEOF_COLLECTION(pair); // ----------------------------------------------------------------------------- : ScriptValue // Base cases -ScriptValue::operator String() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("string" ))); } -ScriptValue::operator int() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("integer" ))); } -ScriptValue::operator bool() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("boolean" ))); } -ScriptValue::operator double() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("double" ))); } -ScriptValue::operator AColor() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("color" ))); } -ScriptValueP ScriptValue::eval(Context&) const { return delayError(_ERROR_2_("can't convert", typeName(), _TYPE_("function"))); } +ScriptValue::operator String() const { throw ScriptErrorConversion(typeName(), _TYPE_("string" )); } +ScriptValue::operator int() const { throw ScriptErrorConversion(typeName(), _TYPE_("integer" )); } +ScriptValue::operator bool() const { throw ScriptErrorConversion(typeName(), _TYPE_("boolean" )); } +ScriptValue::operator double() const { throw ScriptErrorConversion(typeName(), _TYPE_("double" )); } +ScriptValue::operator AColor() const { throw ScriptErrorConversion(typeName(), _TYPE_("color" )); } +ScriptValueP ScriptValue::eval(Context&) const { return delay_error(ScriptErrorConversion(typeName(), _TYPE_("function"))); } ScriptValueP ScriptValue::next(ScriptValueP* key_out) { throw InternalError(_("Can't convert from ")+typeName()+_(" to iterator")); } -ScriptValueP ScriptValue::makeIterator(const ScriptValueP&) const { return delayError(_ERROR_2_("can't convert", typeName(), _TYPE_("collection"))); } -int ScriptValue::itemCount() const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("collection"))); } -GeneratedImageP ScriptValue::toImage(const ScriptValueP&) const { throw ScriptError(_ERROR_2_("can't convert", typeName(), _TYPE_("image" ))); } +ScriptValueP ScriptValue::makeIterator(const ScriptValueP&) const { return delay_error(ScriptErrorConversion(typeName(), _TYPE_("collection"))); } +int ScriptValue::itemCount() const { throw ScriptErrorConversion(typeName(), _TYPE_("collection")); } +GeneratedImageP ScriptValue::toImage(const ScriptValueP&) const { throw ScriptErrorConversion(typeName(), _TYPE_("image" )); } String ScriptValue::toCode() const { return *this; } CompareWhat ScriptValue::compareAs(String& compare_str, void const*& compare_ptr) const { compare_str = toCode(); @@ -39,11 +39,11 @@ ScriptValueP ScriptValue::getMember(const String& name) const { if (name.ToLong(&index)) { return getIndex(index); } else { - return delayError(_ERROR_2_("has no member", typeName(), name)); + return delay_error(ScriptErrorNoMember(typeName(), name)); } } ScriptValueP ScriptValue::getIndex(int index) const { - return delayError(_ERROR_2_("has no member", typeName(), String()<= 0 && (size_t)index < value.size()) { return to_script(String(1,value[index])); } else { - return delayError(_ERROR_2_("has no member value", value, name)); + return delay_error(_ERROR_2_("has no member value", value, name)); } } private: diff --git a/src/util/error.hpp b/src/util/error.hpp index ff05cb5f..9c9e93ab 100644 --- a/src/util/error.hpp +++ b/src/util/error.hpp @@ -108,6 +108,28 @@ class ScriptError : public Error { inline ScriptError(const String& str) : Error(str) {} }; +/// "Variable not set" +class ScriptErrorNoVariable : public ScriptError { + public: + inline ScriptErrorNoVariable(const String& var) : ScriptError(_("Variable not set: ") + var) {} +}; + +/// "Can't convert from A to B" +class ScriptErrorConversion : public ScriptError { + public: + inline ScriptErrorConversion(const String& a, const String& b) + : ScriptError(_ERROR_2_("can't convert", a, b)) {} + inline ScriptErrorConversion(const String& value, const String& a, const String& b) + : ScriptError(_ERROR_3_("can't convert value", value, a, b)) {} +}; + +/// "A has no member B" +class ScriptErrorNoMember : public ScriptError { + public: + inline ScriptErrorNoMember(const String& type, const String& member) + : ScriptError(_ERROR_2_("has no member", type, member)) {} +}; + // ----------------------------------------------------------------------------- : Error handling /// Should errors be written to stdout? diff --git a/src/util/tagged_string.cpp b/src/util/tagged_string.cpp index 112357db..d6f0373f 100644 --- a/src/util/tagged_string.cpp +++ b/src/util/tagged_string.cpp @@ -307,9 +307,12 @@ void cursor_to_index_range(const String& str, size_t cursor, size_t& start, size if (cur == cursor) start = i; } } - end = min(i, size); - if (cur < cursor) start = end = size; - if (end <= start) end = start + 1; + if (cur < cursor) { + start = end = size; + } else { + end = min(i, size); + } + end = max(end, start + 1); // always start < end, since there are always valid cursor positions } size_t cursor_to_index(const String& str, size_t cursor, Movement dir) {