mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Simplified compilation of 'assert' pseudo function;
Added remove_duplicates flag to sort_list function; Fixed documentation of <size:> tag git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@1028 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -10,9 +10,12 @@ Optionally order by some other property.
|
||||
! Parameter Type Description
|
||||
| @input@ [[type:list]] or [[type:set]] List to sort.
|
||||
| @order_by@ [[type:function]] (optional) Function to order by, for example when @order_by: {input.name}@ orders items by their name property.
|
||||
| @remove_duplicates@ [[type:boolean]] DOC_MSE_VERSION: since 0.3.7
|
||||
If @true@, keep only one copy of duplicate elements.
|
||||
|
||||
--Examples--
|
||||
> sort_list([5,2,3,1,4]) == [1,2,3,4,5]
|
||||
> sort_list(["aaa","cccc","bb"]) == ["aaa","bb","cccc"]
|
||||
> sort_list(["aaa","cccc","bb"], order_by: {number_of_items(in:input)})
|
||||
> sort_list(["aaa","cccc","bb"], order_by: length)
|
||||
> == ["bb","aaa","cccc"]
|
||||
> sort_list([1,2,1,2,2,3], remove_duplicates:true) == [1,2,3]
|
||||
|
||||
@@ -16,7 +16,7 @@ This is written as the character with code 1 in files.
|
||||
| @"<i>"@ The text inside the tag is italic.
|
||||
| @"<sym>"@ The text inside the tag is rendered as symbols, if a [[prop:style:symbol font]] is set for the text box.
|
||||
| @"<color:???>"@ The text inside the tag is rendered with the given [[type:color]].
|
||||
| @"<size:???>"@ The text inside the tag is scaled by the a factor, for example @"<size:2>text</size>"@ makes the text twice as large.
|
||||
| @"<size:???>"@ The text inside the tag is rendered with the given font size in points, for example @"<size:12>text</size>"@ makes the text 12 points. The text is scaled down proportianally when it does not fit in a text box and the @scale down to@ attribute allows it.
|
||||
| @"<line>"@ Line breaks inside this tag use the [[prop:style:line height line]], and they show a horizontal line.
|
||||
| @"<soft-line>"@ Line breaks inside this tag use the [[prop:style:soft line height]].
|
||||
| @"<atom>"@ An atomic piece of text. The cursor can never be inside it; it is selected as a whole.
|
||||
|
||||
@@ -33,7 +33,26 @@ SCRIPT_FUNCTION(trace) {
|
||||
|
||||
SCRIPT_FUNCTION(warning) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_DEFAULT_C(bool, condition, true);
|
||||
if (condition) {
|
||||
handle_warning(input, true);
|
||||
}
|
||||
return script_nil;
|
||||
}
|
||||
SCRIPT_FUNCTION(warning_if_neq) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_N(ScriptValueP, SCRIPT_VAR__1, v1);
|
||||
SCRIPT_PARAM_N(ScriptValueP, SCRIPT_VAR__2, v2);
|
||||
if (!equal(v1,v2)) {
|
||||
String s1 = _("?"), s2 = _("?");
|
||||
try {
|
||||
s1 = v1->toCode();
|
||||
} catch (...) {}
|
||||
try {
|
||||
s2 = v2->toCode();
|
||||
} catch (...) {}
|
||||
handle_warning(input + s1 + _(" != ") + s2, true);
|
||||
}
|
||||
return script_nil;
|
||||
}
|
||||
|
||||
@@ -59,6 +78,7 @@ String format_input(const String& format, Context& ctx) {
|
||||
|
||||
SCRIPT_FUNCTION(to_string) {
|
||||
ScriptValueP format = ctx.getVariable(SCRIPT_VAR_format);
|
||||
try {
|
||||
if (format && format->type() == SCRIPT_STRING) {
|
||||
// format specifier. Be careful, the built in function 'format' has the same name
|
||||
SCRIPT_RETURN(format_input(*format, ctx));
|
||||
@@ -67,6 +87,9 @@ SCRIPT_FUNCTION(to_string) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
} catch (const ScriptError& e) {
|
||||
return new_intrusive1<ScriptDelayedError>(e);
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(to_int) {
|
||||
@@ -145,6 +168,11 @@ SCRIPT_FUNCTION(to_color) {
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(to_code) {
|
||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||
SCRIPT_RETURN(input->toCode());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Math
|
||||
|
||||
SCRIPT_FUNCTION(abs) {
|
||||
@@ -344,14 +372,20 @@ int position_in_vector(const ScriptValueP& of, const ScriptValueP& in, const Scr
|
||||
inline bool smart_less_first(const pair<String,ScriptValueP>& a, const pair<String,ScriptValueP>& b) {
|
||||
return smart_less(a.first, b.first);
|
||||
}
|
||||
inline bool smart_equal_first(const pair<String,ScriptValueP>& a, const pair<String,ScriptValueP>& b) {
|
||||
return smart_equal(a.first, b.first);
|
||||
}
|
||||
|
||||
// sort a script list
|
||||
ScriptValueP sort_script(Context& ctx, const ScriptValueP& list, ScriptValue& order_by) {
|
||||
ScriptValueP sort_script(Context& ctx, const ScriptValueP& list, ScriptValue& order_by, bool remove_duplicates) {
|
||||
ScriptType list_t = list->type();
|
||||
if (list_t == SCRIPT_STRING) {
|
||||
// sort a string
|
||||
String s = list->toString();
|
||||
sort(s.begin(), s.end());
|
||||
if (remove_duplicates) {
|
||||
s.erase( unique(s.begin(), s.end()), s.end() );
|
||||
}
|
||||
SCRIPT_RETURN(s);
|
||||
} else {
|
||||
// are we sorting a set?
|
||||
@@ -364,6 +398,10 @@ ScriptValueP sort_script(Context& ctx, const ScriptValueP& list, ScriptValue& or
|
||||
values.push_back(make_pair(order_by.eval(ctx)->toString(), v));
|
||||
}
|
||||
sort(values.begin(), values.end(), smart_less_first);
|
||||
// unique
|
||||
if (remove_duplicates) {
|
||||
values.erase( unique(values.begin(), values.end(), smart_equal_first), values.end() );
|
||||
}
|
||||
// return collection
|
||||
ScriptCustomCollectionP ret(new ScriptCustomCollection());
|
||||
FOR_EACH(v, values) {
|
||||
@@ -442,10 +480,12 @@ SCRIPT_FUNCTION(filter_list) {
|
||||
|
||||
SCRIPT_FUNCTION(sort_list) {
|
||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||
SCRIPT_PARAM_N(ScriptValueP, _("order by"), order_by);
|
||||
return sort_script(ctx, input, *order_by);
|
||||
SCRIPT_PARAM_DEFAULT_N(ScriptValueP, _("order by"), order_by, script_nil);
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("remove duplicates"), remove_duplicates, false);
|
||||
return sort_script(ctx, input, *order_by, remove_duplicates);
|
||||
}
|
||||
|
||||
|
||||
SCRIPT_FUNCTION(random_shuffle) {
|
||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||
// convert to CustomCollection
|
||||
@@ -571,6 +611,7 @@ void init_script_basic_functions(Context& ctx) {
|
||||
ctx.setVariable(_("to number"), script_to_number);
|
||||
ctx.setVariable(_("to boolean"), script_to_boolean);
|
||||
ctx.setVariable(_("to color"), script_to_color);
|
||||
ctx.setVariable(_("to code"), script_to_code);
|
||||
// math
|
||||
ctx.setVariable(_("abs"), script_abs);
|
||||
ctx.setVariable(_("random_real"), script_random_real);
|
||||
|
||||
+14
-28
@@ -23,6 +23,7 @@ DECLARE_TYPEOF_COLLECTION(Variable);
|
||||
String read_utf8_line(wxInputStream& input, bool eat_bom = true, bool until_eof = false);
|
||||
|
||||
extern ScriptValueP script_warning;
|
||||
extern ScriptValueP script_warning_if_neq;
|
||||
|
||||
// ----------------------------------------------------------------------------- : Tokenizing : class
|
||||
|
||||
@@ -557,45 +558,30 @@ void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
|
||||
expectToken(input, _("("));
|
||||
size_t start = input.peek().pos;
|
||||
int line = input.getLineNumber();
|
||||
size_t function_pos = script.getConstants().size();
|
||||
script.addInstruction(I_PUSH_CONST, script_warning);
|
||||
parseOper(input, script, PREC_ALL); // condition
|
||||
size_t end = input.peek().pos;
|
||||
String message = String::Format(_("Assertion failure on line %d:\n expected: "), line) + input.getSourceCode(start,end);
|
||||
expectToken(input, _(")"), &token);
|
||||
if (script.getInstructions().back().instr == I_BINARY && script.getInstructions().back().instr2 == I_EQ) {
|
||||
// compile "assert(x == y)" into "
|
||||
// compile "assert(x == y)" into
|
||||
// warning_if_neq("condition", _1: x, _2: y)
|
||||
message += _("\n found: ");
|
||||
script.getInstructions().pop_back(); // remove ==
|
||||
script.addInstruction(I_DUP, 1); // duplicate X
|
||||
script.addInstruction(I_DUP, 1); // duplicate Y
|
||||
script.addInstruction(I_BINARY, I_EQ); //
|
||||
unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else
|
||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
||||
unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end
|
||||
script.comeFrom(jmpElse); // lbl_else:
|
||||
script.addInstruction(I_PUSH_CONST, script_warning); // push warning
|
||||
script.getConstants()[function_pos] = script_warning_if_neq;
|
||||
script.getInstructions().pop_back(); // POP == instruction
|
||||
script.addInstruction(I_PUSH_CONST, message); // push "condition"
|
||||
script.addInstruction(I_DUP, 3); // duplicate X
|
||||
script.addInstruction(I_BINARY, I_ADD); // add
|
||||
script.addInstruction(I_PUSH_CONST, String(_(" != "))); // push " != "
|
||||
script.addInstruction(I_BINARY, I_ADD); // add
|
||||
script.addInstruction(I_DUP, 2); // duplicate Y
|
||||
script.addInstruction(I_BINARY, I_ADD); // add
|
||||
script.addInstruction(I_CALL, 1); // call
|
||||
script.addInstruction(I_CALL, 3); // call
|
||||
script.addInstruction(I_NOP, SCRIPT_VAR__1); // (_1:)
|
||||
script.addInstruction(I_NOP, SCRIPT_VAR__2); // (_2:)
|
||||
script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
|
||||
script.comeFrom(jmpEnd); // lbl_end:
|
||||
script.addInstruction(I_BINARY, I_POP); // pop Y_copy
|
||||
script.addInstruction(I_BINARY, I_POP); // pop X_copy
|
||||
} else {
|
||||
// compile into: if condition then nil else warning("condition")
|
||||
unsigned jmpElse = script.addInstruction(I_JUMP_IF_NOT); // jnz lbl_else
|
||||
script.addInstruction(I_PUSH_CONST, script_nil); // push nil
|
||||
unsigned jmpEnd = script.addInstruction(I_JUMP); // jump lbl_end
|
||||
script.comeFrom(jmpElse); // lbl_else:
|
||||
script.addInstruction(I_PUSH_CONST, script_warning); // push warning
|
||||
// compile into: warning("condition", condition: not condition)
|
||||
script.addInstruction(I_UNARY, I_NOT); // not
|
||||
script.addInstruction(I_PUSH_CONST, message); // push "condition"
|
||||
script.addInstruction(I_CALL, 1); // call
|
||||
script.addInstruction(I_CALL, 2); // call
|
||||
script.addInstruction(I_NOP, SCRIPT_VAR_condition); // (condition:)
|
||||
script.addInstruction(I_NOP, SCRIPT_VAR_input); // (input:)
|
||||
script.comeFrom(jmpEnd); // lbl_end:
|
||||
}
|
||||
} else {
|
||||
// variable
|
||||
|
||||
@@ -52,6 +52,8 @@ void init_script_variables() {
|
||||
#define VarN(X,name) if (SCRIPT_VAR_##X != string_to_variable(name)) assert(false);
|
||||
#define Var(X) VarN(X,_(#X))
|
||||
Var(input);
|
||||
Var(_1);
|
||||
Var(_2);
|
||||
Var(in);
|
||||
Var(match);
|
||||
Var(replace);
|
||||
@@ -73,6 +75,7 @@ void init_script_variables() {
|
||||
Var(card);
|
||||
Var(styling);
|
||||
Var(value);
|
||||
Var(condition);
|
||||
assert(variables.size() == SCRIPT_VAR_CUSTOM_FIRST);
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,8 @@ struct Instruction {
|
||||
// for faster lookup from code
|
||||
enum Variable
|
||||
{ SCRIPT_VAR_input
|
||||
, SCRIPT_VAR__1
|
||||
, SCRIPT_VAR__2
|
||||
, SCRIPT_VAR_in
|
||||
, SCRIPT_VAR_match
|
||||
, SCRIPT_VAR_replace
|
||||
@@ -132,6 +134,7 @@ enum Variable
|
||||
, SCRIPT_VAR_card
|
||||
, SCRIPT_VAR_styling
|
||||
, SCRIPT_VAR_value
|
||||
, SCRIPT_VAR_condition
|
||||
, SCRIPT_VAR_CUSTOM_FIRST // other variables start from here
|
||||
, SCRIPT_VAR_CUSTOM_LOTS = 0xFFFFFF // ensure that sizeof(Variable) is large enough
|
||||
};
|
||||
|
||||
@@ -95,6 +95,12 @@ ScriptValueP rangeIterator(int start, int end);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Collections
|
||||
|
||||
class ScriptCollectionBase : public ScriptValue {
|
||||
public:
|
||||
virtual ScriptType type() const { return SCRIPT_COLLECTION; }
|
||||
virtual String toCode() const;
|
||||
};
|
||||
|
||||
// Iterator over a collection
|
||||
template <typename Collection>
|
||||
class ScriptCollectionIterator : public ScriptIterator {
|
||||
@@ -114,10 +120,9 @@ class ScriptCollectionIterator : public ScriptIterator {
|
||||
|
||||
/// Script value containing a collection
|
||||
template <typename Collection>
|
||||
class ScriptCollection : public ScriptValue {
|
||||
class ScriptCollection : public ScriptCollectionBase {
|
||||
public:
|
||||
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 getIndex(int index) const {
|
||||
if (index >= 0 && index < (int)value->size()) {
|
||||
@@ -190,9 +195,8 @@ class ScriptMap : public ScriptValue {
|
||||
// ----------------------------------------------------------------------------- : Collections : from script
|
||||
|
||||
/// Script value containing a custom collection, returned from script functions
|
||||
class ScriptCustomCollection : public ScriptValue {
|
||||
class ScriptCustomCollection : public ScriptCollectionBase {
|
||||
public:
|
||||
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;
|
||||
|
||||
+27
-2
@@ -27,8 +27,9 @@ ScriptValueP ScriptValue::eval(Context&) const { return delay
|
||||
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"))); }
|
||||
String ScriptValue::toCode() const { return *this; }
|
||||
CompareWhat ScriptValue::compareAs(String& compare_str, void const*& compare_ptr) const {
|
||||
compare_str = toString();
|
||||
compare_str = toCode();
|
||||
return COMPARE_AS_STRING;
|
||||
}
|
||||
ScriptValueP ScriptValue::getMember(const String& name) const {
|
||||
@@ -322,12 +323,36 @@ class ScriptNil : public ScriptValue {
|
||||
virtual operator double() const { return 0.0; }
|
||||
virtual operator int() const { return 0; }
|
||||
virtual operator bool() const { return false; }
|
||||
virtual ScriptValueP eval(Context&) const { return script_nil; } // nil() == nil
|
||||
virtual ScriptValueP eval(Context& ctx) const {
|
||||
// nil(input) == input
|
||||
return ctx.getVariable(SCRIPT_VAR_input);
|
||||
}
|
||||
};
|
||||
|
||||
/// The preallocated nil value
|
||||
ScriptValueP script_nil(new ScriptNil);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Collection base
|
||||
|
||||
String ScriptCollectionBase::toCode() const {
|
||||
String ret = _("[");
|
||||
bool first = true;
|
||||
#ifdef USE_INTRUSIVE_PTR
|
||||
// we can just turn this into a ScriptValueP
|
||||
// TODO: remove thisP alltogether
|
||||
ScriptValueP it = makeIterator(ScriptValueP(const_cast<ScriptValue*>((ScriptValue*)this)));
|
||||
#else
|
||||
#error "makeIterator needs a ScriptValueP :("
|
||||
#endif
|
||||
while (ScriptValueP v = it->next()) {
|
||||
if (!first) ret += _(",");
|
||||
first = false;
|
||||
ret += v->toCode();
|
||||
}
|
||||
ret += _("]");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Custom collection
|
||||
|
||||
// Iterator over a custom collection
|
||||
|
||||
@@ -67,6 +67,9 @@ class ScriptValue : public IntrusivePtrBaseWithDelete {
|
||||
/// Convert this value to a color
|
||||
virtual operator AColor() const;
|
||||
|
||||
/// Script code to generate this value
|
||||
virtual String toCode() const;
|
||||
|
||||
/// Explicit overload to convert to a string
|
||||
/** This is sometimes necessary, because wxString has an int constructor,
|
||||
* which confuses gcc. */
|
||||
|
||||
+21
-14
@@ -276,7 +276,7 @@ Char decompose_char2(Char c) {
|
||||
}
|
||||
}
|
||||
|
||||
bool smart_less(const String& sa, const String& sb) {
|
||||
int smart_compare(const String& sa, const String& sb) {
|
||||
bool in_num = false; // are we inside a number?
|
||||
bool lt = false; // is sa less than sb?
|
||||
bool eq = true; // so far is everything equal?
|
||||
@@ -295,12 +295,12 @@ bool smart_less(const String& sa, const String& sb) {
|
||||
}
|
||||
} else if (in_num && da) {
|
||||
// comparing numbers, one is longer, therefore it is greater
|
||||
return false;
|
||||
return 1;
|
||||
} else if (in_num && db) {
|
||||
return true;
|
||||
return -1;
|
||||
} else if (in_num && !eq) {
|
||||
// two numbers of the same length, but not equal
|
||||
return lt;
|
||||
return lt ? -1 : 1;
|
||||
} else if (a != b) {
|
||||
// not a number
|
||||
eq = true; lt = false;
|
||||
@@ -310,26 +310,26 @@ bool smart_less(const String& sa, const String& sb) {
|
||||
// Decompose characters
|
||||
Char la2 = decompose_char2(a), lb2 = decompose_char2(b);
|
||||
// Compare
|
||||
if (la < lb) return true;
|
||||
if (la > lb) return false;
|
||||
if (la < lb) return -1;
|
||||
if (la > lb) return 1;
|
||||
// Remaining from decomposition
|
||||
if (la2 || lb2) {
|
||||
if (la2) a = la2;
|
||||
else {
|
||||
if (++pa >= na) return false;
|
||||
if (++pa >= na) return 1;
|
||||
a = sa.GetChar(pa);
|
||||
}
|
||||
if (lb2) b = lb2;
|
||||
else {
|
||||
if (++pb >= nb) return true;
|
||||
if (++pb >= nb) return -1;
|
||||
b = sb.GetChar(pb);
|
||||
}
|
||||
goto next; // don't move to the next character in both strings
|
||||
}
|
||||
} else {
|
||||
// control characters
|
||||
if (a < b) return true;
|
||||
else return false;
|
||||
if (a < b) return -1;
|
||||
else return 1;
|
||||
}
|
||||
}
|
||||
in_num = da && db;
|
||||
@@ -341,16 +341,23 @@ bool smart_less(const String& sa, const String& sb) {
|
||||
if (na - pa < nb - pb) {
|
||||
// number b continues?
|
||||
Char b = sb.GetChar(pb);
|
||||
if (isDigit(b) || eq) return true; // b is longer
|
||||
if (isDigit(b) || eq) return -1; // b is longer
|
||||
} else if (na - pa > nb - pb) {
|
||||
Char a = sa.GetChar(pa);
|
||||
if (isDigit(a) || eq) return false; // a is longer
|
||||
if (isDigit(a) || eq) return 1; // a is longer
|
||||
}
|
||||
return lt; // compare numbers
|
||||
return eq ? 0 : lt ? -1 : 1; // compare numbers
|
||||
} else {
|
||||
return na - pa < nb - pb; // outside number, shorter string comes first
|
||||
return na - pa == nb - pb ? 0
|
||||
: na - pa < nb - pb ? -1 : 1; // outside number, shorter string comes first
|
||||
}
|
||||
}
|
||||
bool smart_less(const String& sa, const String& sb) {
|
||||
return smart_compare(sa, sb) == -1;
|
||||
}
|
||||
bool smart_equal(const String& sa, const String& sb) {
|
||||
return smart_compare(sa, sb) == 0;
|
||||
}
|
||||
|
||||
bool starts_with(const String& str, const String& start) {
|
||||
if (str.size() < start.size()) return false;
|
||||
|
||||
+7
-1
@@ -162,12 +162,18 @@ String remove_shortcut(const String&);
|
||||
|
||||
// ----------------------------------------------------------------------------- : Comparing / finding
|
||||
|
||||
/// Compare two strings, is the first less than the first?
|
||||
/// Compare two strings
|
||||
/** Uses a smart comparison algorithm that understands numbers.
|
||||
* The comparison is case insensitive.
|
||||
* Doesn't handle leading zeros.
|
||||
*
|
||||
* Returns -1 if a < b, 0 if they are equal, and 1 if a > b
|
||||
*/
|
||||
int smart_compare(const String&, const String&);
|
||||
/// Compare two strings, is the first less than the first?
|
||||
bool smart_less(const String&, const String&);
|
||||
/// Compare two strings for equality
|
||||
bool smart_equal(const String&, const String&);
|
||||
|
||||
/// Return whether str starts with start
|
||||
/** starts_with(a,b) == is_substr(a,0,b) */
|
||||
|
||||
Reference in New Issue
Block a user