Commit f7bb6944 authored by twanvl's avatar twanvl

Simplified compilation of 'assert' pseudo function;

Added remove_duplicates flag to sort_list function;
Fixed documentation of <size:> tag
parent 747579cd
......@@ -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);
handle_warning(input, true);
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,13 +78,17 @@ String format_input(const String& format, Context& ctx) {
SCRIPT_FUNCTION(to_string) {
ScriptValueP format = ctx.getVariable(SCRIPT_VAR_format);
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));
} else {
// simple conversion
SCRIPT_PARAM_C(String, input);
SCRIPT_RETURN(input);
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));
} else {
// simple conversion
SCRIPT_PARAM_C(String, input);
SCRIPT_RETURN(input);
}
} catch (const ScriptError& e) {
return new_intrusive1<ScriptDelayedError>(e);
}
}
......@@ -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);
......
......@@ -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.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.getConstants()[function_pos] = script_warning_if_neq;
script.getInstructions().pop_back(); // POP == instruction
script.addInstruction(I_PUSH_CONST, message); // push "condition"
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);
}
......
No preview for this file type
......@@ -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,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. */
......
......@@ -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;
......
......@@ -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) */
......
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