From 7cb2292d36c5ccf323604589a457b15745e73be1 Mon Sep 17 00:00:00 2001 From: twanvl Date: Wed, 9 Jul 2008 15:51:07 +0000 Subject: [PATCH] Added ^ power operator, Added abs, random_int, random_real, random_shuffle, random_select script functions. Made == comparison of doubles use a small epsilon, so things like 3/2 == 1.5 are actually true. git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1013 0fc631ac-6414-0410-93d0-97cfa31319b6 --- src/data/field/choice.cpp | 2 +- src/script/context.cpp | 9 ++- src/script/functions/basic.cpp | 105 +++++++++++++++++++++++++++++++-- src/script/functions/regex.cpp | 4 +- src/script/parser.cpp | 13 ++-- src/script/script.cpp | 2 + src/script/script.hpp | 3 + src/script/to_value.hpp | 12 ++-- src/script/value.cpp | 41 ++++++++++--- src/script/value.hpp | 2 + 10 files changed, 167 insertions(+), 26 deletions(-) diff --git a/src/data/field/choice.cpp b/src/data/field/choice.cpp index 29ca7100..cca39487 100644 --- a/src/data/field/choice.cpp +++ b/src/data/field/choice.cpp @@ -198,7 +198,7 @@ void ChoiceStyle::initImage() { // CALL 0 // PUSH_CONST nil // OR_ELSE - intrusive_ptr lookup(new ScriptCustomCollection()); + ScriptCustomCollectionP lookup(new ScriptCustomCollection()); FOR_EACH(ci, choice_images) { lookup->key_value[ci.first] = ci.second.getScriptP(); } diff --git a/src/script/context.cpp b/src/script/context.cpp index e0cc116e..1aeaea84 100644 --- a/src/script/context.cpp +++ b/src/script/context.cpp @@ -402,6 +402,13 @@ void instrBinary (BinaryInstructionType i, ScriptValueP& a, const ScriptValueP& a = to_script((int)*a % (int)*b); } break; + case I_POW: + if (at == SCRIPT_DOUBLE || bt == SCRIPT_DOUBLE) { + a = to_script(pow((double)*a, (double)*b)); + } else { + a = to_script(pow((int)*a, (int)*b)); + } + break; case I_AND: OPERATOR_B(&&); case I_OR: OPERATOR_B(||); case I_XOR: OPERATOR_B(!=); @@ -442,7 +449,7 @@ void instrQuaternary(QuaternaryInstructionType i, ScriptValueP& a, const ScriptV // ----------------------------------------------------------------------------- : Simple instructions : objects and closures void Context::makeObject(size_t n) { - intrusive_ptr ret(new ScriptCustomCollection()); + ScriptCustomCollectionP ret(new ScriptCustomCollection()); size_t begin = stack.size() - 2 * n; for (size_t i = 0 ; i < n ; ++i) { const ScriptValueP& key = stack[begin + 2 * i]; diff --git a/src/script/functions/basic.cpp b/src/script/functions/basic.cpp index 6d3ad41f..2a1df29a 100644 --- a/src/script/functions/basic.cpp +++ b/src/script/functions/basic.cpp @@ -89,6 +89,21 @@ SCRIPT_FUNCTION(to_real) { SCRIPT_RETURN(input); } +SCRIPT_FUNCTION(to_number) { + ScriptValueP input = ctx.getVariable(SCRIPT_VAR_input); + ScriptType t = input->type(); + if (t == SCRIPT_BOOL) { + SCRIPT_RETURN((bool)*input ? 1 : 0); + } else if (t == SCRIPT_COLOR) { + AColor c = (AColor)*input; + SCRIPT_RETURN( (c.Red() + c.Blue() + c.Green()) / 3 ); + } else if (t == SCRIPT_DOUBLE) { + SCRIPT_RETURN((double)*input); + } else { + SCRIPT_RETURN((int)*input); + } +} + SCRIPT_FUNCTION(to_boolean) { ScriptValueP input = ctx.getVariable(SCRIPT_VAR_input); ScriptType t = input->type(); @@ -106,6 +121,30 @@ SCRIPT_FUNCTION(to_color) { SCRIPT_RETURN(input); } +// ----------------------------------------------------------------------------- : Math + +SCRIPT_FUNCTION(abs) { + ScriptValueP input = ctx.getVariable(SCRIPT_VAR_input); + ScriptType t = input->type(); + if (t == SCRIPT_DOUBLE) { + SCRIPT_RETURN(fabs((double)*input)); + } else { + SCRIPT_RETURN(abs((int)*input)); + } +} + +SCRIPT_FUNCTION(random_real) { + SCRIPT_PARAM_DEFAULT_C(double, begin, 0.0); + SCRIPT_PARAM_DEFAULT_C(double, end, 1.0); + SCRIPT_RETURN( (double)rand() / RAND_MAX * (end - begin) + begin ); +} + +SCRIPT_FUNCTION(random_int) { + SCRIPT_PARAM_DEFAULT_C(int, begin, 0); + SCRIPT_PARAM_C( int, end); + SCRIPT_RETURN( rand() % (end - begin) + begin ); +} + // ----------------------------------------------------------------------------- : String stuff // convert a string to upper case @@ -142,8 +181,8 @@ SCRIPT_FUNCTION(trim) { // extract a substring SCRIPT_FUNCTION(substring) { SCRIPT_PARAM_C(String, input); - SCRIPT_PARAM_DEFAULT(int, begin, 0); - SCRIPT_PARAM_DEFAULT(int, end, INT_MAX); + SCRIPT_PARAM_DEFAULT_C(int, begin, 0); + SCRIPT_PARAM_DEFAULT_C(int, end, INT_MAX); if (begin < 0) begin = 0; if (end < 0) end = 0; if (begin >= end || (size_t)begin >= input.size()) { @@ -302,7 +341,7 @@ ScriptValueP sort_script(Context& ctx, const ScriptValueP& list, ScriptValue& or } sort(values.begin(), values.end(), smart_less_first); // return collection - intrusive_ptr ret(new ScriptCustomCollection()); + ScriptCustomCollectionP ret(new ScriptCustomCollection()); FOR_EACH(v, values) { ret->value.push_back(v.second); } @@ -365,7 +404,7 @@ SCRIPT_FUNCTION(filter_list) { SCRIPT_PARAM_C(ScriptValueP, input); SCRIPT_PARAM_C(ScriptValueP, filter); // filter a collection - intrusive_ptr ret(new ScriptCustomCollection()); + ScriptCustomCollectionP ret(new ScriptCustomCollection()); ScriptValueP it = input->makeIterator(input); while (ScriptValueP v = it->next()) { ctx.setVariable(SCRIPT_VAR_input, v); @@ -383,6 +422,57 @@ SCRIPT_FUNCTION(sort_list) { return sort_script(ctx, input, *order_by); } +SCRIPT_FUNCTION(random_shuffle) { + SCRIPT_PARAM_C(ScriptValueP, input); + // convert to CustomCollection + ScriptCustomCollectionP ret(new ScriptCustomCollection()); + ScriptValueP it = input->makeIterator(input); + while (ScriptValueP v = it->next()) { + ret->value.push_back(v); + } + // shuffle + random_shuffle(ret->value.begin(), ret->value.end()); + return ret; +} + +SCRIPT_FUNCTION(random_select) { + SCRIPT_PARAM_C(ScriptValueP, input); + SCRIPT_OPTIONAL_PARAM(int, count) { + // pick a single one + int itemCount = input->itemCount(); + if (itemCount == 0) { + throw ScriptError(String::Format(_("Can not select a random item from an empty collection"), count)); + } + return input->getIndex( rand() % itemCount ); + } else { + // pick many + SCRIPT_PARAM_DEFAULT_C(bool, replace, false); + ScriptCustomCollectionP ret(new ScriptCustomCollection); + int itemCount = input->itemCount(); + if (replace) { + if (itemCount == 0) { + throw ScriptError(String::Format(_("Can not select %d items from an empty collection"), count)); + } + for (int i = 0 ; i < count ; ++i) { + ret->value.push_back( input->getIndex( rand() % itemCount ) ); + } + } else { + if (count > itemCount) { + throw ScriptError(String::Format(_("Can not select %d items from a collection conaining only %d items"), count, input->itemCount())); + } + // transfer all to ret and shuffle + ScriptValueP it = input->makeIterator(input); + while (ScriptValueP v = it->next()) { + ret->value.push_back(v); + } + random_shuffle(ret->value.begin(), ret->value.end()); + // keep only the first 'count' + ret->value.resize(count); + } + return ret; + } +} + // ----------------------------------------------------------------------------- : Keywords @@ -454,8 +544,13 @@ void init_script_basic_functions(Context& ctx) { ctx.setVariable(_("to string"), script_to_string); ctx.setVariable(_("to int"), script_to_int); ctx.setVariable(_("to real"), script_to_real); + ctx.setVariable(_("to number"), script_to_number); ctx.setVariable(_("to boolean"), script_to_boolean); ctx.setVariable(_("to color"), script_to_color); + // math + ctx.setVariable(_("abs"), script_abs); + ctx.setVariable(_("random_real"), script_random_real); + ctx.setVariable(_("random_int"), script_random_int); // string ctx.setVariable(_("to upper"), script_to_upper); ctx.setVariable(_("to lower"), script_to_lower); @@ -482,6 +577,8 @@ void init_script_basic_functions(Context& ctx) { ctx.setVariable(_("number of items"), script_number_of_items); ctx.setVariable(_("filter list"), script_filter_list); ctx.setVariable(_("sort list"), script_sort_list); + ctx.setVariable(_("random shuffle"), script_random_shuffle); + ctx.setVariable(_("random select"), script_random_select); // keyword ctx.setVariable(_("expand keywords"), script_expand_keywords); ctx.setVariable(_("expand keywords rule"), new_intrusive1(script_expand_keywords)); diff --git a/src/script/functions/regex.cpp b/src/script/functions/regex.cpp index ace1a46c..16ca3d66 100644 --- a/src/script/functions/regex.cpp +++ b/src/script/functions/regex.cpp @@ -344,7 +344,7 @@ class ScriptBreakRule : public ScriptValue { virtual String typeName() const { return _("break_rule"); } virtual ScriptValueP eval(Context& ctx) const { SCRIPT_PARAM_C(String, input); - intrusive_ptr ret(new ScriptCustomCollection); + ScriptCustomCollectionP ret(new ScriptCustomCollection); while (regex.Matches(input)) { // match, append to result size_t start, len; @@ -393,7 +393,7 @@ SCRIPT_FUNCTION_WITH_SIMPLIFY(break_text) { SCRIPT_PARAM_C(String, input); SCRIPT_PARAM_C(ScriptRegexP, match); SCRIPT_OPTIONAL_PARAM_C_(ScriptRegexP, in_context); - intrusive_ptr ret(new ScriptCustomCollection); + ScriptCustomCollectionP ret(new ScriptCustomCollection); // find all matches while (match->regex.Matches(input)) { // match, append to result diff --git a/src/script/parser.cpp b/src/script/parser.cpp index f1aa97e6..af19c339 100644 --- a/src/script/parser.cpp +++ b/src/script/parser.cpp @@ -119,8 +119,7 @@ class TokenIterator { bool isAlpha_(Char c) { return isAlpha(c) || c==_('_'); } bool isAlnum_(Char c) { return isAlnum(c) || c==_('_'); } -bool isOper (Char c) { return c==_('+') || c==_('-') || c==_('*') || c==_('/') || c==_('!') || c==_('.') || c==_('@') || - c==_(':') || c==_('=') || c==_('<') || c==_('>') || c==_(';') || c==_(','); } +bool isOper (Char c) { return wxStrchr(_("+-*/!.@%^&:=<>;,"),c) != nullptr; } bool isLparen(Char c) { return c==_('(') || c==_('[') || c==_('{'); } bool isRparen(Char c) { return c==_(')') || c==_(']') || c==_('}'); } bool isDigitOrDot(Char c) { return isDigit(c) || c==_('.'); } @@ -347,6 +346,7 @@ enum Precedence , PREC_CMP // == != < > <= >= , PREC_ADD // + - , PREC_MUL // * / mod +, PREC_POW // ^ (right associative) , PREC_UNARY // - not (unary operators) , PREC_FUN // [] () . (function call, member) , PREC_STRING // +{ }+ (smart string operators) @@ -680,10 +680,11 @@ void parseOper(TokenIterator& input, Script& script, Precedence minPrec, Instruc else if (minPrec <= PREC_CMP && token==_(">=")) parseOper(input, script, PREC_ADD, I_BINARY, I_GE); else if (minPrec <= PREC_ADD && token==_("+")) parseOper(input, script, PREC_MUL, I_BINARY, I_ADD); else if (minPrec <= PREC_ADD && token==_("-")) parseOper(input, script, PREC_MUL, I_BINARY, I_SUB); - else if (minPrec <= PREC_MUL && token==_("*")) parseOper(input, script, PREC_UNARY, I_BINARY, I_MUL); - else if (minPrec <= PREC_MUL && token==_("/")) parseOper(input, script, PREC_UNARY, I_BINARY, I_FDIV); - else if (minPrec <= PREC_MUL && token==_("div")) parseOper(input, script, PREC_UNARY, I_BINARY, I_DIV); - else if (minPrec <= PREC_MUL && token==_("mod")) parseOper(input, script, PREC_UNARY, I_BINARY, I_MOD); + else if (minPrec <= PREC_MUL && token==_("*")) parseOper(input, script, PREC_POW, I_BINARY, I_MUL); + else if (minPrec <= PREC_MUL && token==_("/")) parseOper(input, script, PREC_POW, I_BINARY, I_FDIV); + else if (minPrec <= PREC_MUL && token==_("div")) parseOper(input, script, PREC_POW, I_BINARY, I_DIV); + else if (minPrec <= PREC_MUL && token==_("mod")) parseOper(input, script, PREC_POW, I_BINARY, I_MOD); + else if (minPrec <= PREC_POW && token==_("^")) parseOper(input, script, PREC_POW, I_BINARY, I_POW); else if (minPrec <= PREC_FUN && token==_(".")) { // get member by name const Token& token = input.read(); if (token == TOK_NAME || token == TOK_INT || token == TOK_DOUBLE || token == TOK_STRING) { diff --git a/src/script/script.cpp b/src/script/script.cpp index b44ca848..c22c49d1 100644 --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -58,6 +58,8 @@ void init_script_variables() { VarN(in_context,_("in context")); Var(recursive); Var(order); + Var(begin); + Var(end); Var(filter); Var(choice); Var(choices); diff --git a/src/script/script.hpp b/src/script/script.hpp index 293eb7f4..77ef22cf 100644 --- a/src/script/script.hpp +++ b/src/script/script.hpp @@ -63,6 +63,7 @@ enum BinaryInstructionType , I_FDIV ///< floating point division , I_DIV ///< integer division , I_MOD ///< modulus +, I_POW ///< power // Logical , I_AND ///< logical and , I_OR ///< logical or @@ -116,6 +117,8 @@ enum Variable , SCRIPT_VAR_in_context , SCRIPT_VAR_recursive , SCRIPT_VAR_order +, SCRIPT_VAR_begin +, SCRIPT_VAR_end , SCRIPT_VAR_filter , SCRIPT_VAR_choice , SCRIPT_VAR_choices diff --git a/src/script/to_value.hpp b/src/script/to_value.hpp index 5d2b9a02..f9275705 100644 --- a/src/script/to_value.hpp +++ b/src/script/to_value.hpp @@ -119,12 +119,11 @@ class ScriptCollection : public ScriptValue { inline ScriptCollection(const Collection* v) : value(v) {} virtual ScriptType type() const { return SCRIPT_COLLECTION; } virtual String typeName() const { return _TYPE_1_("collection of", type_name(*value->begin())); } - virtual ScriptValueP getMember(const String& name) const { - long index; - if (name.ToLong(&index) && index >= 0 && (size_t)index < value->size()) { + virtual ScriptValueP getIndex(int index) const { + if (index >= 0 && index < (int)value->size()) { return to_script(value->at(index)); } else { - return ScriptValue::getMember(name); + return ScriptValue::getIndex(index); } } virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const { @@ -196,6 +195,7 @@ class ScriptCustomCollection : public ScriptValue { virtual ScriptType type() const { return SCRIPT_COLLECTION; } virtual String typeName() const { return _TYPE_("collection"); } virtual ScriptValueP getMember(const String& name) const; + virtual ScriptValueP getIndex(int index) const; virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const; virtual int itemCount() const { return (int)value.size(); } /// Collections can be compared by comparing pointers @@ -210,6 +210,8 @@ class ScriptCustomCollection : public ScriptValue { map key_value; }; +DECLARE_POINTER_TYPE(ScriptCustomCollection); + // ----------------------------------------------------------------------------- : Collections : concatenation /// Script value containing the concatenation of two collections @@ -219,6 +221,7 @@ class ScriptConcatCollection : public ScriptValue { virtual ScriptType type() const { return SCRIPT_COLLECTION; } virtual String typeName() const { return _TYPE_("collection"); } virtual ScriptValueP getMember(const String& name) const; + virtual ScriptValueP getIndex(int index) const; virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const; virtual int itemCount() const { return a->itemCount() + b->itemCount(); } /// Collections can be compared by comparing pointers @@ -260,6 +263,7 @@ class ScriptObject : public ScriptValue { } } } + virtual ScriptValueP getIndex(int index) const { ScriptValueP d = getDefault(); return d ? d->getIndex(index) : ScriptValue::getIndex(index); } virtual ScriptValueP dependencyMember(const String& name, const Dependency& dep) const { mark_dependency_member(*value, name, dep); return getMember(name); diff --git a/src/script/value.cpp b/src/script/value.cpp index 54e3a3a4..49c61804 100644 --- a/src/script/value.cpp +++ b/src/script/value.cpp @@ -24,7 +24,6 @@ ScriptValue::operator bool() const { throw Script 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"))); } -ScriptValueP ScriptValue::getMember(const String& name) const { return delayError(_ERROR_2_("has no member", typeName(), name)); } ScriptValueP ScriptValue::next() { 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"))); } @@ -32,12 +31,28 @@ CompareWhat ScriptValue::compareAs(String& compare_str, void const*& compare_pt compare_str = toString(); return COMPARE_AS_STRING; } +ScriptValueP ScriptValue::getMember(const String& name) const { + long index; + if (name.ToLong(&index)) { + return getIndex(index); + } else { + return delayError(_ERROR_2_("has no member", typeName(), name)); + } +} +ScriptValueP ScriptValue::getIndex(int index) const { + return delayError(_ERROR_2_("has no member", typeName(), String()<itemCount() != b->itemCount()) return false; @@ -338,12 +353,14 @@ ScriptValueP ScriptCustomCollection::getMember(const String& name) const { if (it != key_value.end()) { return it->second; } else { - long index; - if (name.ToLong(&index) && index >= 0 && (size_t)index < value.size()) { - return value.at(index); - } else { - return ScriptValue::getMember(name); - } + return ScriptValue::getMember(name); + } +} +ScriptValueP ScriptCustomCollection::getIndex(int index) const { + if (index >= 0 && (size_t)index < value.size()) { + return value.at(index); + } else { + return ScriptValue::getIndex(index); } } ScriptValueP ScriptCustomCollection::makeIterator(const ScriptValueP& thisP) const { @@ -381,6 +398,14 @@ ScriptValueP ScriptConcatCollection::getMember(const String& name) const { return b->getMember(name); } } +ScriptValueP ScriptConcatCollection::getIndex(int index) const { + int itemsInA = a->itemCount(); + if (index < itemsInA) { + return a->getIndex(index); + } else { + return b->getIndex(index - itemsInA); + } +} ScriptValueP ScriptConcatCollection::makeIterator(const ScriptValueP& thisP) const { return new_intrusive2(a->makeIterator(a), b->makeIterator(b)); } diff --git a/src/script/value.hpp b/src/script/value.hpp index e69ef128..d1420ff7 100644 --- a/src/script/value.hpp +++ b/src/script/value.hpp @@ -96,6 +96,8 @@ class ScriptValue : public IntrusivePtrBaseWithDelete { virtual ScriptValueP next(); /// Return the number of items in this value (assuming it is a collection) virtual int itemCount() const; + /// Get a member at the given index + virtual ScriptValueP getIndex(int index) const; }; extern ScriptValueP script_nil; ///< The preallocated nil value