mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-11 13:17:00 -04:00
Change tabs to two spaces.
This commit is contained in:
+474
-474
File diff suppressed because it is too large
Load Diff
+99
-99
@@ -20,16 +20,16 @@ class Dependency;
|
||||
template <typename K, typename V>
|
||||
class VectorIntMap {
|
||||
public:
|
||||
inline V& operator [] (K key) {
|
||||
if (values.size() <= key) {
|
||||
values.resize(key + 1);
|
||||
}
|
||||
return values[key];
|
||||
}
|
||||
/// Get access to the vector
|
||||
inline const vector<V>& get() const { return values; }
|
||||
inline V& operator [] (K key) {
|
||||
if (values.size() <= key) {
|
||||
values.resize(key + 1);
|
||||
}
|
||||
return values[key];
|
||||
}
|
||||
/// Get access to the vector
|
||||
inline const vector<V>& get() const { return values; }
|
||||
private:
|
||||
vector<V> values;
|
||||
vector<V> values;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Context
|
||||
@@ -37,105 +37,105 @@ class VectorIntMap {
|
||||
/// Context for script evaluation
|
||||
class Context {
|
||||
public:
|
||||
Context();
|
||||
|
||||
/// Evaluate a script inside this context.
|
||||
/** This function is safely reentrant.
|
||||
* @param openScope if false, variables set in this eval call will leak out.
|
||||
*/
|
||||
ScriptValueP eval(const Script& script, bool openScope = true);
|
||||
|
||||
/// Analyze the dependencies of a script
|
||||
/** All things the script depends on are marked with signalDependent(dep).
|
||||
* The return value of this function should be ignored
|
||||
*/
|
||||
ScriptValueP dependencies(const Dependency& dep, const Script& script);
|
||||
|
||||
/// Set a variable to a new value (in the current scope)
|
||||
void setVariable(const String& name, const ScriptValueP& value);
|
||||
/// Set a variable to a new value (in the current scope)
|
||||
void setVariable(Variable name, const ScriptValueP& value);
|
||||
|
||||
/// Get the value of a variable, throws if it not set
|
||||
ScriptValueP getVariable(const String& name);
|
||||
/// Get the value of a variable, returns ScriptValue() if it is not set
|
||||
ScriptValueP getVariableOpt(const String& name);
|
||||
/// Get the value of a variable, throws if it not set
|
||||
ScriptValueP getVariable(Variable var);
|
||||
/// Get the value of a variable, returns ScriptValue() if it is not set
|
||||
inline ScriptValueP getVariableOpt(Variable var) { return variables[var].value; }
|
||||
/// Get the value of a variable only if it was set in the current scope, returns ScriptValue() if it is not set
|
||||
ScriptValueP getVariableInScopeOpt(Variable var);
|
||||
/// In what scope was the variable set?
|
||||
/** Returns 0 for the current scope and >0 for outer scopes.
|
||||
* Returns -1 if the varible is not set
|
||||
*/
|
||||
int getVariableScope(Variable var);
|
||||
|
||||
/// Make a closure of the function with the direct parameters of the current call
|
||||
ScriptValueP makeClosure(const ScriptValueP& fun);
|
||||
|
||||
Context();
|
||||
|
||||
/// Evaluate a script inside this context.
|
||||
/** This function is safely reentrant.
|
||||
* @param openScope if false, variables set in this eval call will leak out.
|
||||
*/
|
||||
ScriptValueP eval(const Script& script, bool openScope = true);
|
||||
|
||||
/// Analyze the dependencies of a script
|
||||
/** All things the script depends on are marked with signalDependent(dep).
|
||||
* The return value of this function should be ignored
|
||||
*/
|
||||
ScriptValueP dependencies(const Dependency& dep, const Script& script);
|
||||
|
||||
/// Set a variable to a new value (in the current scope)
|
||||
void setVariable(const String& name, const ScriptValueP& value);
|
||||
/// Set a variable to a new value (in the current scope)
|
||||
void setVariable(Variable name, const ScriptValueP& value);
|
||||
|
||||
/// Get the value of a variable, throws if it not set
|
||||
ScriptValueP getVariable(const String& name);
|
||||
/// Get the value of a variable, returns ScriptValue() if it is not set
|
||||
ScriptValueP getVariableOpt(const String& name);
|
||||
/// Get the value of a variable, throws if it not set
|
||||
ScriptValueP getVariable(Variable var);
|
||||
/// Get the value of a variable, returns ScriptValue() if it is not set
|
||||
inline ScriptValueP getVariableOpt(Variable var) { return variables[var].value; }
|
||||
/// Get the value of a variable only if it was set in the current scope, returns ScriptValue() if it is not set
|
||||
ScriptValueP getVariableInScopeOpt(Variable var);
|
||||
/// In what scope was the variable set?
|
||||
/** Returns 0 for the current scope and >0 for outer scopes.
|
||||
* Returns -1 if the varible is not set
|
||||
*/
|
||||
int getVariableScope(Variable var);
|
||||
|
||||
/// Make a closure of the function with the direct parameters of the current call
|
||||
ScriptValueP makeClosure(const ScriptValueP& fun);
|
||||
|
||||
public:
|
||||
|
||||
/// Open a new scope
|
||||
/** returns the number of shadowed binding before that scope */
|
||||
size_t openScope();
|
||||
/// Close a scope, must be passed a value from openScope
|
||||
void closeScope(size_t scope);
|
||||
friend class LocalScope;
|
||||
|
||||
|
||||
/// Open a new scope
|
||||
/** returns the number of shadowed binding before that scope */
|
||||
size_t openScope();
|
||||
/// Close a scope, must be passed a value from openScope
|
||||
void closeScope(size_t scope);
|
||||
friend class LocalScope;
|
||||
|
||||
public:// public for FOR_EACH
|
||||
/// Record of a variable
|
||||
struct VariableValue {
|
||||
VariableValue() : level(0) {}
|
||||
unsigned int level; ///< Scope level on which this variable was set
|
||||
ScriptValueP value; ///< Value of this variable
|
||||
};
|
||||
/// Record of a variable binding that is being shadowed (overwritten) by another binding
|
||||
struct Binding {
|
||||
Variable variable; ///< Name of the overwritten variable.
|
||||
VariableValue value; ///< Old value of that variable.
|
||||
};
|
||||
/// Record of a variable
|
||||
struct VariableValue {
|
||||
VariableValue() : level(0) {}
|
||||
unsigned int level; ///< Scope level on which this variable was set
|
||||
ScriptValueP value; ///< Value of this variable
|
||||
};
|
||||
/// Record of a variable binding that is being shadowed (overwritten) by another binding
|
||||
struct Binding {
|
||||
Variable variable; ///< Name of the overwritten variable.
|
||||
VariableValue value; ///< Old value of that variable.
|
||||
};
|
||||
private:
|
||||
/// Variables, indexed by integer name (using string_to_variable)
|
||||
VectorIntMap<unsigned int, VariableValue> variables;
|
||||
/// Shadowed variable bindings
|
||||
vector<Binding> shadowed;
|
||||
/// Number of scopes opened
|
||||
unsigned int level;
|
||||
/// Stack of values
|
||||
vector<ScriptValueP> stack;
|
||||
#ifdef _DEBUG
|
||||
/// The opened scopes, for sanity checking
|
||||
vector<size_t> scopes;
|
||||
#endif
|
||||
|
||||
// utility types for dependency analysis
|
||||
struct Jump;
|
||||
struct JumpOrder;
|
||||
|
||||
/// Return the bindings in the current scope
|
||||
void getBindings(size_t scope, vector<Binding>&);
|
||||
/// Remove all bindings made in the current scope
|
||||
void resetBindings(size_t scope);
|
||||
/// Make an object with n elements, popping 2n values from the stack, and push it onto the stack
|
||||
void makeObject(size_t n);
|
||||
/// Make a closure with n arguments
|
||||
void makeClosure(size_t n, const Instruction*& instr);
|
||||
|
||||
/// Get a variable name givin its value, returns (Variable)-1 if not found (slow!)
|
||||
Variable lookupVariableValue(const ScriptValueP& value);
|
||||
friend class ScriptCompose;
|
||||
/// Variables, indexed by integer name (using string_to_variable)
|
||||
VectorIntMap<unsigned int, VariableValue> variables;
|
||||
/// Shadowed variable bindings
|
||||
vector<Binding> shadowed;
|
||||
/// Number of scopes opened
|
||||
unsigned int level;
|
||||
/// Stack of values
|
||||
vector<ScriptValueP> stack;
|
||||
#ifdef _DEBUG
|
||||
/// The opened scopes, for sanity checking
|
||||
vector<size_t> scopes;
|
||||
#endif
|
||||
|
||||
// utility types for dependency analysis
|
||||
struct Jump;
|
||||
struct JumpOrder;
|
||||
|
||||
/// Return the bindings in the current scope
|
||||
void getBindings(size_t scope, vector<Binding>&);
|
||||
/// Remove all bindings made in the current scope
|
||||
void resetBindings(size_t scope);
|
||||
/// Make an object with n elements, popping 2n values from the stack, and push it onto the stack
|
||||
void makeObject(size_t n);
|
||||
/// Make a closure with n arguments
|
||||
void makeClosure(size_t n, const Instruction*& instr);
|
||||
|
||||
/// Get a variable name givin its value, returns (Variable)-1 if not found (slow!)
|
||||
Variable lookupVariableValue(const ScriptValueP& value);
|
||||
friend class ScriptCompose;
|
||||
};
|
||||
|
||||
/// A class that creates a local scope
|
||||
class LocalScope {
|
||||
public:
|
||||
inline LocalScope(Context& ctx) : ctx(ctx), scope(ctx.openScope()) {}
|
||||
inline ~LocalScope() { ctx.closeScope(scope); }
|
||||
inline LocalScope(Context& ctx) : ctx(ctx), scope(ctx.openScope()) {}
|
||||
inline ~LocalScope() { ctx.closeScope(scope); }
|
||||
private:
|
||||
Context& ctx;
|
||||
size_t scope;
|
||||
Context& ctx;
|
||||
size_t scope;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+327
-327
@@ -26,10 +26,10 @@ extern ScriptValueP dependency_dummy;
|
||||
// it simply supresses all error messages.
|
||||
class DependencyDummy : public ScriptIterator {
|
||||
public:
|
||||
virtual ScriptType type() const { return SCRIPT_DUMMY; }
|
||||
virtual String typeName() const { return _("dummy"); }
|
||||
virtual ScriptValueP next(ScriptValueP*) { return ScriptValueP(); }
|
||||
virtual ScriptValueP dependencyName(const ScriptValue&, const Dependency&) const { return dependency_dummy; }
|
||||
virtual ScriptType type() const { return SCRIPT_DUMMY; }
|
||||
virtual String typeName() const { return _("dummy"); }
|
||||
virtual ScriptValueP next(ScriptValueP*) { return ScriptValueP(); }
|
||||
virtual ScriptValueP dependencyName(const ScriptValue&, const Dependency&) const { return dependency_dummy; }
|
||||
};
|
||||
|
||||
ScriptValueP dependency_dummy(new DependencyDummy);
|
||||
@@ -42,361 +42,361 @@ ScriptValueP unified(const ScriptValueP& a, const ScriptValueP& b);
|
||||
*/
|
||||
class DependencyUnion : public ScriptValue {
|
||||
public:
|
||||
DependencyUnion(const ScriptValueP& a, const ScriptValueP& b)
|
||||
: a(a), b(b)
|
||||
{}
|
||||
|
||||
virtual ScriptType type() const { return SCRIPT_DUMMY; }
|
||||
virtual String typeName() const { return _("union of ") + a->typeName() + _(" and ") + b->typeName(); }
|
||||
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency& dep) const {
|
||||
return unified( a->dependencies(ctx,dep), b->dependencies(ctx,dep));
|
||||
}
|
||||
virtual ScriptValueP makeIterator(ScriptValueP thisP) const {
|
||||
return unified(a->makeIterator(thisP), b->makeIterator(thisP));
|
||||
}
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency& dep) const {
|
||||
return unified(a->dependencyMember(name,dep), b->dependencyMember(name,dep));
|
||||
}
|
||||
virtual ScriptValueP dependencyName(const ScriptValue& container, const Dependency& dep) const {
|
||||
return unified(a->dependencyName(container,dep), b->dependencyName(container,dep));
|
||||
}
|
||||
DependencyUnion(const ScriptValueP& a, const ScriptValueP& b)
|
||||
: a(a), b(b)
|
||||
{}
|
||||
|
||||
virtual ScriptType type() const { return SCRIPT_DUMMY; }
|
||||
virtual String typeName() const { return _("union of ") + a->typeName() + _(" and ") + b->typeName(); }
|
||||
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency& dep) const {
|
||||
return unified( a->dependencies(ctx,dep), b->dependencies(ctx,dep));
|
||||
}
|
||||
virtual ScriptValueP makeIterator(ScriptValueP thisP) const {
|
||||
return unified(a->makeIterator(thisP), b->makeIterator(thisP));
|
||||
}
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency& dep) const {
|
||||
return unified(a->dependencyMember(name,dep), b->dependencyMember(name,dep));
|
||||
}
|
||||
virtual ScriptValueP dependencyName(const ScriptValue& container, const Dependency& dep) const {
|
||||
return unified(a->dependencyName(container,dep), b->dependencyName(container,dep));
|
||||
}
|
||||
private:
|
||||
ScriptValueP a, b;
|
||||
ScriptValueP a, b;
|
||||
};
|
||||
|
||||
// Unify two values from different execution paths
|
||||
void unify(ScriptValueP& a, const ScriptValueP& b) {
|
||||
assert(a && b);
|
||||
if (a != b) a = intrusive(new DependencyUnion(a,b));
|
||||
assert(a && b);
|
||||
if (a != b) a = intrusive(new DependencyUnion(a,b));
|
||||
}
|
||||
// Unify two values from different execution paths
|
||||
ScriptValueP unified(const ScriptValueP& a, const ScriptValueP& b) {
|
||||
assert(a && b);
|
||||
if (a == b) return a;
|
||||
else return intrusive(new DependencyUnion(a,b));
|
||||
assert(a && b);
|
||||
if (a == b) return a;
|
||||
else return intrusive(new DependencyUnion(a,b));
|
||||
}
|
||||
|
||||
/// Behaves like script_nil, but with a name
|
||||
class ScriptMissingVariable : public ScriptValue {
|
||||
public:
|
||||
ScriptMissingVariable(const String& name) : name(name) {}
|
||||
virtual ScriptType type() const { return SCRIPT_NIL; }
|
||||
virtual String typeName() const { return _("missing variable '") + name + _("'"); }
|
||||
virtual operator String() const { return wxEmptyString; }
|
||||
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
|
||||
ScriptMissingVariable(const String& name) : name(name) {}
|
||||
virtual ScriptType type() const { return SCRIPT_NIL; }
|
||||
virtual String typeName() const { return _("missing variable '") + name + _("'"); }
|
||||
virtual operator String() const { return wxEmptyString; }
|
||||
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
|
||||
private:
|
||||
String name; ///< Name of the variable
|
||||
String name; ///< Name of the variable
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Jump record
|
||||
|
||||
// Utility class: a jump that has been postponed
|
||||
struct Context::Jump {
|
||||
const Instruction* target; ///< Target of the jump
|
||||
vector<ScriptValueP> stack_top; ///< The top part of the stack, everything local to the current call
|
||||
vector<Binding> bindings; ///< The bindings made up to this point in the current scope
|
||||
const Instruction* target; ///< Target of the jump
|
||||
vector<ScriptValueP> stack_top; ///< The top part of the stack, everything local to the current call
|
||||
vector<Binding> bindings; ///< The bindings made up to this point in the current scope
|
||||
};
|
||||
// an ordering on jumps by their target, lowest target = highest priority
|
||||
struct Context::JumpOrder {
|
||||
inline bool operator () (Jump* a, Jump* b) {
|
||||
return a->target > b->target;
|
||||
}
|
||||
inline bool operator () (Jump* a, Jump* b) {
|
||||
return a->target > b->target;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Dependency analysis
|
||||
|
||||
ScriptValueP Context::dependencies(const Dependency& dep, const Script& script) {
|
||||
// Dependency analysis proceeds in the same way as normal evaluation.
|
||||
// Operator calls will be replaced by "push dummy", we don't care about values.
|
||||
// Only the operators left are:
|
||||
// - member operator; and it signals a dependency.
|
||||
// - looper construction
|
||||
// - + for function composition
|
||||
// Variable assignments are performed as normall.
|
||||
// Jumps are tricky:
|
||||
// - I_LOOP: We want to prevent infinite loops, the solution is that after the first
|
||||
// iteration we set the looper to a dummy value, so the loop is only executed once.
|
||||
// TODO: This could result in false negatives when iterating over things like fields.
|
||||
// We ignore this, because loops are usually only used for exporting, where dependency
|
||||
// analysis is not used anyway.
|
||||
// - I_JUMP_IF_NOT: We don't know the value of the condition, so we must evaluate both branches.
|
||||
// The simple solution would be to use recursion to fork off one of the cases.
|
||||
// This could result in an exponential increase in execution time,
|
||||
// because the analysis after an if statement is duplicated.
|
||||
// A better solution is to evalutate branches 'in parallel'.
|
||||
// We create a jump record for taking the branch, and evaluate the fall through case.
|
||||
// When later a jump record points to the current instruction the stack and variables of that
|
||||
// record are unify with the current execution path.
|
||||
// - I_JUMP: We must can not follow all jumps, because they may lead to a point beyond a jump record,
|
||||
// we can then no longer hope to unify with that jump record.
|
||||
// Instead we create a new jump record, and follow the jump record with the lowest target address.
|
||||
// This story doesn't hold for backwards jumps, we can safely follow those (see I_LOOP above)
|
||||
|
||||
// Scope for evaluating this script.
|
||||
size_t stack_size = stack.size();
|
||||
size_t scope = openScope();
|
||||
|
||||
// Forward jumps waiting to be performed, by order of target (descending)
|
||||
priority_queue<Jump*,vector<Jump*>,JumpOrder> jumps;
|
||||
|
||||
try {
|
||||
// Instruction pointer
|
||||
const Instruction* instr = &script.instructions[0];
|
||||
|
||||
// Loop until we are done
|
||||
while (true) {
|
||||
// Is there a jump going here?
|
||||
// If so, unify with current execution path
|
||||
while (!jumps.empty() && jumps.top()->target == instr) {
|
||||
// unify with current execution path
|
||||
Jump* j = jumps.top(); jumps.pop();
|
||||
// unify stack
|
||||
assert(stack_size + j->stack_top.size() == stack.size());
|
||||
for (size_t i = 0; i < j->stack_top.size() ; ++i) {
|
||||
unify(stack[stack_size + i], j->stack_top[i]);
|
||||
}
|
||||
// unify bindings
|
||||
FOR_EACH(v, j->bindings) {
|
||||
ScriptValueP old_value = variables[v.variable].value;
|
||||
if (old_value) {
|
||||
setVariable(v.variable, unified(old_value, v.value.value) );
|
||||
}
|
||||
}
|
||||
delete j;
|
||||
}
|
||||
|
||||
if (instr >= &script.instructions[0] + script.instructions.size()) break; // end of script
|
||||
|
||||
// Analyze the current instruction
|
||||
Instruction i = *instr++;
|
||||
switch (i.instr) {
|
||||
case I_NOP: break;
|
||||
// Push a constant (as normal)
|
||||
case I_PUSH_CONST: {
|
||||
stack.push_back(script.constants[i.data]);
|
||||
break;
|
||||
}
|
||||
// Jump
|
||||
case I_JUMP: {
|
||||
if ( &script.instructions[0] + i.data >= instr) {
|
||||
// forward jump
|
||||
// create jump record
|
||||
Jump* jump = new Jump;
|
||||
jump->target = &script.instructions[0] + i.data;
|
||||
jump->stack_top.assign(stack.begin() + stack_size, stack.end());
|
||||
getBindings(scope, jump->bindings);
|
||||
jumps.push(jump);
|
||||
// clear scope
|
||||
stack.resize(stack_size);
|
||||
resetBindings(scope);
|
||||
// we don't follow this jump just yet, there may be jumps that point to earlier positions
|
||||
Jump* jumpTo = jumps.top(); jumps.pop();
|
||||
instr = jumpTo->target;
|
||||
FOR_EACH(s, jumpTo->stack_top) stack.push_back(s);
|
||||
FOR_EACH(b, jumpTo->bindings) setVariable(b.variable, b.value.value);
|
||||
delete jumpTo;
|
||||
} else {
|
||||
// backward jump: just follow it, someone else (I_LOOP) will make sure
|
||||
// we don't go into an infinite loop
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Conditional jump
|
||||
case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR: {
|
||||
if (i.instr == I_JUMP_IF_NOT) {
|
||||
stack.pop_back(); // pop condition
|
||||
}
|
||||
// create jump record
|
||||
Jump* jump = new Jump;
|
||||
jump->target = &script.instructions[0] + i.data; // note: operator[] triggers assertion (in msvc>=9) failure if i.data==instructions.size()
|
||||
assert(jump->target >= instr); // jumps must be forward
|
||||
jump->stack_top.assign(stack.begin() + stack_size, stack.end());
|
||||
getBindings(scope, jump->bindings);
|
||||
jumps.push(jump);
|
||||
// just fall through for the case that the condition holds
|
||||
if (i.instr != I_JUMP_IF_NOT) {
|
||||
stack.pop_back(); // pop condition afterwards, so it is not poped when jump is taken
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Get an object member (almost as normal)
|
||||
case I_MEMBER_C: {
|
||||
String name = *script.constants[i.data];
|
||||
stack.back() = stack.back()->dependencyMember(name, dep); // dependency on member
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next value or jump (almost as normal)
|
||||
case I_LOOP: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP val = it->next();
|
||||
if (val) {
|
||||
// we have not been through the body
|
||||
it = dependency_dummy; // invalidate iterator, so we loop only once
|
||||
stack.push_back(val);
|
||||
} else {
|
||||
// we have been through the body once already
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next value or jump (almost as normal)
|
||||
case I_LOOP_WITH_KEY: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP key;
|
||||
ScriptValueP val = it->next(&key);
|
||||
if (val) {
|
||||
it = dependency_dummy; // invalidate iterator, so we loop only once
|
||||
stack.push_back(val);
|
||||
stack.push_back(key);
|
||||
} else {
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make an object
|
||||
case I_MAKE_OBJECT: {
|
||||
makeObject(i.data);
|
||||
break;
|
||||
}
|
||||
|
||||
// Function call (as normal)
|
||||
// Don't optimize tail calls; we may not jump as we normally do
|
||||
case I_CALL: case I_TAILCALL: {
|
||||
// open a new scope
|
||||
LocalScope new_scope(*this);
|
||||
// prepare arguments
|
||||
for (unsigned int j = 0 ; j < i.data ; ++j) {
|
||||
setVariable((Variable)instr[i.data - j - 1].data, stack.back());
|
||||
stack.pop_back();
|
||||
}
|
||||
instr += i.data; // skip arguments, there had better not be any jumps into the argument list
|
||||
// get function and call
|
||||
stack.back() = stack.back()->dependencies(*this, dep);
|
||||
break;
|
||||
}
|
||||
|
||||
// Closure object (as normal)
|
||||
case I_CLOSURE: {
|
||||
makeClosure(i.data, instr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get a variable (almost as normal)
|
||||
case I_GET_VAR: {
|
||||
ScriptValueP value = variables[i.data].value;
|
||||
if (!value) {
|
||||
value = intrusive(new ScriptMissingVariable(variable_to_string((Variable)i.data))); // no errors here
|
||||
}
|
||||
value->dependencyThis(dep);
|
||||
stack.push_back(value);
|
||||
break;
|
||||
}
|
||||
// Set a variable (as normal)
|
||||
case I_SET_VAR: {
|
||||
setVariable((Variable)i.data, stack.back());
|
||||
break;
|
||||
}
|
||||
|
||||
// Simple instruction: unary
|
||||
case I_UNARY: {
|
||||
ScriptValueP& a = stack.back();
|
||||
switch (i.instr1) {
|
||||
case I_ITERATOR_C:
|
||||
a = a->makeIterator(a); // as normal
|
||||
break;
|
||||
default:
|
||||
a = dependency_dummy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Simple instruction: binary
|
||||
case I_BINARY: {
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
switch (i.instr2) {
|
||||
case I_ITERATOR_R:
|
||||
a = rangeIterator(0,0); // values don't matter
|
||||
break;
|
||||
case I_MEMBER: {
|
||||
a = b->dependencyName(*a, dep); // dependency on member
|
||||
break;
|
||||
} case I_ADD:
|
||||
unify(a, b); // may be function composition
|
||||
break;
|
||||
default:
|
||||
a = dependency_dummy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Simple instruction: ternary
|
||||
case I_TERNARY: {
|
||||
ScriptValueP c = stack.back(); stack.pop_back();
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
a = dependency_dummy;
|
||||
break;
|
||||
}
|
||||
// Simple instruction: quaternary
|
||||
case I_QUATERNARY: {
|
||||
ScriptValueP d = stack.back(); stack.pop_back();
|
||||
ScriptValueP c = stack.back(); stack.pop_back();
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
a = dependency_dummy;
|
||||
break;
|
||||
}
|
||||
// Duplicate stack
|
||||
case I_DUP: {
|
||||
stack.push_back(stack.at(stack.size() - i.data - 1));
|
||||
break;
|
||||
}
|
||||
// Pop value off stack
|
||||
case I_POP: {
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function return (as normal)
|
||||
closeScope(scope);
|
||||
ScriptValueP result = stack.back();
|
||||
stack.pop_back();
|
||||
assert(stack.size() == stack_size); // we end up with the same stack
|
||||
assert(jumps.empty()); // no open jump records
|
||||
return result;
|
||||
|
||||
} catch (...) {
|
||||
// cleanup after an exception
|
||||
// the only place where exceptions should be possible is in someValue->getMember
|
||||
if (scope) closeScope(scope); // restore scope
|
||||
stack.resize(stack_size); // restore stack
|
||||
// delete jump records
|
||||
while (!jumps.empty()) {
|
||||
delete jumps.top();
|
||||
jumps.pop();
|
||||
}
|
||||
throw; // rethrow
|
||||
}
|
||||
// Dependency analysis proceeds in the same way as normal evaluation.
|
||||
// Operator calls will be replaced by "push dummy", we don't care about values.
|
||||
// Only the operators left are:
|
||||
// - member operator; and it signals a dependency.
|
||||
// - looper construction
|
||||
// - + for function composition
|
||||
// Variable assignments are performed as normall.
|
||||
// Jumps are tricky:
|
||||
// - I_LOOP: We want to prevent infinite loops, the solution is that after the first
|
||||
// iteration we set the looper to a dummy value, so the loop is only executed once.
|
||||
// TODO: This could result in false negatives when iterating over things like fields.
|
||||
// We ignore this, because loops are usually only used for exporting, where dependency
|
||||
// analysis is not used anyway.
|
||||
// - I_JUMP_IF_NOT: We don't know the value of the condition, so we must evaluate both branches.
|
||||
// The simple solution would be to use recursion to fork off one of the cases.
|
||||
// This could result in an exponential increase in execution time,
|
||||
// because the analysis after an if statement is duplicated.
|
||||
// A better solution is to evalutate branches 'in parallel'.
|
||||
// We create a jump record for taking the branch, and evaluate the fall through case.
|
||||
// When later a jump record points to the current instruction the stack and variables of that
|
||||
// record are unify with the current execution path.
|
||||
// - I_JUMP: We must can not follow all jumps, because they may lead to a point beyond a jump record,
|
||||
// we can then no longer hope to unify with that jump record.
|
||||
// Instead we create a new jump record, and follow the jump record with the lowest target address.
|
||||
// This story doesn't hold for backwards jumps, we can safely follow those (see I_LOOP above)
|
||||
|
||||
// Scope for evaluating this script.
|
||||
size_t stack_size = stack.size();
|
||||
size_t scope = openScope();
|
||||
|
||||
// Forward jumps waiting to be performed, by order of target (descending)
|
||||
priority_queue<Jump*,vector<Jump*>,JumpOrder> jumps;
|
||||
|
||||
try {
|
||||
// Instruction pointer
|
||||
const Instruction* instr = &script.instructions[0];
|
||||
|
||||
// Loop until we are done
|
||||
while (true) {
|
||||
// Is there a jump going here?
|
||||
// If so, unify with current execution path
|
||||
while (!jumps.empty() && jumps.top()->target == instr) {
|
||||
// unify with current execution path
|
||||
Jump* j = jumps.top(); jumps.pop();
|
||||
// unify stack
|
||||
assert(stack_size + j->stack_top.size() == stack.size());
|
||||
for (size_t i = 0; i < j->stack_top.size() ; ++i) {
|
||||
unify(stack[stack_size + i], j->stack_top[i]);
|
||||
}
|
||||
// unify bindings
|
||||
FOR_EACH(v, j->bindings) {
|
||||
ScriptValueP old_value = variables[v.variable].value;
|
||||
if (old_value) {
|
||||
setVariable(v.variable, unified(old_value, v.value.value) );
|
||||
}
|
||||
}
|
||||
delete j;
|
||||
}
|
||||
|
||||
if (instr >= &script.instructions[0] + script.instructions.size()) break; // end of script
|
||||
|
||||
// Analyze the current instruction
|
||||
Instruction i = *instr++;
|
||||
switch (i.instr) {
|
||||
case I_NOP: break;
|
||||
// Push a constant (as normal)
|
||||
case I_PUSH_CONST: {
|
||||
stack.push_back(script.constants[i.data]);
|
||||
break;
|
||||
}
|
||||
// Jump
|
||||
case I_JUMP: {
|
||||
if ( &script.instructions[0] + i.data >= instr) {
|
||||
// forward jump
|
||||
// create jump record
|
||||
Jump* jump = new Jump;
|
||||
jump->target = &script.instructions[0] + i.data;
|
||||
jump->stack_top.assign(stack.begin() + stack_size, stack.end());
|
||||
getBindings(scope, jump->bindings);
|
||||
jumps.push(jump);
|
||||
// clear scope
|
||||
stack.resize(stack_size);
|
||||
resetBindings(scope);
|
||||
// we don't follow this jump just yet, there may be jumps that point to earlier positions
|
||||
Jump* jumpTo = jumps.top(); jumps.pop();
|
||||
instr = jumpTo->target;
|
||||
FOR_EACH(s, jumpTo->stack_top) stack.push_back(s);
|
||||
FOR_EACH(b, jumpTo->bindings) setVariable(b.variable, b.value.value);
|
||||
delete jumpTo;
|
||||
} else {
|
||||
// backward jump: just follow it, someone else (I_LOOP) will make sure
|
||||
// we don't go into an infinite loop
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Conditional jump
|
||||
case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR: {
|
||||
if (i.instr == I_JUMP_IF_NOT) {
|
||||
stack.pop_back(); // pop condition
|
||||
}
|
||||
// create jump record
|
||||
Jump* jump = new Jump;
|
||||
jump->target = &script.instructions[0] + i.data; // note: operator[] triggers assertion (in msvc>=9) failure if i.data==instructions.size()
|
||||
assert(jump->target >= instr); // jumps must be forward
|
||||
jump->stack_top.assign(stack.begin() + stack_size, stack.end());
|
||||
getBindings(scope, jump->bindings);
|
||||
jumps.push(jump);
|
||||
// just fall through for the case that the condition holds
|
||||
if (i.instr != I_JUMP_IF_NOT) {
|
||||
stack.pop_back(); // pop condition afterwards, so it is not poped when jump is taken
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Get an object member (almost as normal)
|
||||
case I_MEMBER_C: {
|
||||
String name = *script.constants[i.data];
|
||||
stack.back() = stack.back()->dependencyMember(name, dep); // dependency on member
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next value or jump (almost as normal)
|
||||
case I_LOOP: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP val = it->next();
|
||||
if (val) {
|
||||
// we have not been through the body
|
||||
it = dependency_dummy; // invalidate iterator, so we loop only once
|
||||
stack.push_back(val);
|
||||
} else {
|
||||
// we have been through the body once already
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Loop over a container, push next value or jump (almost as normal)
|
||||
case I_LOOP_WITH_KEY: {
|
||||
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||
ScriptValueP key;
|
||||
ScriptValueP val = it->next(&key);
|
||||
if (val) {
|
||||
it = dependency_dummy; // invalidate iterator, so we loop only once
|
||||
stack.push_back(val);
|
||||
stack.push_back(key);
|
||||
} else {
|
||||
stack.erase(stack.end() - 2); // remove iterator
|
||||
instr = &script.instructions[i.data];
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Make an object
|
||||
case I_MAKE_OBJECT: {
|
||||
makeObject(i.data);
|
||||
break;
|
||||
}
|
||||
|
||||
// Function call (as normal)
|
||||
// Don't optimize tail calls; we may not jump as we normally do
|
||||
case I_CALL: case I_TAILCALL: {
|
||||
// open a new scope
|
||||
LocalScope new_scope(*this);
|
||||
// prepare arguments
|
||||
for (unsigned int j = 0 ; j < i.data ; ++j) {
|
||||
setVariable((Variable)instr[i.data - j - 1].data, stack.back());
|
||||
stack.pop_back();
|
||||
}
|
||||
instr += i.data; // skip arguments, there had better not be any jumps into the argument list
|
||||
// get function and call
|
||||
stack.back() = stack.back()->dependencies(*this, dep);
|
||||
break;
|
||||
}
|
||||
|
||||
// Closure object (as normal)
|
||||
case I_CLOSURE: {
|
||||
makeClosure(i.data, instr);
|
||||
break;
|
||||
}
|
||||
|
||||
// Get a variable (almost as normal)
|
||||
case I_GET_VAR: {
|
||||
ScriptValueP value = variables[i.data].value;
|
||||
if (!value) {
|
||||
value = intrusive(new ScriptMissingVariable(variable_to_string((Variable)i.data))); // no errors here
|
||||
}
|
||||
value->dependencyThis(dep);
|
||||
stack.push_back(value);
|
||||
break;
|
||||
}
|
||||
// Set a variable (as normal)
|
||||
case I_SET_VAR: {
|
||||
setVariable((Variable)i.data, stack.back());
|
||||
break;
|
||||
}
|
||||
|
||||
// Simple instruction: unary
|
||||
case I_UNARY: {
|
||||
ScriptValueP& a = stack.back();
|
||||
switch (i.instr1) {
|
||||
case I_ITERATOR_C:
|
||||
a = a->makeIterator(a); // as normal
|
||||
break;
|
||||
default:
|
||||
a = dependency_dummy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Simple instruction: binary
|
||||
case I_BINARY: {
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
switch (i.instr2) {
|
||||
case I_ITERATOR_R:
|
||||
a = rangeIterator(0,0); // values don't matter
|
||||
break;
|
||||
case I_MEMBER: {
|
||||
a = b->dependencyName(*a, dep); // dependency on member
|
||||
break;
|
||||
} case I_ADD:
|
||||
unify(a, b); // may be function composition
|
||||
break;
|
||||
default:
|
||||
a = dependency_dummy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Simple instruction: ternary
|
||||
case I_TERNARY: {
|
||||
ScriptValueP c = stack.back(); stack.pop_back();
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
a = dependency_dummy;
|
||||
break;
|
||||
}
|
||||
// Simple instruction: quaternary
|
||||
case I_QUATERNARY: {
|
||||
ScriptValueP d = stack.back(); stack.pop_back();
|
||||
ScriptValueP c = stack.back(); stack.pop_back();
|
||||
ScriptValueP b = stack.back(); stack.pop_back();
|
||||
ScriptValueP& a = stack.back();
|
||||
a = dependency_dummy;
|
||||
break;
|
||||
}
|
||||
// Duplicate stack
|
||||
case I_DUP: {
|
||||
stack.push_back(stack.at(stack.size() - i.data - 1));
|
||||
break;
|
||||
}
|
||||
// Pop value off stack
|
||||
case I_POP: {
|
||||
stack.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function return (as normal)
|
||||
closeScope(scope);
|
||||
ScriptValueP result = stack.back();
|
||||
stack.pop_back();
|
||||
assert(stack.size() == stack_size); // we end up with the same stack
|
||||
assert(jumps.empty()); // no open jump records
|
||||
return result;
|
||||
|
||||
} catch (...) {
|
||||
// cleanup after an exception
|
||||
// the only place where exceptions should be possible is in someValue->getMember
|
||||
if (scope) closeScope(scope); // restore scope
|
||||
stack.resize(stack_size); // restore stack
|
||||
// delete jump records
|
||||
while (!jumps.empty()) {
|
||||
delete jumps.top();
|
||||
jumps.pop();
|
||||
}
|
||||
throw; // rethrow
|
||||
}
|
||||
}
|
||||
|
||||
void Context::getBindings(size_t scope, vector<Binding>& bindings) {
|
||||
for (size_t i = scope + 1 ; i < shadowed.size() ; ++i) {
|
||||
Binding b = {shadowed[i].variable, variables[shadowed[i].variable]};
|
||||
bindings.push_back(b);
|
||||
}
|
||||
for (size_t i = scope + 1 ; i < shadowed.size() ; ++i) {
|
||||
Binding b = {shadowed[i].variable, variables[shadowed[i].variable]};
|
||||
bindings.push_back(b);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::resetBindings(size_t scope) {
|
||||
// close and re-open the scope
|
||||
closeScope(scope);
|
||||
size_t same_scope = openScope();
|
||||
assert(scope == same_scope);
|
||||
// close and re-open the scope
|
||||
closeScope(scope);
|
||||
size_t same_scope = openScope();
|
||||
assert(scope == same_scope);
|
||||
}
|
||||
|
||||
+33
-33
@@ -15,36 +15,36 @@
|
||||
|
||||
/// Types of dependencies
|
||||
enum DependencyType
|
||||
{ DEP_CARD_FIELD ///< dependency of a script in a "card" field
|
||||
, DEP_CARDS_FIELD ///< dependency of a script in a "card" field for all cards
|
||||
, DEP_SET_FIELD ///< dependency of a script in a "set" field
|
||||
, DEP_CARD_STYLE ///< dependency of a script in a "style" property, data gives the stylesheet
|
||||
, DEP_EXTRA_CARD_FIELD ///< dependency of a script in an extra stylesheet specific card field
|
||||
, DEP_CARD_COPY_DEP ///< copy the dependencies from a card field
|
||||
, DEP_SET_COPY_DEP ///< copy the dependencies from a set field
|
||||
, DEP_DUMMY ///< used for other purposes, index and data can be anything
|
||||
// in particular, this is used for determining /if/ there are dependencies
|
||||
{ DEP_CARD_FIELD ///< dependency of a script in a "card" field
|
||||
, DEP_CARDS_FIELD ///< dependency of a script in a "card" field for all cards
|
||||
, DEP_SET_FIELD ///< dependency of a script in a "set" field
|
||||
, DEP_CARD_STYLE ///< dependency of a script in a "style" property, data gives the stylesheet
|
||||
, DEP_EXTRA_CARD_FIELD ///< dependency of a script in an extra stylesheet specific card field
|
||||
, DEP_CARD_COPY_DEP ///< copy the dependencies from a card field
|
||||
, DEP_SET_COPY_DEP ///< copy the dependencies from a set field
|
||||
, DEP_DUMMY ///< used for other purposes, index and data can be anything
|
||||
// in particular, this is used for determining /if/ there are dependencies
|
||||
};
|
||||
|
||||
/// A 'pointer' to some script that depends on another script
|
||||
class Dependency {
|
||||
public:
|
||||
inline Dependency(DependencyType type, size_t index, void* data = nullptr)
|
||||
: type(type), index(index), data(data)
|
||||
{}
|
||||
|
||||
DependencyType type : 5; ///< Type of the dependent script
|
||||
size_t index : 27; ///< index into an IndexMap
|
||||
void* data; ///< Extra pointer data
|
||||
|
||||
/// This dependency, but dependent on all cards instead of just one
|
||||
inline Dependency makeCardIndependend() const {
|
||||
return Dependency(type == DEP_CARD_FIELD ? DEP_CARDS_FIELD : type, index, data);
|
||||
}
|
||||
|
||||
inline bool operator == (const Dependency& d) const {
|
||||
return type == d.type && index == d.index && data == d.data;
|
||||
}
|
||||
inline Dependency(DependencyType type, size_t index, void* data = nullptr)
|
||||
: type(type), index(index), data(data)
|
||||
{}
|
||||
|
||||
DependencyType type : 5; ///< Type of the dependent script
|
||||
size_t index : 27; ///< index into an IndexMap
|
||||
void* data; ///< Extra pointer data
|
||||
|
||||
/// This dependency, but dependent on all cards instead of just one
|
||||
inline Dependency makeCardIndependend() const {
|
||||
return Dependency(type == DEP_CARD_FIELD ? DEP_CARDS_FIELD : type, index, data);
|
||||
}
|
||||
|
||||
inline bool operator == (const Dependency& d) const {
|
||||
return type == d.type && index == d.index && data == d.data;
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Dependencies
|
||||
@@ -52,15 +52,15 @@ class Dependency {
|
||||
/// A list of dependencies
|
||||
class Dependencies : public vector<Dependency> {
|
||||
public:
|
||||
/// Add a dependency, prevents duplicates
|
||||
inline void add(const Dependency& d) {
|
||||
if (d.type == DEP_DUMMY) return;
|
||||
if (find(begin(),end(),d) == end()) {
|
||||
push_back(d);
|
||||
}
|
||||
}
|
||||
/// Add a dependency, prevents duplicates
|
||||
inline void add(const Dependency& d) {
|
||||
if (d.type == DEP_DUMMY) return;
|
||||
if (find(begin(),end(),d) == end()) {
|
||||
push_back(d);
|
||||
}
|
||||
}
|
||||
private:
|
||||
using vector<Dependency>::push_back;
|
||||
using vector<Dependency>::push_back;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+537
-537
File diff suppressed because it is too large
Load Diff
@@ -21,39 +21,39 @@
|
||||
// ----------------------------------------------------------------------------- : new_card
|
||||
|
||||
SCRIPT_FUNCTION(new_card) {
|
||||
SCRIPT_PARAM(GameP, game);
|
||||
CardP new_card = intrusive(new Card(*game));
|
||||
// set field values
|
||||
SCRIPT_PARAM(ScriptValueP, input);
|
||||
ScriptValueP it = input->makeIterator(input);
|
||||
ScriptValueP key;
|
||||
while (ScriptValueP v = it->next(&key)) {
|
||||
assert(key);
|
||||
String name = key->toString();
|
||||
// find value to update
|
||||
IndexMap<FieldP,ValueP>::const_iterator value_it = new_card->data.find(name);
|
||||
if (value_it == new_card->data.end()) {
|
||||
throw ScriptError(format_string(_("Card doesn't have a field named '%s'"),name));
|
||||
}
|
||||
Value* value = value_it->get();
|
||||
// set the value
|
||||
if (TextValue* tvalue = dynamic_cast<TextValue*>(value)) {
|
||||
tvalue->value = v->toString();
|
||||
} else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(value)) {
|
||||
cvalue->value = v->toString();
|
||||
} else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(value)) {
|
||||
pvalue->package_name = v->toString();
|
||||
} else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(value)) {
|
||||
cvalue->value = (AColor)*v;
|
||||
} else {
|
||||
throw ScriptError(format_string(_("Can not set value '%s', it is not of the right type"),name));
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(new_card);
|
||||
SCRIPT_PARAM(GameP, game);
|
||||
CardP new_card = intrusive(new Card(*game));
|
||||
// set field values
|
||||
SCRIPT_PARAM(ScriptValueP, input);
|
||||
ScriptValueP it = input->makeIterator(input);
|
||||
ScriptValueP key;
|
||||
while (ScriptValueP v = it->next(&key)) {
|
||||
assert(key);
|
||||
String name = key->toString();
|
||||
// find value to update
|
||||
IndexMap<FieldP,ValueP>::const_iterator value_it = new_card->data.find(name);
|
||||
if (value_it == new_card->data.end()) {
|
||||
throw ScriptError(format_string(_("Card doesn't have a field named '%s'"),name));
|
||||
}
|
||||
Value* value = value_it->get();
|
||||
// set the value
|
||||
if (TextValue* tvalue = dynamic_cast<TextValue*>(value)) {
|
||||
tvalue->value = v->toString();
|
||||
} else if (ChoiceValue* cvalue = dynamic_cast<ChoiceValue*>(value)) {
|
||||
cvalue->value = v->toString();
|
||||
} else if (PackageChoiceValue* pvalue = dynamic_cast<PackageChoiceValue*>(value)) {
|
||||
pvalue->package_name = v->toString();
|
||||
} else if (ColorValue* cvalue = dynamic_cast<ColorValue*>(value)) {
|
||||
cvalue->value = (AColor)*v;
|
||||
} else {
|
||||
throw ScriptError(format_string(_("Can not set value '%s', it is not of the right type"),name));
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(new_card);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_construction_functions(Context& ctx) {
|
||||
ctx.setVariable(_("new card"), script_new_card);
|
||||
ctx.setVariable(_("new card"), script_new_card);
|
||||
}
|
||||
|
||||
+311
-311
@@ -32,219 +32,219 @@ DECLARE_TYPEOF_COLLECTION(ChoiceField::ChoiceP);
|
||||
//
|
||||
|
||||
SCRIPT_FUNCTION_WITH_DEP(combined_editor) {
|
||||
// read 'field#' arguments
|
||||
vector<TextValue*> values;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("field"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(ValueP, name, value) {
|
||||
TextValue* text_value = dynamic_cast<TextValue*>(value.get());
|
||||
if (!text_value) throw ScriptError(_("Argument '")+name+_("' should be a text field"));
|
||||
values.push_back(text_value);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
if (values.empty()) {
|
||||
throw ScriptError(_("No fields specified for combined_editor"));
|
||||
}
|
||||
// read 'separator#' arguments
|
||||
vector<String> separators;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("separator"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, name, separator) {
|
||||
separators.push_back(separator);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
if (separators.size() < values.size() - 1) {
|
||||
throw ScriptError(String::Format(_("Not enough separators for combine_editor, expected %d"), values.size()-1));
|
||||
}
|
||||
// the value
|
||||
SCRIPT_PARAM_C(String, value);
|
||||
// remove suffix/prefix
|
||||
SCRIPT_OPTIONAL_PARAM_(String, prefix);
|
||||
SCRIPT_OPTIONAL_PARAM_(String, suffix);
|
||||
if (is_substr(value,0,_("<prefix"))) {
|
||||
value = value.substr(min(value.size(), match_close_tag_end(value, 0)));
|
||||
}
|
||||
size_t pos = value.rfind(_("<suffix"));
|
||||
if (pos != String::npos && match_close_tag_end(value,pos) >= value.size()) {
|
||||
value = value.substr(0, pos);
|
||||
}
|
||||
// split the value
|
||||
vector<pair<String,bool> > value_parts; // (value part, is empty)
|
||||
pos = value.find(_("<sep"));
|
||||
while (pos != String::npos) {
|
||||
String part = value.substr(0, pos);
|
||||
value_parts.push_back(make_pair(part, false));
|
||||
value = value.substr(min(match_close_tag_end(value,pos), value.size()));
|
||||
pos = value.find(_("<sep"));
|
||||
}
|
||||
value_parts.push_back(make_pair(value, false));
|
||||
value_parts.resize(values.size()); // TODO: what if there are more value_parts than values?
|
||||
// update the values if our input value is newer?
|
||||
Age new_value_update = last_update_age();
|
||||
FOR_EACH_2(v, values, nv, value_parts) {
|
||||
//if (v->value() != nv.first && v->last_update < new_value_update) {
|
||||
if (v->last_update < new_value_update) {
|
||||
bool changed = v->value() != nv.first;
|
||||
v->value.assign(nv.first);
|
||||
changed |= v->update(ctx);
|
||||
v->last_update = new_value_update;
|
||||
if (changed) { // notify of change
|
||||
SCRIPT_OPTIONAL_PARAM_(CardP, card);
|
||||
SCRIPT_PARAM(Set*, set);
|
||||
ScriptValueEvent change(card.get(), v);
|
||||
set->actions.tellListeners(change, false);
|
||||
}
|
||||
}
|
||||
nv.first = v->value();
|
||||
nv.second = index_to_untagged(nv.first, nv.first.size()) == 0;
|
||||
}
|
||||
// options
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("hide when empty"), hide_when_empty, false);
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("soft before empty"), soft_before_empty, false);
|
||||
// recombine the parts
|
||||
String new_value = value_parts.front().first;
|
||||
bool new_value_empty = value_parts.front().second;
|
||||
size_t size_before_last = 0;
|
||||
for (size_t i = 1 ; i < value_parts.size() ; ++i) {
|
||||
size_before_last = new_value.size();
|
||||
if (value_parts[i].second && new_value_empty && hide_when_empty) {
|
||||
// no separator
|
||||
} else if (value_parts[i].second && soft_before_empty) {
|
||||
// soft separator
|
||||
new_value += _("<sep-soft>") + separators[i - 1] + _("</sep-soft>");
|
||||
new_value_empty = false;
|
||||
} else {
|
||||
// normal separator
|
||||
new_value += _("<sep>") + separators[i - 1] + _("</sep>");
|
||||
new_value_empty = false;
|
||||
}
|
||||
new_value += value_parts[i].first;
|
||||
}
|
||||
if (!new_value_empty || !hide_when_empty) {
|
||||
if (!suffix.empty()) {
|
||||
if (is_substr(new_value, size_before_last, _("<sep-soft>")) && value_parts.size() >= 2) {
|
||||
// If the value ends in a soft separator, we have this situation:
|
||||
// [blah]<sep-soft>ABC</sep-soft><suffix>XYZ</suffix>
|
||||
// This renderes as:
|
||||
// [blah] XYZ
|
||||
// Which looks bad, so instead change the text to
|
||||
// [blah]<sep>XYZ<soft>ABC</soft></sep>
|
||||
// Which might be slightly incorrect, but soft text doesn't matter anyway.
|
||||
size_t after = min(new_value.size(), match_close_tag_end(new_value, size_before_last));
|
||||
new_value = new_value.substr(0, size_before_last)
|
||||
+ _("<sep>")
|
||||
+ suffix
|
||||
+ _("<soft>")
|
||||
+ separators[value_parts.size() - 2]
|
||||
+ _("</soft></sep>")
|
||||
+ new_value.substr(after);
|
||||
} else {
|
||||
new_value += _("<suffix>") + suffix + _("</suffix>");
|
||||
}
|
||||
}
|
||||
if (!prefix.empty()) {
|
||||
new_value = _("<prefix>") + prefix + _("</prefix>") + new_value;
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(new_value);
|
||||
// read 'field#' arguments
|
||||
vector<TextValue*> values;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("field"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(ValueP, name, value) {
|
||||
TextValue* text_value = dynamic_cast<TextValue*>(value.get());
|
||||
if (!text_value) throw ScriptError(_("Argument '")+name+_("' should be a text field"));
|
||||
values.push_back(text_value);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
if (values.empty()) {
|
||||
throw ScriptError(_("No fields specified for combined_editor"));
|
||||
}
|
||||
// read 'separator#' arguments
|
||||
vector<String> separators;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("separator"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, name, separator) {
|
||||
separators.push_back(separator);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
if (separators.size() < values.size() - 1) {
|
||||
throw ScriptError(String::Format(_("Not enough separators for combine_editor, expected %d"), values.size()-1));
|
||||
}
|
||||
// the value
|
||||
SCRIPT_PARAM_C(String, value);
|
||||
// remove suffix/prefix
|
||||
SCRIPT_OPTIONAL_PARAM_(String, prefix);
|
||||
SCRIPT_OPTIONAL_PARAM_(String, suffix);
|
||||
if (is_substr(value,0,_("<prefix"))) {
|
||||
value = value.substr(min(value.size(), match_close_tag_end(value, 0)));
|
||||
}
|
||||
size_t pos = value.rfind(_("<suffix"));
|
||||
if (pos != String::npos && match_close_tag_end(value,pos) >= value.size()) {
|
||||
value = value.substr(0, pos);
|
||||
}
|
||||
// split the value
|
||||
vector<pair<String,bool> > value_parts; // (value part, is empty)
|
||||
pos = value.find(_("<sep"));
|
||||
while (pos != String::npos) {
|
||||
String part = value.substr(0, pos);
|
||||
value_parts.push_back(make_pair(part, false));
|
||||
value = value.substr(min(match_close_tag_end(value,pos), value.size()));
|
||||
pos = value.find(_("<sep"));
|
||||
}
|
||||
value_parts.push_back(make_pair(value, false));
|
||||
value_parts.resize(values.size()); // TODO: what if there are more value_parts than values?
|
||||
// update the values if our input value is newer?
|
||||
Age new_value_update = last_update_age();
|
||||
FOR_EACH_2(v, values, nv, value_parts) {
|
||||
//if (v->value() != nv.first && v->last_update < new_value_update) {
|
||||
if (v->last_update < new_value_update) {
|
||||
bool changed = v->value() != nv.first;
|
||||
v->value.assign(nv.first);
|
||||
changed |= v->update(ctx);
|
||||
v->last_update = new_value_update;
|
||||
if (changed) { // notify of change
|
||||
SCRIPT_OPTIONAL_PARAM_(CardP, card);
|
||||
SCRIPT_PARAM(Set*, set);
|
||||
ScriptValueEvent change(card.get(), v);
|
||||
set->actions.tellListeners(change, false);
|
||||
}
|
||||
}
|
||||
nv.first = v->value();
|
||||
nv.second = index_to_untagged(nv.first, nv.first.size()) == 0;
|
||||
}
|
||||
// options
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("hide when empty"), hide_when_empty, false);
|
||||
SCRIPT_PARAM_DEFAULT_N(bool, _("soft before empty"), soft_before_empty, false);
|
||||
// recombine the parts
|
||||
String new_value = value_parts.front().first;
|
||||
bool new_value_empty = value_parts.front().second;
|
||||
size_t size_before_last = 0;
|
||||
for (size_t i = 1 ; i < value_parts.size() ; ++i) {
|
||||
size_before_last = new_value.size();
|
||||
if (value_parts[i].second && new_value_empty && hide_when_empty) {
|
||||
// no separator
|
||||
} else if (value_parts[i].second && soft_before_empty) {
|
||||
// soft separator
|
||||
new_value += _("<sep-soft>") + separators[i - 1] + _("</sep-soft>");
|
||||
new_value_empty = false;
|
||||
} else {
|
||||
// normal separator
|
||||
new_value += _("<sep>") + separators[i - 1] + _("</sep>");
|
||||
new_value_empty = false;
|
||||
}
|
||||
new_value += value_parts[i].first;
|
||||
}
|
||||
if (!new_value_empty || !hide_when_empty) {
|
||||
if (!suffix.empty()) {
|
||||
if (is_substr(new_value, size_before_last, _("<sep-soft>")) && value_parts.size() >= 2) {
|
||||
// If the value ends in a soft separator, we have this situation:
|
||||
// [blah]<sep-soft>ABC</sep-soft><suffix>XYZ</suffix>
|
||||
// This renderes as:
|
||||
// [blah] XYZ
|
||||
// Which looks bad, so instead change the text to
|
||||
// [blah]<sep>XYZ<soft>ABC</soft></sep>
|
||||
// Which might be slightly incorrect, but soft text doesn't matter anyway.
|
||||
size_t after = min(new_value.size(), match_close_tag_end(new_value, size_before_last));
|
||||
new_value = new_value.substr(0, size_before_last)
|
||||
+ _("<sep>")
|
||||
+ suffix
|
||||
+ _("<soft>")
|
||||
+ separators[value_parts.size() - 2]
|
||||
+ _("</soft></sep>")
|
||||
+ new_value.substr(after);
|
||||
} else {
|
||||
new_value += _("<suffix>") + suffix + _("</suffix>");
|
||||
}
|
||||
}
|
||||
if (!prefix.empty()) {
|
||||
new_value = _("<prefix>") + prefix + _("</prefix>") + new_value;
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(new_value);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION_DEPENDENCIES(combined_editor) {
|
||||
// read 'field#' arguments
|
||||
vector<FieldP> fields;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("field"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(ValueP, name, value) {
|
||||
fields.push_back(value->fieldP);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
// Find the target field
|
||||
SCRIPT_PARAM_C(Set*, set);
|
||||
GameP game = set->game;
|
||||
FieldP target_field;
|
||||
if (dep.type == DEP_CARD_FIELD) target_field = game->card_fields[dep.index];
|
||||
else if (dep.type == DEP_SET_FIELD) target_field = game->set_fields[dep.index];
|
||||
else if (dep.type == DEP_EXTRA_CARD_FIELD) {
|
||||
SCRIPT_PARAM_C(StyleSheetP, stylesheet);
|
||||
target_field = stylesheet->extra_card_fields[dep.index];
|
||||
}
|
||||
else throw InternalError(_("Finding dependencies of combined error for non card/set field"));
|
||||
// Add dependencies, from target_field on field#
|
||||
// For card fields
|
||||
size_t j = 0;
|
||||
FOR_EACH(f, game->card_fields) {
|
||||
Dependency dep(DEP_CARD_COPY_DEP, j++);
|
||||
FOR_EACH(fn, fields) {
|
||||
if (f == fn) {
|
||||
target_field->dependent_scripts.add(dep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// For set fields
|
||||
j = 0;
|
||||
FOR_EACH(f, game->set_fields) {
|
||||
Dependency dep(DEP_SET_COPY_DEP, j++);
|
||||
FOR_EACH(fn, fields) {
|
||||
if (f == fn) {
|
||||
target_field->dependent_scripts.add(dep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependency_dummy;
|
||||
// read 'field#' arguments
|
||||
vector<FieldP> fields;
|
||||
for (int i = 0 ; ; ++i) {
|
||||
String name = _("field"); if (i > 0) name = name << i;
|
||||
SCRIPT_OPTIONAL_PARAM_N(ValueP, name, value) {
|
||||
fields.push_back(value->fieldP);
|
||||
} else if (i > 0) break;
|
||||
}
|
||||
// Find the target field
|
||||
SCRIPT_PARAM_C(Set*, set);
|
||||
GameP game = set->game;
|
||||
FieldP target_field;
|
||||
if (dep.type == DEP_CARD_FIELD) target_field = game->card_fields[dep.index];
|
||||
else if (dep.type == DEP_SET_FIELD) target_field = game->set_fields[dep.index];
|
||||
else if (dep.type == DEP_EXTRA_CARD_FIELD) {
|
||||
SCRIPT_PARAM_C(StyleSheetP, stylesheet);
|
||||
target_field = stylesheet->extra_card_fields[dep.index];
|
||||
}
|
||||
else throw InternalError(_("Finding dependencies of combined error for non card/set field"));
|
||||
// Add dependencies, from target_field on field#
|
||||
// For card fields
|
||||
size_t j = 0;
|
||||
FOR_EACH(f, game->card_fields) {
|
||||
Dependency dep(DEP_CARD_COPY_DEP, j++);
|
||||
FOR_EACH(fn, fields) {
|
||||
if (f == fn) {
|
||||
target_field->dependent_scripts.add(dep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// For set fields
|
||||
j = 0;
|
||||
FOR_EACH(f, game->set_fields) {
|
||||
Dependency dep(DEP_SET_COPY_DEP, j++);
|
||||
FOR_EACH(fn, fields) {
|
||||
if (f == fn) {
|
||||
target_field->dependent_scripts.add(dep);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dependency_dummy;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Choice values
|
||||
|
||||
// convert a full choice name into the name of the top level group it is in
|
||||
SCRIPT_FUNCTION(primary_choice) {
|
||||
SCRIPT_PARAM_C(ValueP,input);
|
||||
ChoiceValueP value = dynamic_pointer_cast<ChoiceValue>(input);
|
||||
if (!value) {
|
||||
throw ScriptError(_("Argument to 'primary_choice' should be a choice value"));
|
||||
}
|
||||
// determine choice
|
||||
int id = value->field().choices->choiceId(value->value);
|
||||
// find the last group that still contains id
|
||||
const vector<ChoiceField::ChoiceP>& choices = value->field().choices->choices;
|
||||
FOR_EACH_CONST_REVERSE(c, choices) {
|
||||
if (id >= c->first_id) {
|
||||
SCRIPT_RETURN(c->name);
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(_(""));
|
||||
SCRIPT_PARAM_C(ValueP,input);
|
||||
ChoiceValueP value = dynamic_pointer_cast<ChoiceValue>(input);
|
||||
if (!value) {
|
||||
throw ScriptError(_("Argument to 'primary_choice' should be a choice value"));
|
||||
}
|
||||
// determine choice
|
||||
int id = value->field().choices->choiceId(value->value);
|
||||
// find the last group that still contains id
|
||||
const vector<ChoiceField::ChoiceP>& choices = value->field().choices->choices;
|
||||
FOR_EACH_CONST_REVERSE(c, choices) {
|
||||
if (id >= c->first_id) {
|
||||
SCRIPT_RETURN(c->name);
|
||||
}
|
||||
}
|
||||
SCRIPT_RETURN(_(""));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Multiple choice values
|
||||
|
||||
/// Iterate over a comma separated list
|
||||
bool next_choice(size_t& start, size_t& end, const String& input) {
|
||||
if (start >= input.size()) return false;
|
||||
// ingore whitespace
|
||||
while (start < input.size() && input.GetChar(start) == _(' ')) ++start;
|
||||
// find end
|
||||
end = input.find_first_of(_(','), start);
|
||||
if (end == String::npos) end = input.size();
|
||||
return true;
|
||||
if (start >= input.size()) return false;
|
||||
// ingore whitespace
|
||||
while (start < input.size() && input.GetChar(start) == _(' ')) ++start;
|
||||
// find end
|
||||
end = input.find_first_of(_(','), start);
|
||||
if (end == String::npos) end = input.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Is the given choice active?
|
||||
bool chosen(const String& choice, const String& input) {
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,input) ; start = end+1 ) {
|
||||
// does this choice match the one asked about?
|
||||
if (end - start == choice.size() && is_substr(input, start, choice)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,input) ; start = end+1 ) {
|
||||
// does this choice match the one asked about?
|
||||
if (end - start == choice.size() && is_substr(input, start, choice)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// is the given choice active?
|
||||
SCRIPT_FUNCTION(chosen) {
|
||||
SCRIPT_PARAM_C(String,choice);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_RETURN(chosen(choice, input));
|
||||
SCRIPT_PARAM_C(String,choice);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_RETURN(chosen(choice, input));
|
||||
}
|
||||
|
||||
/// Filter the choices
|
||||
@@ -252,154 +252,154 @@ SCRIPT_FUNCTION(chosen) {
|
||||
* and at least min. Use prefered as choice to add/keep in case of conflicts.
|
||||
*/
|
||||
String filter_choices(const String& input, const vector<String>& choices, int min, int max, String prefered) {
|
||||
if (choices.empty()) return input; // do nothing, shouldn't happen, but better make sure
|
||||
String ret;
|
||||
int count = 0;
|
||||
vector<bool> seen(choices.size()); // which choices have we seen in input?
|
||||
// walk over the input
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,input) ; start = end +1) {
|
||||
// is this choice in choices
|
||||
bool in_choices = false;
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (!seen[i] && is_substr(input, start, choices[i])) {
|
||||
seen[i] = true; ++count;
|
||||
in_choices = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// is this choice unaffected?
|
||||
if (!in_choices) {
|
||||
if (!ret.empty()) ret += _(", ");
|
||||
ret += input.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
// keep more choices
|
||||
if (count < min) {
|
||||
// add prefered choice
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (choices[i] == prefered) {
|
||||
if (!seen[i]) {
|
||||
seen[i] = true; ++count;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// add more choices
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (count >= min) break;
|
||||
if (!seen[i]) {
|
||||
seen[i] = true; ++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
// keep less choices
|
||||
if (count > max) {
|
||||
for (size_t i = choices.size() - 1 ; i >= 0 ; --i) {
|
||||
if (count <= max) break;
|
||||
if (seen[i]) {
|
||||
if (max > 0 && choices[i] == prefered) continue; // we would rather not remove prefered choice
|
||||
seen[i] = false; --count;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add the 'seen' choices to ret
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (seen[i]) {
|
||||
if (!ret.empty()) ret += _(", ");
|
||||
ret += choices[i];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
if (choices.empty()) return input; // do nothing, shouldn't happen, but better make sure
|
||||
String ret;
|
||||
int count = 0;
|
||||
vector<bool> seen(choices.size()); // which choices have we seen in input?
|
||||
// walk over the input
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,input) ; start = end +1) {
|
||||
// is this choice in choices
|
||||
bool in_choices = false;
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (!seen[i] && is_substr(input, start, choices[i])) {
|
||||
seen[i] = true; ++count;
|
||||
in_choices = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// is this choice unaffected?
|
||||
if (!in_choices) {
|
||||
if (!ret.empty()) ret += _(", ");
|
||||
ret += input.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
// keep more choices
|
||||
if (count < min) {
|
||||
// add prefered choice
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (choices[i] == prefered) {
|
||||
if (!seen[i]) {
|
||||
seen[i] = true; ++count;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
// add more choices
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (count >= min) break;
|
||||
if (!seen[i]) {
|
||||
seen[i] = true; ++count;
|
||||
}
|
||||
}
|
||||
}
|
||||
// keep less choices
|
||||
if (count > max) {
|
||||
for (size_t i = choices.size() - 1 ; i >= 0 ; --i) {
|
||||
if (count <= max) break;
|
||||
if (seen[i]) {
|
||||
if (max > 0 && choices[i] == prefered) continue; // we would rather not remove prefered choice
|
||||
seen[i] = false; --count;
|
||||
}
|
||||
}
|
||||
}
|
||||
// add the 'seen' choices to ret
|
||||
for (size_t i = 0 ; i < choices.size() ; ++i) {
|
||||
if (seen[i]) {
|
||||
if (!ret.empty()) ret += _(", ");
|
||||
ret += choices[i];
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// read 'choice#' arguments
|
||||
void read_choices_param(Context& ctx, vector<String>& choices_out) {
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choices) {
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,choices) ; start = end + 1) {
|
||||
choices_out.push_back(choices.substr(start,end-start));
|
||||
}
|
||||
} else {
|
||||
// old style: separate arguments
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choice) {
|
||||
choices_out.push_back(choice);
|
||||
} else {
|
||||
for (int i = 1 ; ; ++i) {
|
||||
String name = _("choice");
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, name << i, choice) {
|
||||
choices_out.push_back(choice);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choices) {
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,choices) ; start = end + 1) {
|
||||
choices_out.push_back(choices.substr(start,end-start));
|
||||
}
|
||||
} else {
|
||||
// old style: separate arguments
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choice) {
|
||||
choices_out.push_back(choice);
|
||||
} else {
|
||||
for (int i = 1 ; ; ++i) {
|
||||
String name = _("choice");
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, name << i, choice) {
|
||||
choices_out.push_back(choice);
|
||||
} else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the given choice if it is not already active
|
||||
SCRIPT_FUNCTION(require_choice) {
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 1, (int)choices.size(), last_change));
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 1, (int)choices.size(), last_change));
|
||||
}
|
||||
|
||||
// make sure at most one of the choices is active
|
||||
SCRIPT_FUNCTION(exclusive_choice) {
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 0, 1, last_change));
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 0, 1, last_change));
|
||||
}
|
||||
|
||||
// make sure exactly one of the choices is active
|
||||
SCRIPT_FUNCTION(require_exclusive_choice) {
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 1, 1, last_change));
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("last change"),last_change);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 1, 1, last_change));
|
||||
}
|
||||
|
||||
// make sure none of the choices are active
|
||||
SCRIPT_FUNCTION(remove_choice) {
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 0, 0, _("")));
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
vector<String> choices;
|
||||
read_choices_param(ctx, choices);
|
||||
SCRIPT_RETURN(filter_choices(input, choices, 0, 0, _("")));
|
||||
}
|
||||
|
||||
// count how many choices are active
|
||||
SCRIPT_FUNCTION(count_chosen) {
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choices) {
|
||||
// only count specific choices
|
||||
int count = 0;
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,choices) ; start = end + 1) {
|
||||
if (chosen(choices.substr(start,end-start),input)) ++count;
|
||||
}
|
||||
SCRIPT_RETURN(count);
|
||||
} else {
|
||||
// count all choices => count comma's + 1
|
||||
if (input.empty()) {
|
||||
SCRIPT_RETURN(0);
|
||||
} else {
|
||||
int count = 1;
|
||||
FOR_EACH(c, input) if (c == _(',')) ++count;
|
||||
SCRIPT_RETURN(count);
|
||||
}
|
||||
}
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
SCRIPT_OPTIONAL_PARAM_C(String,choices) {
|
||||
// only count specific choices
|
||||
int count = 0;
|
||||
for (size_t start = 0, end = 0 ; next_choice(start,end,choices) ; start = end + 1) {
|
||||
if (chosen(choices.substr(start,end-start),input)) ++count;
|
||||
}
|
||||
SCRIPT_RETURN(count);
|
||||
} else {
|
||||
// count all choices => count comma's + 1
|
||||
if (input.empty()) {
|
||||
SCRIPT_RETURN(0);
|
||||
} else {
|
||||
int count = 1;
|
||||
FOR_EACH(c, input) if (c == _(',')) ++count;
|
||||
SCRIPT_RETURN(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_editor_functions(Context& ctx) {
|
||||
ctx.setVariable(_("forward editor"), script_combined_editor); // compatability
|
||||
ctx.setVariable(_("combined editor"), script_combined_editor);
|
||||
ctx.setVariable(_("primary choice"), script_primary_choice);
|
||||
ctx.setVariable(_("chosen"), script_chosen);
|
||||
ctx.setVariable(_("count chosen"), script_count_chosen);
|
||||
ctx.setVariable(_("require choice"), script_require_choice);
|
||||
ctx.setVariable(_("exclusive choice"), script_exclusive_choice);
|
||||
ctx.setVariable(_("require exclusive choice"), script_require_exclusive_choice);
|
||||
ctx.setVariable(_("remove choice"), script_remove_choice);
|
||||
ctx.setVariable(_("forward editor"), script_combined_editor); // compatability
|
||||
ctx.setVariable(_("combined editor"), script_combined_editor);
|
||||
ctx.setVariable(_("primary choice"), script_primary_choice);
|
||||
ctx.setVariable(_("chosen"), script_chosen);
|
||||
ctx.setVariable(_("count chosen"), script_count_chosen);
|
||||
ctx.setVariable(_("require choice"), script_require_choice);
|
||||
ctx.setVariable(_("exclusive choice"), script_exclusive_choice);
|
||||
ctx.setVariable(_("require exclusive choice"), script_require_exclusive_choice);
|
||||
ctx.setVariable(_("remove choice"), script_remove_choice);
|
||||
}
|
||||
|
||||
+251
-251
@@ -15,208 +15,208 @@
|
||||
// ----------------------------------------------------------------------------- : Util
|
||||
|
||||
bool is_vowel(Char c) {
|
||||
return c == _('a') || c == _('e') || c == _('i') || c == _('o') || c == _('u')
|
||||
|| c == _('A') || c == _('E') || c == _('I') || c == _('O') || c == _('U');
|
||||
return c == _('a') || c == _('e') || c == _('i') || c == _('o') || c == _('u')
|
||||
|| c == _('A') || c == _('E') || c == _('I') || c == _('O') || c == _('U');
|
||||
}
|
||||
inline bool is_constant(Char c) {
|
||||
return !is_vowel(c);
|
||||
return !is_vowel(c);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Numbers
|
||||
|
||||
/// Write a number using words, for example 23 -> "twenty-three"
|
||||
String english_number(int i) {
|
||||
switch (i) {
|
||||
case 0: return _("zero");
|
||||
case 1: return _("one");
|
||||
case 2: return _("two");
|
||||
case 3: return _("three");
|
||||
case 4: return _("four");
|
||||
case 5: return _("five");
|
||||
case 6: return _("six");
|
||||
case 7: return _("seven");
|
||||
case 8: return _("eight");
|
||||
case 9: return _("nine");
|
||||
case 10: return _("ten");
|
||||
case 11: return _("eleven");
|
||||
case 12: return _("twelve");
|
||||
case 13: return _("thirteen");
|
||||
case 15: return _("fifteen");
|
||||
case 18: return _("eighteen");
|
||||
case 20: return _("twenty");
|
||||
case 30: return _("thirty");
|
||||
case 40: return _("forty");
|
||||
case 50: return _("fifty");
|
||||
case 80: return _("eighty");
|
||||
default: {
|
||||
if (i < 0 || i >= 100) {
|
||||
// number too large, keep as digits
|
||||
return (String() << i);
|
||||
} else if (i < 20) {
|
||||
return english_number(i%10) + _("teen");
|
||||
} else if (i % 10 == 0) {
|
||||
return english_number(i/10) + _("ty");
|
||||
} else {
|
||||
// <a>ty-<b>
|
||||
return english_number(i/10*10) + _("-") + english_number(i%10);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (i) {
|
||||
case 0: return _("zero");
|
||||
case 1: return _("one");
|
||||
case 2: return _("two");
|
||||
case 3: return _("three");
|
||||
case 4: return _("four");
|
||||
case 5: return _("five");
|
||||
case 6: return _("six");
|
||||
case 7: return _("seven");
|
||||
case 8: return _("eight");
|
||||
case 9: return _("nine");
|
||||
case 10: return _("ten");
|
||||
case 11: return _("eleven");
|
||||
case 12: return _("twelve");
|
||||
case 13: return _("thirteen");
|
||||
case 15: return _("fifteen");
|
||||
case 18: return _("eighteen");
|
||||
case 20: return _("twenty");
|
||||
case 30: return _("thirty");
|
||||
case 40: return _("forty");
|
||||
case 50: return _("fifty");
|
||||
case 80: return _("eighty");
|
||||
default: {
|
||||
if (i < 0 || i >= 100) {
|
||||
// number too large, keep as digits
|
||||
return (String() << i);
|
||||
} else if (i < 20) {
|
||||
return english_number(i%10) + _("teen");
|
||||
} else if (i % 10 == 0) {
|
||||
return english_number(i/10) + _("ty");
|
||||
} else {
|
||||
// <a>ty-<b>
|
||||
return english_number(i/10*10) + _("-") + english_number(i%10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write an ordinal number using words, for example 23 -> "twenty-third"
|
||||
String english_ordinal(int i) {
|
||||
switch (i) {
|
||||
case 1: return _("first");
|
||||
case 2: return _("second");
|
||||
case 3: return _("third");
|
||||
case 5: return _("fifth");
|
||||
case 8: return _("eighth");
|
||||
case 9: return _("ninth");
|
||||
case 11: return _("eleventh");
|
||||
case 12: return _("twelfth");
|
||||
default: {
|
||||
if (i <= 0 || i >= 100) {
|
||||
// number too large, keep as digits
|
||||
return String::Format(_("%dth"), i);
|
||||
} else if (i < 20) {
|
||||
return english_number(i) + _("th");
|
||||
} else if (i % 10 == 0) {
|
||||
String num = english_number(i);
|
||||
return num.substr(0,num.size()-1) + _("ieth"); // twentieth
|
||||
} else {
|
||||
// <a>ty-<b>th
|
||||
return english_number(i/10*10) + _("-") + english_ordinal(i%10);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (i) {
|
||||
case 1: return _("first");
|
||||
case 2: return _("second");
|
||||
case 3: return _("third");
|
||||
case 5: return _("fifth");
|
||||
case 8: return _("eighth");
|
||||
case 9: return _("ninth");
|
||||
case 11: return _("eleventh");
|
||||
case 12: return _("twelfth");
|
||||
default: {
|
||||
if (i <= 0 || i >= 100) {
|
||||
// number too large, keep as digits
|
||||
return String::Format(_("%dth"), i);
|
||||
} else if (i < 20) {
|
||||
return english_number(i) + _("th");
|
||||
} else if (i % 10 == 0) {
|
||||
String num = english_number(i);
|
||||
return num.substr(0,num.size()-1) + _("ieth"); // twentieth
|
||||
} else {
|
||||
// <a>ty-<b>th
|
||||
return english_number(i/10*10) + _("-") + english_ordinal(i%10);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Write a number using words, use "a" for 1
|
||||
String english_number_a(int i) {
|
||||
if (i == 1) return _("a");
|
||||
else return english_number(i);
|
||||
if (i == 1) return _("a");
|
||||
else return english_number(i);
|
||||
}
|
||||
/// Write a number using words, use "" for 1
|
||||
String english_number_multiple(int i) {
|
||||
if (i == 1) return _("");
|
||||
else return english_number(i);
|
||||
if (i == 1) return _("");
|
||||
else return english_number(i);
|
||||
}
|
||||
|
||||
|
||||
// script_english_number_*
|
||||
String do_english_num(String input, String(*fun)(int)) {
|
||||
if (is_substr(input, 0, _("<param-"))) {
|
||||
// a keyword parameter, of the form "<param->123</param->"
|
||||
size_t start = skip_tag(input, 0);
|
||||
if (start != String::npos) {
|
||||
size_t end = input.find_first_of(_('<'), start);
|
||||
if (end != String::npos) {
|
||||
String is = input.substr(start, end - start);
|
||||
long i = 0;
|
||||
if (is.ToLong(&i)) {
|
||||
if (i == 1) {
|
||||
return _("<hint-1>") + substr_replace(input, start, end, fun(i));
|
||||
} else {
|
||||
return _("<hint-2>") + substr_replace(input, start, end, fun(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("<hint-2>") + input;
|
||||
} else {
|
||||
long i = 0;
|
||||
if (input.ToLong(&i)) {
|
||||
return fun(i);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
if (is_substr(input, 0, _("<param-"))) {
|
||||
// a keyword parameter, of the form "<param->123</param->"
|
||||
size_t start = skip_tag(input, 0);
|
||||
if (start != String::npos) {
|
||||
size_t end = input.find_first_of(_('<'), start);
|
||||
if (end != String::npos) {
|
||||
String is = input.substr(start, end - start);
|
||||
long i = 0;
|
||||
if (is.ToLong(&i)) {
|
||||
if (i == 1) {
|
||||
return _("<hint-1>") + substr_replace(input, start, end, fun(i));
|
||||
} else {
|
||||
return _("<hint-2>") + substr_replace(input, start, end, fun(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _("<hint-2>") + input;
|
||||
} else {
|
||||
long i = 0;
|
||||
if (input.ToLong(&i)) {
|
||||
return fun(i);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(english_number) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number));
|
||||
}
|
||||
SCRIPT_FUNCTION(english_number_a) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number_a));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number_a));
|
||||
}
|
||||
SCRIPT_FUNCTION(english_number_multiple) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number_multiple));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_number_multiple));
|
||||
}
|
||||
SCRIPT_FUNCTION(english_number_ordinal) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_ordinal));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english_num(input, english_ordinal));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Singular/plural
|
||||
|
||||
String english_singular(const String& str) {
|
||||
if (str.size() > 3 && is_substr(str, str.size()-3, _("ies"))) {
|
||||
return str.substr(0, str.size() - 3) + _("y");
|
||||
} else if (str.size() > 3 && is_substr(str, str.size()-3, _("oes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("ches"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("shes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("sses"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 5 && is_substr(str, str.size()-3, _("ves")) && (is_substr(str, str.size()-5, _("el")) || is_substr(str, str.size()-5, _("ar")) )) {
|
||||
return str.substr(0, str.size() - 3) + _("f");
|
||||
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
|
||||
return str.substr(0, str.size() - 1);
|
||||
} else if (str.size() >= 3 && is_substr(str, str.size()-3, _("men"))) {
|
||||
return str.substr(0, str.size() - 2) + _("an");
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
if (str.size() > 3 && is_substr(str, str.size()-3, _("ies"))) {
|
||||
return str.substr(0, str.size() - 3) + _("y");
|
||||
} else if (str.size() > 3 && is_substr(str, str.size()-3, _("oes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("ches"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("shes"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 4 && is_substr(str, str.size()-4, _("sses"))) {
|
||||
return str.substr(0, str.size() - 2);
|
||||
} else if (str.size() > 5 && is_substr(str, str.size()-3, _("ves")) && (is_substr(str, str.size()-5, _("el")) || is_substr(str, str.size()-5, _("ar")) )) {
|
||||
return str.substr(0, str.size() - 3) + _("f");
|
||||
} else if (str.size() > 1 && str.GetChar(str.size() - 1) == _('s')) {
|
||||
return str.substr(0, str.size() - 1);
|
||||
} else if (str.size() >= 3 && is_substr(str, str.size()-3, _("men"))) {
|
||||
return str.substr(0, str.size() - 2) + _("an");
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
String english_plural(const String& str) {
|
||||
if (str.size() > 2) {
|
||||
Char a = str.GetChar(str.size() - 2);
|
||||
Char b = str.GetChar(str.size() - 1);
|
||||
if (b == _('y') && is_constant(a)) {
|
||||
return str.substr(0, str.size() - 1) + _("ies");
|
||||
} else if (b == _('o') && is_constant(a)) {
|
||||
return str + _("es");
|
||||
} else if ((a == _('s') || a == _('c')) && b == _('h')) {
|
||||
return str + _("es");
|
||||
} else if (b == _('s')) {
|
||||
return str + _("es");
|
||||
} else {
|
||||
return str + _("s");
|
||||
}
|
||||
}
|
||||
return str + _("s");
|
||||
if (str.size() > 2) {
|
||||
Char a = str.GetChar(str.size() - 2);
|
||||
Char b = str.GetChar(str.size() - 1);
|
||||
if (b == _('y') && is_constant(a)) {
|
||||
return str.substr(0, str.size() - 1) + _("ies");
|
||||
} else if (b == _('o') && is_constant(a)) {
|
||||
return str + _("es");
|
||||
} else if ((a == _('s') || a == _('c')) && b == _('h')) {
|
||||
return str + _("es");
|
||||
} else if (b == _('s')) {
|
||||
return str + _("es");
|
||||
} else {
|
||||
return str + _("s");
|
||||
}
|
||||
}
|
||||
return str + _("s");
|
||||
}
|
||||
|
||||
// script_english_singular/plural/singplur
|
||||
String do_english(String input, String(*fun)(const String&)) {
|
||||
if (is_substr(input, 0, _("<param-"))) {
|
||||
// a keyword parameter, of the form "<param->123</param->"
|
||||
size_t start = skip_tag(input, 0);
|
||||
if (start != String::npos) {
|
||||
size_t end = input.find_first_of(_('<'), start);
|
||||
if (end != String::npos) {
|
||||
String is = input.substr(start, end - start);
|
||||
return substr_replace(input, start, end, fun(is));
|
||||
}
|
||||
}
|
||||
return input; // failed
|
||||
} else {
|
||||
return fun(input);
|
||||
}
|
||||
if (is_substr(input, 0, _("<param-"))) {
|
||||
// a keyword parameter, of the form "<param->123</param->"
|
||||
size_t start = skip_tag(input, 0);
|
||||
if (start != String::npos) {
|
||||
size_t end = input.find_first_of(_('<'), start);
|
||||
if (end != String::npos) {
|
||||
String is = input.substr(start, end - start);
|
||||
return substr_replace(input, start, end, fun(is));
|
||||
}
|
||||
}
|
||||
return input; // failed
|
||||
} else {
|
||||
return fun(input);
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(english_singular) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english(input, english_singular));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english(input, english_singular));
|
||||
}
|
||||
SCRIPT_FUNCTION(english_plural) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english(input, english_plural));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(do_english(input, english_plural));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Hints
|
||||
@@ -235,110 +235,110 @@ SCRIPT_FUNCTION(english_plural) {
|
||||
* Note: there is no close tags for hints
|
||||
*/
|
||||
String process_english_hints(const String& str) {
|
||||
String ret; ret.reserve(str.size());
|
||||
// have we seen a <hint-1/2>?
|
||||
// 1 for singular, 2 for plural
|
||||
int singplur = 0;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (is_substr(str, i, _("<hint-"))) {
|
||||
Char h = str.GetChar(i + 6); // hint code
|
||||
if (h == _('1')) {
|
||||
singplur = 1;
|
||||
} else if (h == _('2')) {
|
||||
singplur = 2;
|
||||
}
|
||||
i = skip_tag(str, i);
|
||||
} else if (is_substr(str, i, _("<param-"))) {
|
||||
size_t after = skip_tag(str, i);
|
||||
if (after != String::npos) {
|
||||
Char c = str.GetChar(after);
|
||||
if (singplur == 1 && is_substr(str, after, _("a</param-"))) {
|
||||
// a -> an, where the a originates from english_number_a(1)
|
||||
size_t after_end = skip_tag(str,after+1);
|
||||
if (after_end == String::npos) {
|
||||
throw Error(_("Incomplete </param> tag"));
|
||||
}
|
||||
if (after_end != String::npos && after_end + 1 < str.size()
|
||||
&& isSpace(str.GetChar(after_end)) && is_vowel(str.GetChar(after_end+1))) {
|
||||
ret.append(str,i,after-i);
|
||||
ret += _("an");
|
||||
i = after + 1;
|
||||
continue;
|
||||
}
|
||||
} else if (is_vowel(c) && ret.size() >= 2) {
|
||||
// a -> an?
|
||||
// is there "a" before this?
|
||||
String last = ret.substr(ret.size() - 2);
|
||||
if ( (ret.size() == 2 || !isAlpha(ret.GetChar(ret.size() - 3))) &&
|
||||
(last == _("a ") || last == _("A ")) ) {
|
||||
ret.insert(ret.size() - 1, _('n'));
|
||||
}
|
||||
} else if (is_substr(str, after, _("</param-")) && ret.size() >= 1 &&
|
||||
ret.GetChar(ret.size() - 1) == _(' ')) {
|
||||
// empty param, drop space before it
|
||||
ret.resize(ret.size() - 1);
|
||||
}
|
||||
}
|
||||
ret.append(str,i,min(after,str.size())-i);
|
||||
i = after;
|
||||
} else if (is_substr(str, i, _("<singular>"))) {
|
||||
// singular -> keep, plural -> drop
|
||||
size_t start = skip_tag(str, i);
|
||||
size_t end = match_close_tag(str, start);
|
||||
if (singplur == 1 && end != String::npos) {
|
||||
ret += str.substr(start, end - start);
|
||||
}
|
||||
singplur = 0;
|
||||
i = skip_tag(str, end);
|
||||
} else if (is_substr(str, i, _("<plural>"))) {
|
||||
// singular -> drop, plural -> keep
|
||||
size_t start = skip_tag(str, i);
|
||||
size_t end = match_close_tag(str, start);
|
||||
if (singplur == 2 && end != String::npos) {
|
||||
ret += str.substr(start, end - start);
|
||||
}
|
||||
singplur = 0;
|
||||
i = skip_tag(str, end);
|
||||
} else if (c == _('(') && singplur) {
|
||||
// singular -> drop (...), plural -> keep it
|
||||
size_t end = str.find_first_of(_(')'), i);
|
||||
if (end != String::npos) {
|
||||
if (singplur == 2) {
|
||||
ret += str.substr(i + 1, end - i - 1);
|
||||
}
|
||||
i = end + 1;
|
||||
} else { // handle like normal
|
||||
ret += c;
|
||||
++i;
|
||||
}
|
||||
singplur = 0;
|
||||
} else if (c == _('<')) {
|
||||
size_t after = skip_tag(str, i);
|
||||
ret.append(str,i,min(after,str.size())-i);
|
||||
i = after;
|
||||
} else {
|
||||
ret += c;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
String ret; ret.reserve(str.size());
|
||||
// have we seen a <hint-1/2>?
|
||||
// 1 for singular, 2 for plural
|
||||
int singplur = 0;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (is_substr(str, i, _("<hint-"))) {
|
||||
Char h = str.GetChar(i + 6); // hint code
|
||||
if (h == _('1')) {
|
||||
singplur = 1;
|
||||
} else if (h == _('2')) {
|
||||
singplur = 2;
|
||||
}
|
||||
i = skip_tag(str, i);
|
||||
} else if (is_substr(str, i, _("<param-"))) {
|
||||
size_t after = skip_tag(str, i);
|
||||
if (after != String::npos) {
|
||||
Char c = str.GetChar(after);
|
||||
if (singplur == 1 && is_substr(str, after, _("a</param-"))) {
|
||||
// a -> an, where the a originates from english_number_a(1)
|
||||
size_t after_end = skip_tag(str,after+1);
|
||||
if (after_end == String::npos) {
|
||||
throw Error(_("Incomplete </param> tag"));
|
||||
}
|
||||
if (after_end != String::npos && after_end + 1 < str.size()
|
||||
&& isSpace(str.GetChar(after_end)) && is_vowel(str.GetChar(after_end+1))) {
|
||||
ret.append(str,i,after-i);
|
||||
ret += _("an");
|
||||
i = after + 1;
|
||||
continue;
|
||||
}
|
||||
} else if (is_vowel(c) && ret.size() >= 2) {
|
||||
// a -> an?
|
||||
// is there "a" before this?
|
||||
String last = ret.substr(ret.size() - 2);
|
||||
if ( (ret.size() == 2 || !isAlpha(ret.GetChar(ret.size() - 3))) &&
|
||||
(last == _("a ") || last == _("A ")) ) {
|
||||
ret.insert(ret.size() - 1, _('n'));
|
||||
}
|
||||
} else if (is_substr(str, after, _("</param-")) && ret.size() >= 1 &&
|
||||
ret.GetChar(ret.size() - 1) == _(' ')) {
|
||||
// empty param, drop space before it
|
||||
ret.resize(ret.size() - 1);
|
||||
}
|
||||
}
|
||||
ret.append(str,i,min(after,str.size())-i);
|
||||
i = after;
|
||||
} else if (is_substr(str, i, _("<singular>"))) {
|
||||
// singular -> keep, plural -> drop
|
||||
size_t start = skip_tag(str, i);
|
||||
size_t end = match_close_tag(str, start);
|
||||
if (singplur == 1 && end != String::npos) {
|
||||
ret += str.substr(start, end - start);
|
||||
}
|
||||
singplur = 0;
|
||||
i = skip_tag(str, end);
|
||||
} else if (is_substr(str, i, _("<plural>"))) {
|
||||
// singular -> drop, plural -> keep
|
||||
size_t start = skip_tag(str, i);
|
||||
size_t end = match_close_tag(str, start);
|
||||
if (singplur == 2 && end != String::npos) {
|
||||
ret += str.substr(start, end - start);
|
||||
}
|
||||
singplur = 0;
|
||||
i = skip_tag(str, end);
|
||||
} else if (c == _('(') && singplur) {
|
||||
// singular -> drop (...), plural -> keep it
|
||||
size_t end = str.find_first_of(_(')'), i);
|
||||
if (end != String::npos) {
|
||||
if (singplur == 2) {
|
||||
ret += str.substr(i + 1, end - i - 1);
|
||||
}
|
||||
i = end + 1;
|
||||
} else { // handle like normal
|
||||
ret += c;
|
||||
++i;
|
||||
}
|
||||
singplur = 0;
|
||||
} else if (c == _('<')) {
|
||||
size_t after = skip_tag(str, i);
|
||||
ret.append(str,i,min(after,str.size())-i);
|
||||
i = after;
|
||||
} else {
|
||||
ret += c;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(process_english_hints) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
assert_tagged(input);
|
||||
SCRIPT_RETURN(process_english_hints(input));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
assert_tagged(input);
|
||||
SCRIPT_RETURN(process_english_hints(input));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_english_functions(Context& ctx) {
|
||||
ctx.setVariable(_("english number"), script_english_number);
|
||||
ctx.setVariable(_("english number a"), script_english_number_a);
|
||||
ctx.setVariable(_("english number multiple"), script_english_number_multiple);
|
||||
ctx.setVariable(_("english number ordinal"), script_english_number_ordinal);
|
||||
ctx.setVariable(_("english singular"), script_english_singular);
|
||||
ctx.setVariable(_("english plural"), script_english_plural);
|
||||
ctx.setVariable(_("process english hints"), script_process_english_hints);
|
||||
ctx.setVariable(_("english number"), script_english_number);
|
||||
ctx.setVariable(_("english number a"), script_english_number_a);
|
||||
ctx.setVariable(_("english number multiple"), script_english_number_multiple);
|
||||
ctx.setVariable(_("english number ordinal"), script_english_number_ordinal);
|
||||
ctx.setVariable(_("english singular"), script_english_singular);
|
||||
ctx.setVariable(_("english plural"), script_english_plural);
|
||||
ctx.setVariable(_("process english hints"), script_process_english_hints);
|
||||
}
|
||||
|
||||
+349
-349
@@ -27,65 +27,65 @@ DECLARE_TYPEOF_COLLECTION(SymbolFont::DrawableSymbol);
|
||||
|
||||
// Make sure we can export files to a data directory
|
||||
void guard_export_info(const String& fun, bool need_template = false) {
|
||||
if (!export_info() && (!need_template || export_info()->export_template)) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" from export templates"));
|
||||
} else if (export_info()->directory_relative.empty()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" when 'create directory' is set to true"));
|
||||
}
|
||||
if (!export_info() && (!need_template || export_info()->export_template)) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" from export templates"));
|
||||
} else if (export_info()->directory_relative.empty()) {
|
||||
throw ScriptError(_("Can only use ") + fun + _(" when 'create directory' is set to true"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Find an absolute filename for a relative filename from an export template,
|
||||
/// Returns the absolute filename, and may modify the relative name.
|
||||
String get_export_full_path(String& rel_name) {
|
||||
ExportInfo& ei = *export_info();
|
||||
// the absolute path
|
||||
wxFileName fn(rel_name);
|
||||
fn.Normalize(wxPATH_NORM_ALL, ei.directory_absolute);
|
||||
if (!ei.allow_writes_outside) {
|
||||
// check if path is okay
|
||||
wxFileName fn2(_("x"));
|
||||
fn2.SetPath(ei.directory_absolute);
|
||||
fn2.Normalize(wxPATH_NORM_ALL, ei.directory_absolute);
|
||||
String p1 = fn.GetFullPath();
|
||||
String p2 = fn2.GetFullPath();
|
||||
p2.resize(p2.size() - 1); // drop the x
|
||||
if (p2.empty() || p1.size() < p2.size() || p1.substr(0,p2.size()-1) != p2.substr(0,p2.size()-1)) {
|
||||
throw ScriptError(_("Not a relative filename: ") + rel_name);
|
||||
}
|
||||
}
|
||||
rel_name = fn.GetFullName();
|
||||
return fn.GetFullPath();
|
||||
ExportInfo& ei = *export_info();
|
||||
// the absolute path
|
||||
wxFileName fn(rel_name);
|
||||
fn.Normalize(wxPATH_NORM_ALL, ei.directory_absolute);
|
||||
if (!ei.allow_writes_outside) {
|
||||
// check if path is okay
|
||||
wxFileName fn2(_("x"));
|
||||
fn2.SetPath(ei.directory_absolute);
|
||||
fn2.Normalize(wxPATH_NORM_ALL, ei.directory_absolute);
|
||||
String p1 = fn.GetFullPath();
|
||||
String p2 = fn2.GetFullPath();
|
||||
p2.resize(p2.size() - 1); // drop the x
|
||||
if (p2.empty() || p1.size() < p2.size() || p1.substr(0,p2.size()-1) != p2.substr(0,p2.size()-1)) {
|
||||
throw ScriptError(_("Not a relative filename: ") + rel_name);
|
||||
}
|
||||
}
|
||||
rel_name = fn.GetFullName();
|
||||
return fn.GetFullPath();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : HTML
|
||||
|
||||
// An HTML tag
|
||||
struct Tag {
|
||||
Tag(const Char* open_tag, const Char* close_tag)
|
||||
: open_tag(open_tag), close_tag(close_tag), opened(0)
|
||||
{}
|
||||
const Char* open_tag; ///< The tags to insert in HTML "<tag>"
|
||||
const Char* close_tag; ///< The tags to insert in HTML "</tag>"
|
||||
int opened; ///< How often is the tag opened in the input?
|
||||
/// Write an open or close tag to a string if needed
|
||||
void write(String& ret, bool close) {
|
||||
if (close) {
|
||||
if (--opened == 0) {
|
||||
ret += close_tag;
|
||||
}
|
||||
} else {
|
||||
if (++opened == 1) {
|
||||
ret += open_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
Tag(const Char* open_tag, const Char* close_tag)
|
||||
: open_tag(open_tag), close_tag(close_tag), opened(0)
|
||||
{}
|
||||
const Char* open_tag; ///< The tags to insert in HTML "<tag>"
|
||||
const Char* close_tag; ///< The tags to insert in HTML "</tag>"
|
||||
int opened; ///< How often is the tag opened in the input?
|
||||
/// Write an open or close tag to a string if needed
|
||||
void write(String& ret, bool close) {
|
||||
if (close) {
|
||||
if (--opened == 0) {
|
||||
ret += close_tag;
|
||||
}
|
||||
} else {
|
||||
if (++opened == 1) {
|
||||
ret += open_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// A tag, or a close tag
|
||||
struct NegTag {
|
||||
Tag* tag;
|
||||
bool neg; // a close tag instead of an open tag
|
||||
NegTag(Tag* tag, bool neg) : tag(tag), neg(neg) {}
|
||||
Tag* tag;
|
||||
bool neg; // a close tag instead of an open tag
|
||||
NegTag(Tag* tag, bool neg) : tag(tag), neg(neg) {}
|
||||
};
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(NegTag);
|
||||
@@ -93,371 +93,371 @@ DECLARE_TYPEOF_COLLECTION(NegTag);
|
||||
/// A stack of opened HTML tags
|
||||
class TagStack {
|
||||
public:
|
||||
void open(String& ret, Tag& tag) {
|
||||
add(ret, NegTag(&tag, false));
|
||||
}
|
||||
void close(String& ret, Tag& tag) {
|
||||
add(ret, NegTag(&tag, true));
|
||||
}
|
||||
// Close all tags, should be called at end of input
|
||||
void close_all(String& ret) {
|
||||
// cancel out tags with pending tags
|
||||
write_pending_tags(ret);
|
||||
// close all open tags
|
||||
while (!tags.empty()) {
|
||||
tags.back()->write(ret, true);
|
||||
tags.pop_back();
|
||||
}
|
||||
}
|
||||
// Write all pending tags, should be called before non-tag output
|
||||
void write_pending_tags(String& ret) {
|
||||
FOR_EACH(t, pending_tags) {
|
||||
t.tag->write(ret, t.neg);
|
||||
if (!t.neg) tags.push_back(t.tag);
|
||||
}
|
||||
pending_tags.clear();
|
||||
}
|
||||
|
||||
void open(String& ret, Tag& tag) {
|
||||
add(ret, NegTag(&tag, false));
|
||||
}
|
||||
void close(String& ret, Tag& tag) {
|
||||
add(ret, NegTag(&tag, true));
|
||||
}
|
||||
// Close all tags, should be called at end of input
|
||||
void close_all(String& ret) {
|
||||
// cancel out tags with pending tags
|
||||
write_pending_tags(ret);
|
||||
// close all open tags
|
||||
while (!tags.empty()) {
|
||||
tags.back()->write(ret, true);
|
||||
tags.pop_back();
|
||||
}
|
||||
}
|
||||
// Write all pending tags, should be called before non-tag output
|
||||
void write_pending_tags(String& ret) {
|
||||
FOR_EACH(t, pending_tags) {
|
||||
t.tag->write(ret, t.neg);
|
||||
if (!t.neg) tags.push_back(t.tag);
|
||||
}
|
||||
pending_tags.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
vector<Tag*> tags; ///< Tags opened in the html output
|
||||
vector<NegTag> pending_tags; ///< Tags opened in the tagged string, but not (yet) in the output
|
||||
|
||||
void add(String& ret, const NegTag& tag) {
|
||||
// Cancel out with pending tag?
|
||||
for (int i = (int)pending_tags.size() - 1 ; i >= 0 ; --i) {
|
||||
if (pending_tags[i].tag == tag.tag) {
|
||||
if (pending_tags[i].neg != tag.neg) {
|
||||
pending_tags.erase(pending_tags.begin() + i);
|
||||
return;
|
||||
} else {
|
||||
break; // look no further
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel out with existing tag?
|
||||
if (tag.neg) {
|
||||
for (int i = (int)tags.size() - 1 ; i >= 0 ; --i) {
|
||||
if (tags[i] == tag.tag) {
|
||||
// cancel out with existing tag i, e.g. <b>:
|
||||
// situation was <a><b><c>text
|
||||
// situation will become <a><b><c>text</c></b><c>
|
||||
vector<NegTag> reopen;
|
||||
for (int j = (int)tags.size() - 1 ; j > i ; --j) {
|
||||
pending_tags.push_back(NegTag(tags[j], true)); // close tag, top down
|
||||
tags.pop_back();
|
||||
}
|
||||
pending_tags.push_back(tag); // now close tag i
|
||||
for (int j = i + 1 ; j < (int)tags.size() ; ++j) {
|
||||
pending_tags.push_back(NegTag(tags[j], false)); // reopen later, bottom up
|
||||
tags.pop_back();
|
||||
}
|
||||
tags.resize(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Just insert normally
|
||||
pending_tags.push_back(tag);
|
||||
}
|
||||
vector<Tag*> tags; ///< Tags opened in the html output
|
||||
vector<NegTag> pending_tags; ///< Tags opened in the tagged string, but not (yet) in the output
|
||||
|
||||
void add(String& ret, const NegTag& tag) {
|
||||
// Cancel out with pending tag?
|
||||
for (int i = (int)pending_tags.size() - 1 ; i >= 0 ; --i) {
|
||||
if (pending_tags[i].tag == tag.tag) {
|
||||
if (pending_tags[i].neg != tag.neg) {
|
||||
pending_tags.erase(pending_tags.begin() + i);
|
||||
return;
|
||||
} else {
|
||||
break; // look no further
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cancel out with existing tag?
|
||||
if (tag.neg) {
|
||||
for (int i = (int)tags.size() - 1 ; i >= 0 ; --i) {
|
||||
if (tags[i] == tag.tag) {
|
||||
// cancel out with existing tag i, e.g. <b>:
|
||||
// situation was <a><b><c>text
|
||||
// situation will become <a><b><c>text</c></b><c>
|
||||
vector<NegTag> reopen;
|
||||
for (int j = (int)tags.size() - 1 ; j > i ; --j) {
|
||||
pending_tags.push_back(NegTag(tags[j], true)); // close tag, top down
|
||||
tags.pop_back();
|
||||
}
|
||||
pending_tags.push_back(tag); // now close tag i
|
||||
for (int j = i + 1 ; j < (int)tags.size() ; ++j) {
|
||||
pending_tags.push_back(NegTag(tags[j], false)); // reopen later, bottom up
|
||||
tags.pop_back();
|
||||
}
|
||||
tags.resize(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Just insert normally
|
||||
pending_tags.push_back(tag);
|
||||
}
|
||||
};
|
||||
|
||||
// html-escape a string
|
||||
String html_escape(const String& str) {
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (c == _('\1') || c == _('<')) { // escape <
|
||||
ret += _("<");
|
||||
} else if (c == _('>')) { // escape >
|
||||
ret += _(">");
|
||||
} else if (c == _('&')) { // escape &
|
||||
ret += _("&");
|
||||
} else if (c == _('\'')) { // escape '
|
||||
ret += _("'");
|
||||
} else if (c == _('\"')) { // escape "
|
||||
ret += _(""");
|
||||
} else if (c >= 0x80) { // escape non ascii
|
||||
ret += String(_("&#")) << (int)c << _(';');
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
String ret;
|
||||
FOR_EACH_CONST(c, str) {
|
||||
if (c == _('\1') || c == _('<')) { // escape <
|
||||
ret += _("<");
|
||||
} else if (c == _('>')) { // escape >
|
||||
ret += _(">");
|
||||
} else if (c == _('&')) { // escape &
|
||||
ret += _("&");
|
||||
} else if (c == _('\'')) { // escape '
|
||||
ret += _("'");
|
||||
} else if (c == _('\"')) { // escape "
|
||||
ret += _(""");
|
||||
} else if (c >= 0x80) { // escape non ascii
|
||||
ret += String(_("&#")) << (int)c << _(';');
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// write symbols to html
|
||||
String symbols_to_html(const String& str, SymbolFont& symbol_font, double size) {
|
||||
guard_export_info(_("symbols_to_html"));
|
||||
ExportInfo& ei = *export_info();
|
||||
vector<SymbolFont::DrawableSymbol> symbols;
|
||||
symbol_font.split(str, symbols);
|
||||
String html;
|
||||
FOR_EACH(sym, symbols) {
|
||||
String filename = symbol_font.name() + _("-") + clean_filename(sym.text) + _(".png");
|
||||
map<String,wxSize>::iterator it = ei.exported_images.find(filename);
|
||||
if (it == ei.exported_images.end()) {
|
||||
// save symbol image
|
||||
Image img = symbol_font.getImage(size, sym);
|
||||
wxFileName fn;
|
||||
fn.SetPath(ei.directory_absolute);
|
||||
fn.SetFullName(filename);
|
||||
img.SaveFile(fn.GetFullPath());
|
||||
it = ei.exported_images.insert(make_pair(filename, wxSize(img.GetWidth(), img.GetHeight()))).first;
|
||||
}
|
||||
html += _("<img src='") + filename + _("' alt='") + html_escape(sym.text)
|
||||
+ _("' width='") + (String() << it->second.x)
|
||||
+ _("' height='") + (String() << it->second.y) + _("'>");
|
||||
}
|
||||
return html;
|
||||
guard_export_info(_("symbols_to_html"));
|
||||
ExportInfo& ei = *export_info();
|
||||
vector<SymbolFont::DrawableSymbol> symbols;
|
||||
symbol_font.split(str, symbols);
|
||||
String html;
|
||||
FOR_EACH(sym, symbols) {
|
||||
String filename = symbol_font.name() + _("-") + clean_filename(sym.text) + _(".png");
|
||||
map<String,wxSize>::iterator it = ei.exported_images.find(filename);
|
||||
if (it == ei.exported_images.end()) {
|
||||
// save symbol image
|
||||
Image img = symbol_font.getImage(size, sym);
|
||||
wxFileName fn;
|
||||
fn.SetPath(ei.directory_absolute);
|
||||
fn.SetFullName(filename);
|
||||
img.SaveFile(fn.GetFullPath());
|
||||
it = ei.exported_images.insert(make_pair(filename, wxSize(img.GetWidth(), img.GetHeight()))).first;
|
||||
}
|
||||
html += _("<img src='") + filename + _("' alt='") + html_escape(sym.text)
|
||||
+ _("' width='") + (String() << it->second.x)
|
||||
+ _("' height='") + (String() << it->second.y) + _("'>");
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
String to_html(const String& str_in, const SymbolFontP& symbol_font, double symbol_size) {
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("<b>"), _("</b>")),
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("<b>"), _("</b>")),
|
||||
italic(_("<i>"), _("</i>")),
|
||||
symbol(_("<span class=\"symbol\">"), _("</span>"));
|
||||
TagStack tags;
|
||||
String symbols;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
++i;
|
||||
if (is_substr(str, i, _("b"))) {
|
||||
tags.open (ret, bold);
|
||||
} else if (is_substr(str, i, _("/b"))) {
|
||||
tags.close(ret, bold);
|
||||
} else if (is_substr(str, i, _("i"))) {
|
||||
tags.open (ret, italic);
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} else if (is_substr(str, i, _("sym"))) {
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
}
|
||||
i = skip_tag(str, i-1);
|
||||
} else {
|
||||
// normal character
|
||||
tags.write_pending_tags(ret);
|
||||
++i;
|
||||
if (symbol.opened > 0 && symbol_font) {
|
||||
symbols += c; // write as symbols instead
|
||||
} else {
|
||||
c = untag_char(c);
|
||||
if (c == _('<')) { // escape <
|
||||
ret += _("<");
|
||||
} else if (c == _('&')) { // escape &
|
||||
ret += _("&");
|
||||
} else if (c >= 0x80) { // escape non ascii
|
||||
ret += String(_("&#")) << (int)c << _(';');
|
||||
} else if (c == _('\n')) {
|
||||
ret += _("<br>\n");
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// end of input
|
||||
if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close_all(ret);
|
||||
return ret;
|
||||
TagStack tags;
|
||||
String symbols;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
++i;
|
||||
if (is_substr(str, i, _("b"))) {
|
||||
tags.open (ret, bold);
|
||||
} else if (is_substr(str, i, _("/b"))) {
|
||||
tags.close(ret, bold);
|
||||
} else if (is_substr(str, i, _("i"))) {
|
||||
tags.open (ret, italic);
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} else if (is_substr(str, i, _("sym"))) {
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
}
|
||||
i = skip_tag(str, i-1);
|
||||
} else {
|
||||
// normal character
|
||||
tags.write_pending_tags(ret);
|
||||
++i;
|
||||
if (symbol.opened > 0 && symbol_font) {
|
||||
symbols += c; // write as symbols instead
|
||||
} else {
|
||||
c = untag_char(c);
|
||||
if (c == _('<')) { // escape <
|
||||
ret += _("<");
|
||||
} else if (c == _('&')) { // escape &
|
||||
ret += _("&");
|
||||
} else if (c >= 0x80) { // escape non ascii
|
||||
ret += String(_("&#")) << (int)c << _(';');
|
||||
} else if (c == _('\n')) {
|
||||
ret += _("<br>\n");
|
||||
} else {
|
||||
ret += c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// end of input
|
||||
if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, *symbol_font, symbol_size);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close_all(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// convert a tagged string to html
|
||||
SCRIPT_FUNCTION(to_html) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
// symbol font?
|
||||
SymbolFontP symbol_font;
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, _("symbol font"), font_name) {
|
||||
symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
}
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(to_html(input, symbol_font, symbol_font_size));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
// symbol font?
|
||||
SymbolFontP symbol_font;
|
||||
SCRIPT_OPTIONAL_PARAM_N(String, _("symbol font"), font_name) {
|
||||
symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
}
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(to_html(input, symbol_font, symbol_font_size));
|
||||
}
|
||||
|
||||
// convert a symbol string to html
|
||||
SCRIPT_FUNCTION(symbols_to_html) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_N(String, _("symbol font"), font_name);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
SymbolFontP symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(symbols_to_html(input, *symbol_font, symbol_font_size));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_N(String, _("symbol font"), font_name);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("symbol font size"), symbol_font_size);
|
||||
SymbolFontP symbol_font = SymbolFont::byName(font_name);
|
||||
symbol_font->update(ctx);
|
||||
if (symbol_font_size <= 0) symbol_font_size = 12; // a default
|
||||
SCRIPT_RETURN(symbols_to_html(input, *symbol_font, symbol_font_size));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : BB Code
|
||||
|
||||
String to_bbcode(const String& str_in) {
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("[b]"), _("[/b]")),
|
||||
String str = remove_tag_contents(str_in,_("<sep-soft"));
|
||||
String ret;
|
||||
Tag bold (_("[b]"), _("[/b]")),
|
||||
italic(_("[i]"), _("[/i]"));
|
||||
TagStack tags;
|
||||
String symbols;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
++i;
|
||||
if (is_substr(str, i, _("b"))) {
|
||||
tags.open (ret, bold);
|
||||
} else if (is_substr(str, i, _("/b"))) {
|
||||
tags.close(ret, bold);
|
||||
} else if (is_substr(str, i, _("i"))) {
|
||||
tags.open (ret, italic);
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} /*else if (is_substr(str, i, _("sym"))) {
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
}*/
|
||||
i = skip_tag(str, i-1);
|
||||
} else {
|
||||
// normal character
|
||||
tags.write_pending_tags(ret);
|
||||
++i;
|
||||
// if (symbol.opened > 0 && symbol_font) {
|
||||
// symbols += c; // write as symbols instead
|
||||
// } else {
|
||||
ret += untag_char(c);
|
||||
// }
|
||||
}
|
||||
}
|
||||
// end of input
|
||||
/* if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}*/
|
||||
tags.close_all(ret);
|
||||
return ret;
|
||||
TagStack tags;
|
||||
String symbols;
|
||||
for (size_t i = 0 ; i < str.size() ; ) {
|
||||
Char c = str.GetChar(i);
|
||||
if (c == _('<')) {
|
||||
++i;
|
||||
if (is_substr(str, i, _("b"))) {
|
||||
tags.open (ret, bold);
|
||||
} else if (is_substr(str, i, _("/b"))) {
|
||||
tags.close(ret, bold);
|
||||
} else if (is_substr(str, i, _("i"))) {
|
||||
tags.open (ret, italic);
|
||||
} else if (is_substr(str, i, _("/i"))) {
|
||||
tags.close(ret, italic);
|
||||
} /*else if (is_substr(str, i, _("sym"))) {
|
||||
tags.open (ret, symbol);
|
||||
} else if (is_substr(str, i, _("/sym"))) {
|
||||
if (!symbols.empty()) {
|
||||
// write symbols in a special way
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}
|
||||
tags.close(ret, symbol);
|
||||
}*/
|
||||
i = skip_tag(str, i-1);
|
||||
} else {
|
||||
// normal character
|
||||
tags.write_pending_tags(ret);
|
||||
++i;
|
||||
// if (symbol.opened > 0 && symbol_font) {
|
||||
// symbols += c; // write as symbols instead
|
||||
// } else {
|
||||
ret += untag_char(c);
|
||||
// }
|
||||
}
|
||||
}
|
||||
// end of input
|
||||
/* if (!symbols.empty()) {
|
||||
tags.write_pending_tags(ret);
|
||||
ret += symbols_to_html(symbols, symbol_font);
|
||||
symbols.clear();
|
||||
}*/
|
||||
tags.close_all(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// convert a tagged string to BBCode
|
||||
SCRIPT_FUNCTION(to_bbcode) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
throw "TODO";
|
||||
// SCRIPT_RETURN(to_bbcode(input, symbol_font));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
throw "TODO";
|
||||
// SCRIPT_RETURN(to_bbcode(input, symbol_font));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Text
|
||||
|
||||
// convert a tagged string to plain text
|
||||
SCRIPT_FUNCTION(to_text) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(untag_hide_sep(input));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_RETURN(untag_hide_sep(input));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Files
|
||||
|
||||
// copy from source package -> destination directory, return new filename (relative)
|
||||
SCRIPT_FUNCTION(copy_file) {
|
||||
guard_export_info(_("copy_file"));
|
||||
SCRIPT_PARAM_C(String, input); // file to copy
|
||||
// output path
|
||||
String out_name = input;
|
||||
String out_path = get_export_full_path(out_name);
|
||||
// copy
|
||||
ExportInfo& ei = *export_info();
|
||||
InputStreamP in = ei.export_template->openIn(input);
|
||||
wxFileOutputStream out(out_path);
|
||||
if (!out.Ok()) throw Error(_("Unable to open file '") + out_path + _("' for output"));
|
||||
out.Write(*in);
|
||||
SCRIPT_RETURN(out_name);
|
||||
guard_export_info(_("copy_file"));
|
||||
SCRIPT_PARAM_C(String, input); // file to copy
|
||||
// output path
|
||||
String out_name = input;
|
||||
String out_path = get_export_full_path(out_name);
|
||||
// copy
|
||||
ExportInfo& ei = *export_info();
|
||||
InputStreamP in = ei.export_template->openIn(input);
|
||||
wxFileOutputStream out(out_path);
|
||||
if (!out.Ok()) throw Error(_("Unable to open file '") + out_path + _("' for output"));
|
||||
out.Write(*in);
|
||||
SCRIPT_RETURN(out_name);
|
||||
}
|
||||
|
||||
// write a file to the destination directory
|
||||
SCRIPT_FUNCTION(write_text_file) {
|
||||
guard_export_info(_("write_text_file"));
|
||||
SCRIPT_PARAM_C(String, input); // text to write
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
// output path
|
||||
String out_path = get_export_full_path(file);
|
||||
// write
|
||||
wxFileOutputStream out(out_path);
|
||||
if (!out.Ok()) throw Error(_("Unable to open file '") + out_path + _("' for output"));
|
||||
wxTextOutputStream tout(out);
|
||||
tout.WriteString(BYTE_ORDER_MARK);
|
||||
tout.WriteString(input);
|
||||
SCRIPT_RETURN(file);
|
||||
guard_export_info(_("write_text_file"));
|
||||
SCRIPT_PARAM_C(String, input); // text to write
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
// output path
|
||||
String out_path = get_export_full_path(file);
|
||||
// write
|
||||
wxFileOutputStream out(out_path);
|
||||
if (!out.Ok()) throw Error(_("Unable to open file '") + out_path + _("' for output"));
|
||||
wxTextOutputStream tout(out);
|
||||
tout.WriteString(BYTE_ORDER_MARK);
|
||||
tout.WriteString(input);
|
||||
SCRIPT_RETURN(file);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(write_image_file) {
|
||||
guard_export_info(_("write_image_file"));
|
||||
// output path
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
String out_path = get_export_full_path(file);
|
||||
// duplicates?
|
||||
ExportInfo& ei = *export_info();
|
||||
if (ei.exported_images.find(file) != ei.exported_images.end()) {
|
||||
SCRIPT_RETURN(file); // already written an image with this name
|
||||
}
|
||||
// get image
|
||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, width);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, height);
|
||||
ScriptObject<CardP>* card = dynamic_cast<ScriptObject<CardP>*>(input.get()); // is it a card?
|
||||
Image image;
|
||||
GeneratedImage::Options options(width, height, ei.export_template.get(), ei.set.get());
|
||||
if (card) {
|
||||
image = conform_image(export_bitmap(ei.set, card->getValue()).ConvertToImage(), options);
|
||||
} else {
|
||||
image = image_from_script(input)->generateConform(options);
|
||||
}
|
||||
if (!image.Ok()) throw Error(_("Unable to generate image for file ") + file);
|
||||
// write
|
||||
image.SaveFile(out_path);
|
||||
ei.exported_images.insert(make_pair(file, wxSize(image.GetWidth(), image.GetHeight())));
|
||||
SCRIPT_RETURN(file);
|
||||
guard_export_info(_("write_image_file"));
|
||||
// output path
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
String out_path = get_export_full_path(file);
|
||||
// duplicates?
|
||||
ExportInfo& ei = *export_info();
|
||||
if (ei.exported_images.find(file) != ei.exported_images.end()) {
|
||||
SCRIPT_RETURN(file); // already written an image with this name
|
||||
}
|
||||
// get image
|
||||
SCRIPT_PARAM_C(ScriptValueP, input);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, width);
|
||||
SCRIPT_OPTIONAL_PARAM_(int, height);
|
||||
ScriptObject<CardP>* card = dynamic_cast<ScriptObject<CardP>*>(input.get()); // is it a card?
|
||||
Image image;
|
||||
GeneratedImage::Options options(width, height, ei.export_template.get(), ei.set.get());
|
||||
if (card) {
|
||||
image = conform_image(export_bitmap(ei.set, card->getValue()).ConvertToImage(), options);
|
||||
} else {
|
||||
image = image_from_script(input)->generateConform(options);
|
||||
}
|
||||
if (!image.Ok()) throw Error(_("Unable to generate image for file ") + file);
|
||||
// write
|
||||
image.SaveFile(out_path);
|
||||
ei.exported_images.insert(make_pair(file, wxSize(image.GetWidth(), image.GetHeight())));
|
||||
SCRIPT_RETURN(file);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(write_set_file) {
|
||||
guard_export_info(_("write_set_file"));
|
||||
// output path
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
String out_path = get_export_full_path(file);
|
||||
// export
|
||||
SCRIPT_PARAM_C(Set*, set);
|
||||
set->saveCopy(out_path); // TODO: use export_set instead?
|
||||
SCRIPT_RETURN(file);
|
||||
|
||||
guard_export_info(_("write_set_file"));
|
||||
// output path
|
||||
SCRIPT_PARAM(String, file); // file to write to
|
||||
String out_path = get_export_full_path(file);
|
||||
// export
|
||||
SCRIPT_PARAM_C(Set*, set);
|
||||
set->saveCopy(out_path); // TODO: use export_set instead?
|
||||
SCRIPT_RETURN(file);
|
||||
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(sanitize) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
//TODO
|
||||
SCRIPT_RETURN(input);
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
//TODO
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_export_functions(Context& ctx) {
|
||||
ctx.setVariable(_("to html"), script_to_html);
|
||||
ctx.setVariable(_("symbols to html"), script_symbols_to_html);
|
||||
ctx.setVariable(_("to text"), script_to_text);
|
||||
ctx.setVariable(_("copy file"), script_copy_file);
|
||||
ctx.setVariable(_("write text file"), script_write_text_file);
|
||||
ctx.setVariable(_("write image file"), script_write_image_file);
|
||||
ctx.setVariable(_("write set file"), script_write_set_file);
|
||||
ctx.setVariable(_("sanitize"), script_sanitize);
|
||||
ctx.setVariable(_("to html"), script_to_html);
|
||||
ctx.setVariable(_("symbols to html"), script_symbols_to_html);
|
||||
ctx.setVariable(_("to text"), script_to_text);
|
||||
ctx.setVariable(_("copy file"), script_copy_file);
|
||||
ctx.setVariable(_("write text file"), script_write_text_file);
|
||||
ctx.setVariable(_("write image file"), script_write_image_file);
|
||||
ctx.setVariable(_("write set file"), script_write_set_file);
|
||||
ctx.setVariable(_("sanitize"), script_sanitize);
|
||||
}
|
||||
|
||||
@@ -31,14 +31,14 @@ void init_script_construction_functions(Context& ctx);
|
||||
|
||||
/// Initialize all built in functions for a context
|
||||
inline void init_script_functions(Context& ctx) {
|
||||
init_script_basic_functions(ctx);
|
||||
init_script_regex_functions(ctx);
|
||||
init_script_image_functions(ctx);
|
||||
init_script_editor_functions(ctx);
|
||||
init_script_export_functions(ctx);
|
||||
init_script_english_functions(ctx);
|
||||
init_script_spelling_functions(ctx);
|
||||
init_script_construction_functions(ctx);
|
||||
init_script_basic_functions(ctx);
|
||||
init_script_regex_functions(ctx);
|
||||
init_script_image_functions(ctx);
|
||||
init_script_editor_functions(ctx);
|
||||
init_script_export_functions(ctx);
|
||||
init_script_english_functions(ctx);
|
||||
init_script_spelling_functions(ctx);
|
||||
init_script_construction_functions(ctx);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+147
-147
@@ -26,212 +26,212 @@ void parse_enum(const String&, ImageCombine& out);
|
||||
// ----------------------------------------------------------------------------- : Utility
|
||||
|
||||
template <> inline GeneratedImageP from_script<GeneratedImageP>(const ScriptValueP& value) {
|
||||
return image_from_script(value);
|
||||
return image_from_script(value);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(to_image) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return input;
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return input;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Image functions
|
||||
|
||||
SCRIPT_FUNCTION(linear_blend) {
|
||||
SCRIPT_PARAM(GeneratedImageP, image1);
|
||||
SCRIPT_PARAM(GeneratedImageP, image2);
|
||||
SCRIPT_PARAM(double, x1); SCRIPT_PARAM(double, y1);
|
||||
SCRIPT_PARAM(double, x2); SCRIPT_PARAM(double, y2);
|
||||
return intrusive(new LinearBlendImage(image1, image2, x1,y1, x2,y2));
|
||||
SCRIPT_PARAM(GeneratedImageP, image1);
|
||||
SCRIPT_PARAM(GeneratedImageP, image2);
|
||||
SCRIPT_PARAM(double, x1); SCRIPT_PARAM(double, y1);
|
||||
SCRIPT_PARAM(double, x2); SCRIPT_PARAM(double, y2);
|
||||
return intrusive(new LinearBlendImage(image1, image2, x1,y1, x2,y2));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(masked_blend) {
|
||||
SCRIPT_PARAM(GeneratedImageP, light);
|
||||
SCRIPT_PARAM(GeneratedImageP, dark);
|
||||
SCRIPT_PARAM(GeneratedImageP, mask);
|
||||
return intrusive(new MaskedBlendImage(light, dark, mask));
|
||||
SCRIPT_PARAM(GeneratedImageP, light);
|
||||
SCRIPT_PARAM(GeneratedImageP, dark);
|
||||
SCRIPT_PARAM(GeneratedImageP, mask);
|
||||
return intrusive(new MaskedBlendImage(light, dark, mask));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(combine_blend) {
|
||||
SCRIPT_PARAM(String, combine);
|
||||
SCRIPT_PARAM(GeneratedImageP, image1);
|
||||
SCRIPT_PARAM(GeneratedImageP, image2);
|
||||
ImageCombine image_combine;
|
||||
parse_enum(combine, image_combine);
|
||||
return intrusive(new CombineBlendImage(image1, image2, image_combine));
|
||||
SCRIPT_PARAM(String, combine);
|
||||
SCRIPT_PARAM(GeneratedImageP, image1);
|
||||
SCRIPT_PARAM(GeneratedImageP, image2);
|
||||
ImageCombine image_combine;
|
||||
parse_enum(combine, image_combine);
|
||||
return intrusive(new CombineBlendImage(image1, image2, image_combine));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(set_mask) {
|
||||
SCRIPT_PARAM(GeneratedImageP, image);
|
||||
SCRIPT_PARAM(GeneratedImageP, mask);
|
||||
return intrusive(new SetMaskImage(image, mask));
|
||||
SCRIPT_PARAM(GeneratedImageP, image);
|
||||
SCRIPT_PARAM(GeneratedImageP, mask);
|
||||
return intrusive(new SetMaskImage(image, mask));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(set_alpha) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM(double, alpha);
|
||||
return intrusive(new SetAlphaImage(input, alpha));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM(double, alpha);
|
||||
return intrusive(new SetAlphaImage(input, alpha));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(set_combine) {
|
||||
SCRIPT_PARAM(String, combine);
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
ImageCombine image_combine;
|
||||
parse_enum(combine, image_combine);
|
||||
return intrusive(new SetCombineImage(input, image_combine));
|
||||
SCRIPT_PARAM(String, combine);
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
ImageCombine image_combine;
|
||||
parse_enum(combine, image_combine);
|
||||
return intrusive(new SetCombineImage(input, image_combine));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(saturate) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM(double, amount);
|
||||
return intrusive(new SaturateImage(input, amount));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM(double, amount);
|
||||
return intrusive(new SaturateImage(input, amount));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(invert_image) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new InvertImage(input));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new InvertImage(input));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(recolor_image) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_OPTIONAL_PARAM(Color, red) {
|
||||
SCRIPT_PARAM(Color, green);
|
||||
SCRIPT_PARAM(Color, blue);
|
||||
SCRIPT_PARAM_DEFAULT(Color, white, *wxWHITE);
|
||||
return intrusive(new RecolorImage2(input,red,green,blue,white));
|
||||
} else {
|
||||
SCRIPT_PARAM(Color, color);
|
||||
return intrusive(new RecolorImage(input,color));
|
||||
}
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_OPTIONAL_PARAM(Color, red) {
|
||||
SCRIPT_PARAM(Color, green);
|
||||
SCRIPT_PARAM(Color, blue);
|
||||
SCRIPT_PARAM_DEFAULT(Color, white, *wxWHITE);
|
||||
return intrusive(new RecolorImage2(input,red,green,blue,white));
|
||||
} else {
|
||||
SCRIPT_PARAM(Color, color);
|
||||
return intrusive(new RecolorImage(input,color));
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(enlarge) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(double, _("border size"), border_size);
|
||||
return intrusive(new EnlargeImage(input, border_size));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(double, _("border size"), border_size);
|
||||
return intrusive(new EnlargeImage(input, border_size));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(crop) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(int, _("width"), width);
|
||||
SCRIPT_PARAM_N(int, _("height"), height);
|
||||
SCRIPT_PARAM_N(double, _("offset x"), offset_x);
|
||||
SCRIPT_PARAM_N(double, _("offset y"), offset_y);
|
||||
return intrusive(new CropImage(input, width, height, offset_x, offset_y));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(int, _("width"), width);
|
||||
SCRIPT_PARAM_N(int, _("height"), height);
|
||||
SCRIPT_PARAM_N(double, _("offset x"), offset_x);
|
||||
SCRIPT_PARAM_N(double, _("offset y"), offset_y);
|
||||
return intrusive(new CropImage(input, width, height, offset_x, offset_y));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(flip_horizontal) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new FlipImageHorizontal(input));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new FlipImageHorizontal(input));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(flip_vertical) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new FlipImageVertical(input));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
return intrusive(new FlipImageVertical(input));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(rotate) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(Degrees, _("angle"), angle);
|
||||
return intrusive(new RotateImage(input,deg_to_rad(angle)));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_PARAM_N(Degrees, _("angle"), angle);
|
||||
return intrusive(new RotateImage(input,deg_to_rad(angle)));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(drop_shadow) {
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset x"), offset_x);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset y"), offset_y);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("alpha"), alpha);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("blur radius"), blur_radius);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Color, _("color"), color);
|
||||
return intrusive(new DropShadowImage(input, offset_x, offset_y, alpha, blur_radius, color));
|
||||
SCRIPT_PARAM_C(GeneratedImageP, input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset x"), offset_x);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("offset y"), offset_y);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("alpha"), alpha);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(double, _("blur radius"), blur_radius);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Color, _("color"), color);
|
||||
return intrusive(new DropShadowImage(input, offset_x, offset_y, alpha, blur_radius, color));
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(symbol_variation) {
|
||||
// find symbol
|
||||
SCRIPT_PARAM(ScriptValueP, symbol); // TODO: change to input?
|
||||
ScriptObject<ValueP>* valueO = dynamic_cast<ScriptObject<ValueP>*>(symbol.get());
|
||||
SymbolValue* value = valueO ? dynamic_cast<SymbolValue*>(valueO->getValue().get()) : nullptr;
|
||||
String filename;
|
||||
if (value) {
|
||||
filename = value->filename;
|
||||
} else if (valueO) {
|
||||
throw ScriptErrorConversion(valueO->typeName(), _TYPE_("symbol" ));
|
||||
} else {
|
||||
filename = from_script<String>(symbol);
|
||||
}
|
||||
// known variation?
|
||||
SCRIPT_OPTIONAL_PARAM_(String, variation)
|
||||
if (value && variation_) {
|
||||
// find style
|
||||
SCRIPT_PARAM(Set*, set);
|
||||
SCRIPT_OPTIONAL_PARAM_(CardP, card);
|
||||
SymbolStyleP style = dynamic_pointer_cast<SymbolStyle>(set->stylesheetForP(card)->styleFor(value->fieldP));
|
||||
if (!style) throw InternalError(_("Symbol value has a style of the wrong type"));
|
||||
// find variation
|
||||
FOR_EACH(v, style->variations) {
|
||||
if (v->name == variation) {
|
||||
// found it
|
||||
return intrusive(new SymbolToImage(value, filename, value->last_update, v));
|
||||
}
|
||||
}
|
||||
throw ScriptError(_("Variation of symbol not found ('") + variation + _("')"));
|
||||
} else {
|
||||
// custom variation
|
||||
SCRIPT_PARAM_N(double, _("border radius"), border_radius);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String, _("fill type"), fill_type);
|
||||
SymbolVariationP var(new SymbolVariation);
|
||||
var->border_radius = border_radius;
|
||||
if (fill_type == _("solid") || fill_type.empty()) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color"), fill_color);
|
||||
SCRIPT_PARAM_N(Color, _("border color"), border_color);
|
||||
var->filter = intrusive(new SolidFillSymbolFilter(fill_color, border_color));
|
||||
} else if (fill_type == _("linear gradient")) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
|
||||
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
|
||||
SCRIPT_PARAM_N(double, _("center x"), center_x);
|
||||
SCRIPT_PARAM_N(double, _("center y"), center_y);
|
||||
SCRIPT_PARAM_N(double, _("end x"), end_x);
|
||||
SCRIPT_PARAM_N(double, _("end y"), end_y);
|
||||
var->filter = intrusive(new LinearGradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2
|
||||
,center_x, center_y, end_x, end_y));
|
||||
} else if (fill_type == _("radial gradient")) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
|
||||
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
|
||||
var->filter = intrusive(new RadialGradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2));
|
||||
} else {
|
||||
throw ScriptError(_("Unknown fill type for symbol_variation: ") + fill_type);
|
||||
}
|
||||
return intrusive(new SymbolToImage(value, filename, value ? value->last_update : Age(0), var));
|
||||
}
|
||||
// find symbol
|
||||
SCRIPT_PARAM(ScriptValueP, symbol); // TODO: change to input?
|
||||
ScriptObject<ValueP>* valueO = dynamic_cast<ScriptObject<ValueP>*>(symbol.get());
|
||||
SymbolValue* value = valueO ? dynamic_cast<SymbolValue*>(valueO->getValue().get()) : nullptr;
|
||||
String filename;
|
||||
if (value) {
|
||||
filename = value->filename;
|
||||
} else if (valueO) {
|
||||
throw ScriptErrorConversion(valueO->typeName(), _TYPE_("symbol" ));
|
||||
} else {
|
||||
filename = from_script<String>(symbol);
|
||||
}
|
||||
// known variation?
|
||||
SCRIPT_OPTIONAL_PARAM_(String, variation)
|
||||
if (value && variation_) {
|
||||
// find style
|
||||
SCRIPT_PARAM(Set*, set);
|
||||
SCRIPT_OPTIONAL_PARAM_(CardP, card);
|
||||
SymbolStyleP style = dynamic_pointer_cast<SymbolStyle>(set->stylesheetForP(card)->styleFor(value->fieldP));
|
||||
if (!style) throw InternalError(_("Symbol value has a style of the wrong type"));
|
||||
// find variation
|
||||
FOR_EACH(v, style->variations) {
|
||||
if (v->name == variation) {
|
||||
// found it
|
||||
return intrusive(new SymbolToImage(value, filename, value->last_update, v));
|
||||
}
|
||||
}
|
||||
throw ScriptError(_("Variation of symbol not found ('") + variation + _("')"));
|
||||
} else {
|
||||
// custom variation
|
||||
SCRIPT_PARAM_N(double, _("border radius"), border_radius);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String, _("fill type"), fill_type);
|
||||
SymbolVariationP var(new SymbolVariation);
|
||||
var->border_radius = border_radius;
|
||||
if (fill_type == _("solid") || fill_type.empty()) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color"), fill_color);
|
||||
SCRIPT_PARAM_N(Color, _("border color"), border_color);
|
||||
var->filter = intrusive(new SolidFillSymbolFilter(fill_color, border_color));
|
||||
} else if (fill_type == _("linear gradient")) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
|
||||
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
|
||||
SCRIPT_PARAM_N(double, _("center x"), center_x);
|
||||
SCRIPT_PARAM_N(double, _("center y"), center_y);
|
||||
SCRIPT_PARAM_N(double, _("end x"), end_x);
|
||||
SCRIPT_PARAM_N(double, _("end y"), end_y);
|
||||
var->filter = intrusive(new LinearGradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2
|
||||
,center_x, center_y, end_x, end_y));
|
||||
} else if (fill_type == _("radial gradient")) {
|
||||
SCRIPT_PARAM_N(Color, _("fill color 1"), fill_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("border color 1"), border_color_1);
|
||||
SCRIPT_PARAM_N(Color, _("fill color 2"), fill_color_2);
|
||||
SCRIPT_PARAM_N(Color, _("border color 2"), border_color_2);
|
||||
var->filter = intrusive(new RadialGradientSymbolFilter(fill_color_1, border_color_1, fill_color_2, border_color_2));
|
||||
} else {
|
||||
throw ScriptError(_("Unknown fill type for symbol_variation: ") + fill_type);
|
||||
}
|
||||
return intrusive(new SymbolToImage(value, filename, value ? value->last_update : Age(0), var));
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(built_in_image) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
return intrusive(new BuiltInImage(input));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
return intrusive(new BuiltInImage(input));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_image_functions(Context& ctx) {
|
||||
ctx.setVariable(_("to image"), script_to_image);
|
||||
ctx.setVariable(_("linear blend"), script_linear_blend);
|
||||
ctx.setVariable(_("masked blend"), script_masked_blend);
|
||||
ctx.setVariable(_("combine blend"), script_combine_blend);
|
||||
ctx.setVariable(_("set mask"), script_set_mask);
|
||||
ctx.setVariable(_("set alpha"), script_set_alpha);
|
||||
ctx.setVariable(_("set combine"), script_set_combine);
|
||||
ctx.setVariable(_("saturate"), script_saturate);
|
||||
ctx.setVariable(_("invert image"), script_invert_image);
|
||||
ctx.setVariable(_("recolor image"), script_recolor_image);
|
||||
ctx.setVariable(_("enlarge"), script_enlarge);
|
||||
ctx.setVariable(_("crop"), script_crop);
|
||||
ctx.setVariable(_("flip horizontal"), script_flip_horizontal);
|
||||
ctx.setVariable(_("flip vertical"), script_flip_vertical);
|
||||
ctx.setVariable(_("rotate"), script_rotate);
|
||||
ctx.setVariable(_("drop shadow"), script_drop_shadow);
|
||||
ctx.setVariable(_("symbol variation"), script_symbol_variation);
|
||||
ctx.setVariable(_("built in image"), script_built_in_image);
|
||||
ctx.setVariable(_("to image"), script_to_image);
|
||||
ctx.setVariable(_("linear blend"), script_linear_blend);
|
||||
ctx.setVariable(_("masked blend"), script_masked_blend);
|
||||
ctx.setVariable(_("combine blend"), script_combine_blend);
|
||||
ctx.setVariable(_("set mask"), script_set_mask);
|
||||
ctx.setVariable(_("set alpha"), script_set_alpha);
|
||||
ctx.setVariable(_("set combine"), script_set_combine);
|
||||
ctx.setVariable(_("saturate"), script_saturate);
|
||||
ctx.setVariable(_("invert image"), script_invert_image);
|
||||
ctx.setVariable(_("recolor image"), script_recolor_image);
|
||||
ctx.setVariable(_("enlarge"), script_enlarge);
|
||||
ctx.setVariable(_("crop"), script_crop);
|
||||
ctx.setVariable(_("flip horizontal"), script_flip_horizontal);
|
||||
ctx.setVariable(_("flip vertical"), script_flip_vertical);
|
||||
ctx.setVariable(_("rotate"), script_rotate);
|
||||
ctx.setVariable(_("drop shadow"), script_drop_shadow);
|
||||
ctx.setVariable(_("symbol variation"), script_symbol_variation);
|
||||
ctx.setVariable(_("built in image"), script_built_in_image);
|
||||
}
|
||||
|
||||
+185
-185
@@ -20,238 +20,238 @@ DECLARE_TYPEOF_COLLECTION(pair<Variable COMMA ScriptValueP>);
|
||||
/// A regular expression for use in a script
|
||||
class ScriptRegex : public ScriptValue, public Regex {
|
||||
public:
|
||||
virtual ScriptType type() const { return SCRIPT_REGEX; }
|
||||
virtual String typeName() const { return _("regex"); }
|
||||
|
||||
ScriptRegex(const String& code) {
|
||||
assign(code);
|
||||
}
|
||||
|
||||
/// Match only if in_context also matches
|
||||
bool matches(Results& results, const String& str, String::const_iterator begin, const ScriptRegexP& in_context) {
|
||||
if (!in_context) {
|
||||
return matches(results, begin, str.end());
|
||||
} else {
|
||||
while (matches(results, begin, str.end())) {
|
||||
Results::const_reference match = results[0];
|
||||
String context_str(str.begin(), match.first); // before
|
||||
context_str += _("<match>");
|
||||
context_str.append(match.second, str.end()); // after
|
||||
if (in_context->matches(context_str)) {
|
||||
return true; // the context matches, done
|
||||
}
|
||||
begin = match.second; // skip
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
using Regex::matches;
|
||||
virtual ScriptType type() const { return SCRIPT_REGEX; }
|
||||
virtual String typeName() const { return _("regex"); }
|
||||
|
||||
ScriptRegex(const String& code) {
|
||||
assign(code);
|
||||
}
|
||||
|
||||
/// Match only if in_context also matches
|
||||
bool matches(Results& results, const String& str, String::const_iterator begin, const ScriptRegexP& in_context) {
|
||||
if (!in_context) {
|
||||
return matches(results, begin, str.end());
|
||||
} else {
|
||||
while (matches(results, begin, str.end())) {
|
||||
Results::const_reference match = results[0];
|
||||
String context_str(str.begin(), match.first); // before
|
||||
context_str += _("<match>");
|
||||
context_str.append(match.second, str.end()); // after
|
||||
if (in_context->matches(context_str)) {
|
||||
return true; // the context matches, done
|
||||
}
|
||||
begin = match.second; // skip
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
using Regex::matches;
|
||||
};
|
||||
|
||||
ScriptRegexP regex_from_script(const ScriptValueP& value) {
|
||||
// is it a regex already?
|
||||
ScriptRegexP regex = dynamic_pointer_cast<ScriptRegex>(value);
|
||||
if (!regex) {
|
||||
// TODO: introduce some kind of caching?
|
||||
regex = intrusive(new ScriptRegex(*value));
|
||||
}
|
||||
return regex;
|
||||
// is it a regex already?
|
||||
ScriptRegexP regex = dynamic_pointer_cast<ScriptRegex>(value);
|
||||
if (!regex) {
|
||||
// TODO: introduce some kind of caching?
|
||||
regex = intrusive(new ScriptRegex(*value));
|
||||
}
|
||||
return regex;
|
||||
}
|
||||
|
||||
template <> inline ScriptRegexP from_script<ScriptRegexP>(const ScriptValueP& value) {
|
||||
return regex_from_script(value);
|
||||
return regex_from_script(value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rules : regex replace
|
||||
|
||||
struct RegexReplacer {
|
||||
ScriptRegexP match; ///< Regex to match
|
||||
ScriptRegexP context; ///< Match only in a given context, optional
|
||||
String replacement_string; ///< Replacement
|
||||
ScriptValueP replacement_function; ///< Replacement function instead of a simple string, optional
|
||||
bool recursive; ///< Recurse into the replacement
|
||||
|
||||
String apply(Context& ctx, const String& input, int level = 0) const {
|
||||
String ret;
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, context)) {
|
||||
// for each match ...
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
ret.append(start, pos.first); // everything before the match position stays
|
||||
// determine replacement
|
||||
String inside;
|
||||
if (replacement_function) {
|
||||
// set match results in context
|
||||
for (UInt sub = 0 ; sub < results.size() ; ++sub) {
|
||||
String name = sub == 0 ? _("input") : String(_("_")) << sub;
|
||||
ctx.setVariable(name, to_script(results.str(sub)));
|
||||
}
|
||||
// call
|
||||
inside = replacement_function->eval(ctx)->toString();
|
||||
} else {
|
||||
inside = results.format(replacement_string);
|
||||
}
|
||||
// append replaced inside
|
||||
if (recursive && level < 20) {
|
||||
ret += apply(ctx, inside, level + 1);
|
||||
} else {
|
||||
ret += inside;
|
||||
}
|
||||
start = pos.second;
|
||||
}
|
||||
ret.append(start, input.end());
|
||||
return ret;
|
||||
}
|
||||
ScriptRegexP match; ///< Regex to match
|
||||
ScriptRegexP context; ///< Match only in a given context, optional
|
||||
String replacement_string; ///< Replacement
|
||||
ScriptValueP replacement_function; ///< Replacement function instead of a simple string, optional
|
||||
bool recursive; ///< Recurse into the replacement
|
||||
|
||||
String apply(Context& ctx, const String& input, int level = 0) const {
|
||||
String ret;
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, context)) {
|
||||
// for each match ...
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
ret.append(start, pos.first); // everything before the match position stays
|
||||
// determine replacement
|
||||
String inside;
|
||||
if (replacement_function) {
|
||||
// set match results in context
|
||||
for (UInt sub = 0 ; sub < results.size() ; ++sub) {
|
||||
String name = sub == 0 ? _("input") : String(_("_")) << sub;
|
||||
ctx.setVariable(name, to_script(results.str(sub)));
|
||||
}
|
||||
// call
|
||||
inside = replacement_function->eval(ctx)->toString();
|
||||
} else {
|
||||
inside = results.format(replacement_string);
|
||||
}
|
||||
// append replaced inside
|
||||
if (recursive && level < 20) {
|
||||
ret += apply(ctx, inside, level + 1);
|
||||
} else {
|
||||
ret += inside;
|
||||
}
|
||||
start = pos.second;
|
||||
}
|
||||
ret.append(start, input.end());
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
SCRIPT_FUNCTION_WITH_SIMPLIFY(replace) {
|
||||
// construct replacer
|
||||
RegexReplacer replacer;
|
||||
replacer.match = from_script<ScriptRegexP>(ctx.getVariable(SCRIPT_VAR_match), SCRIPT_VAR_match);
|
||||
if (ctx.getVariableOpt(SCRIPT_VAR_in_context)) {
|
||||
replacer.context = from_script<ScriptRegexP>(ctx.getVariableOpt(SCRIPT_VAR_in_context), SCRIPT_VAR_in_context);
|
||||
}
|
||||
if (ctx.getVariableOpt(SCRIPT_VAR_recursive)) {
|
||||
replacer.recursive = from_script<bool>(ctx.getVariableOpt(SCRIPT_VAR_recursive), SCRIPT_VAR_recursive);
|
||||
} else {
|
||||
replacer.recursive = false;
|
||||
}
|
||||
replacer.replacement_function = ctx.getVariable(SCRIPT_VAR_replace);
|
||||
if (replacer.replacement_function->type() != SCRIPT_FUNCTION) {
|
||||
replacer.replacement_string = replacer.replacement_function->toString();
|
||||
replacer.replacement_function = ScriptValueP();
|
||||
}
|
||||
// run
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
if (replacer.context || replacer.replacement_function || replacer.recursive) {
|
||||
SCRIPT_RETURN(replacer.apply(ctx, input));
|
||||
} else {
|
||||
// simple replacing
|
||||
replacer.match->replace_all(&input, replacer.replacement_string);
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
// construct replacer
|
||||
RegexReplacer replacer;
|
||||
replacer.match = from_script<ScriptRegexP>(ctx.getVariable(SCRIPT_VAR_match), SCRIPT_VAR_match);
|
||||
if (ctx.getVariableOpt(SCRIPT_VAR_in_context)) {
|
||||
replacer.context = from_script<ScriptRegexP>(ctx.getVariableOpt(SCRIPT_VAR_in_context), SCRIPT_VAR_in_context);
|
||||
}
|
||||
if (ctx.getVariableOpt(SCRIPT_VAR_recursive)) {
|
||||
replacer.recursive = from_script<bool>(ctx.getVariableOpt(SCRIPT_VAR_recursive), SCRIPT_VAR_recursive);
|
||||
} else {
|
||||
replacer.recursive = false;
|
||||
}
|
||||
replacer.replacement_function = ctx.getVariable(SCRIPT_VAR_replace);
|
||||
if (replacer.replacement_function->type() != SCRIPT_FUNCTION) {
|
||||
replacer.replacement_string = replacer.replacement_function->toString();
|
||||
replacer.replacement_function = ScriptValueP();
|
||||
}
|
||||
// run
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
if (replacer.context || replacer.replacement_function || replacer.recursive) {
|
||||
SCRIPT_RETURN(replacer.apply(ctx, input));
|
||||
} else {
|
||||
// simple replacing
|
||||
replacer.match->replace_all(&input, replacer.replacement_string);
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
}
|
||||
SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(replace) {
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rules : regex filter
|
||||
|
||||
SCRIPT_FUNCTION_WITH_SIMPLIFY(filter_text) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_OPTIONAL_PARAM_C_(ScriptRegexP, in_context);
|
||||
String ret;
|
||||
// find all matches
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, in_context)) {
|
||||
// match, append to result
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
ret.append(pos.first, pos.second); // the match
|
||||
start = pos.second;
|
||||
}
|
||||
SCRIPT_RETURN(ret);
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_OPTIONAL_PARAM_C_(ScriptRegexP, in_context);
|
||||
String ret;
|
||||
// find all matches
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, in_context)) {
|
||||
// match, append to result
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
ret.append(pos.first, pos.second); // the match
|
||||
start = pos.second;
|
||||
}
|
||||
SCRIPT_RETURN(ret);
|
||||
}
|
||||
SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(filter_text) {
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rules : regex break
|
||||
|
||||
SCRIPT_FUNCTION_WITH_SIMPLIFY(break_text) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_OPTIONAL_PARAM_C_(ScriptRegexP, in_context);
|
||||
ScriptCustomCollectionP ret(new ScriptCustomCollection);
|
||||
// find all matches
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, in_context)) {
|
||||
// match, append to result
|
||||
ret->value.push_back(to_script(results.str()));
|
||||
start = results[0].second;
|
||||
}
|
||||
return ret;
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_OPTIONAL_PARAM_C_(ScriptRegexP, in_context);
|
||||
ScriptCustomCollectionP ret(new ScriptCustomCollection);
|
||||
// find all matches
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, input, start, in_context)) {
|
||||
// match, append to result
|
||||
ret->value.push_back(to_script(results.str()));
|
||||
start = results[0].second;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(break_text) {
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match || b.first == SCRIPT_VAR_in_context) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
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
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, start, input.end())) {
|
||||
// match, append the part before it to the result
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
if (include_empty || pos.first != start) {
|
||||
ret->value.push_back(to_script( String(start,pos.first) ));
|
||||
}
|
||||
start = pos.second;
|
||||
}
|
||||
if (include_empty || start != input.end()) {
|
||||
ret->value.push_back(to_script( String(start,input.end()) ));
|
||||
}
|
||||
return ret;
|
||||
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
|
||||
String::const_iterator start = input.begin();
|
||||
ScriptRegex::Results results;
|
||||
while (match->matches(results, start, input.end())) {
|
||||
// match, append the part before it to the result
|
||||
ScriptRegex::Results::const_reference pos = results[0];
|
||||
if (include_empty || pos.first != start) {
|
||||
ret->value.push_back(to_script( String(start,pos.first) ));
|
||||
}
|
||||
start = pos.second;
|
||||
}
|
||||
if (include_empty || start != input.end()) {
|
||||
ret->value.push_back(to_script( String(start,input.end()) ));
|
||||
}
|
||||
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();
|
||||
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) {
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_RETURN(match->matches(input));
|
||||
SCRIPT_PARAM_C(String, input);
|
||||
SCRIPT_PARAM_C(ScriptRegexP, match);
|
||||
SCRIPT_RETURN(match->matches(input));
|
||||
}
|
||||
SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(match) {
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
FOR_EACH(b, closure.bindings) {
|
||||
if (b.first == SCRIPT_VAR_match) {
|
||||
b.second = regex_from_script(b.second); // pre-compile
|
||||
}
|
||||
}
|
||||
return ScriptValueP();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
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"), intrusive(new ScriptRule(script_replace)));
|
||||
ctx.setVariable(_("filter rule"), intrusive(new ScriptRule(script_filter_text)));
|
||||
ctx.setVariable(_("break rule"), intrusive(new ScriptRule(script_break_text)));
|
||||
ctx.setVariable(_("match rule"), intrusive(new ScriptRule(script_match)));
|
||||
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"), intrusive(new ScriptRule(script_replace)));
|
||||
ctx.setVariable(_("filter rule"), intrusive(new ScriptRule(script_filter_text)));
|
||||
ctx.setVariable(_("break rule"), intrusive(new ScriptRule(script_break_text)));
|
||||
ctx.setVariable(_("match rule"), intrusive(new ScriptRule(script_match)));
|
||||
}
|
||||
|
||||
+144
-144
@@ -17,165 +17,165 @@
|
||||
// ----------------------------------------------------------------------------- : Functions
|
||||
|
||||
inline size_t spelled_correctly(const String& input, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) {
|
||||
// untag
|
||||
String word = untag(input.substr(start,end-start));
|
||||
if (word.empty()) return true;
|
||||
// symbol?
|
||||
if (is_in_tag(input,_("<sym"),start,end) ||
|
||||
is_in_tag(input,_("<nospellcheck"),start,end)) {
|
||||
// symbols are always spelled correctly
|
||||
// and <nospellcheck> tags should prevent spellcheck
|
||||
return true;
|
||||
}
|
||||
// run through spellchecker(s)
|
||||
for (size_t i = 0 ; checkers[i] ; ++i) {
|
||||
if (checkers[i]->spell(word)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// run through additional words regex
|
||||
if (extra_test) {
|
||||
// try on untagged
|
||||
ctx.setVariable(SCRIPT_VAR_input, to_script(word));
|
||||
if (*extra_test->eval(ctx)) {
|
||||
return true;
|
||||
}
|
||||
// try on tagged
|
||||
ctx.setVariable(SCRIPT_VAR_input, to_script(input.substr(start,end-start)));
|
||||
if (*extra_test->eval(ctx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
// untag
|
||||
String word = untag(input.substr(start,end-start));
|
||||
if (word.empty()) return true;
|
||||
// symbol?
|
||||
if (is_in_tag(input,_("<sym"),start,end) ||
|
||||
is_in_tag(input,_("<nospellcheck"),start,end)) {
|
||||
// symbols are always spelled correctly
|
||||
// and <nospellcheck> tags should prevent spellcheck
|
||||
return true;
|
||||
}
|
||||
// run through spellchecker(s)
|
||||
for (size_t i = 0 ; checkers[i] ; ++i) {
|
||||
if (checkers[i]->spell(word)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// run through additional words regex
|
||||
if (extra_test) {
|
||||
// try on untagged
|
||||
ctx.setVariable(SCRIPT_VAR_input, to_script(word));
|
||||
if (*extra_test->eval(ctx)) {
|
||||
return true;
|
||||
}
|
||||
// try on tagged
|
||||
ctx.setVariable(SCRIPT_VAR_input, to_script(input.substr(start,end-start)));
|
||||
if (*extra_test->eval(ctx)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void check_word(const String& tag, const String& input, String& out, size_t start, size_t end, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) {
|
||||
if (start >= end) return;
|
||||
bool good = spelled_correctly(input, start, end, checkers, extra_test, ctx);
|
||||
if (!good) out += _("<") + tag;
|
||||
out.append(input, start, end-start);
|
||||
if (!good) out += _("</") + tag;
|
||||
if (start >= end) return;
|
||||
bool good = spelled_correctly(input, start, end, checkers, extra_test, ctx);
|
||||
if (!good) out += _("<") + tag;
|
||||
out.append(input, start, end-start);
|
||||
if (!good) out += _("</") + tag;
|
||||
}
|
||||
|
||||
void check_word(const String& tag, const String& input, String& out, Char sep, size_t prev, size_t start, size_t end, size_t after, SpellChecker** checkers, const ScriptValueP& extra_test, Context& ctx) {
|
||||
if (start == end) {
|
||||
// word consisting of whitespace/punctuation only
|
||||
if (untag(input.substr(prev,after-prev)).empty()) {
|
||||
if (isSpace(sep) && (after == input.size() || isSpace(input.GetChar(after)))) {
|
||||
// double space
|
||||
out += _("<error-spelling>");
|
||||
out.append(sep);
|
||||
out.append(input, prev, after-prev);
|
||||
out += _("</error-spelling>");
|
||||
} else {
|
||||
if (sep) out.append(sep);
|
||||
out.append(input, prev, after-prev);
|
||||
}
|
||||
} else {
|
||||
// stand alone punctuation
|
||||
if (sep) out.append(sep);
|
||||
out += _("<error-spelling>");
|
||||
out.append(input, prev, after-prev);
|
||||
out += _("</error-spelling>");
|
||||
}
|
||||
} else {
|
||||
// before the word
|
||||
if (sep) out.append(sep);
|
||||
out.append(input, prev, start-prev);
|
||||
// the word itself
|
||||
check_word(tag, input, out, start, end, checkers, extra_test, ctx);
|
||||
// after the word
|
||||
out.append(input, end, after-end);
|
||||
}
|
||||
if (start == end) {
|
||||
// word consisting of whitespace/punctuation only
|
||||
if (untag(input.substr(prev,after-prev)).empty()) {
|
||||
if (isSpace(sep) && (after == input.size() || isSpace(input.GetChar(after)))) {
|
||||
// double space
|
||||
out += _("<error-spelling>");
|
||||
out.append(sep);
|
||||
out.append(input, prev, after-prev);
|
||||
out += _("</error-spelling>");
|
||||
} else {
|
||||
if (sep) out.append(sep);
|
||||
out.append(input, prev, after-prev);
|
||||
}
|
||||
} else {
|
||||
// stand alone punctuation
|
||||
if (sep) out.append(sep);
|
||||
out += _("<error-spelling>");
|
||||
out.append(input, prev, after-prev);
|
||||
out += _("</error-spelling>");
|
||||
}
|
||||
} else {
|
||||
// before the word
|
||||
if (sep) out.append(sep);
|
||||
out.append(input, prev, start-prev);
|
||||
// the word itself
|
||||
check_word(tag, input, out, start, end, checkers, extra_test, ctx);
|
||||
// after the word
|
||||
out.append(input, end, after-end);
|
||||
}
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(check_spelling) {
|
||||
SCRIPT_PARAM_C(StyleSheetP,stylesheet);
|
||||
SCRIPT_PARAM_C(String,language);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
assert_tagged(input);
|
||||
if (!settings.stylesheetSettingsFor(*stylesheet).card_spellcheck_enabled)
|
||||
SCRIPT_RETURN(input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("extra dictionary"),extra_dictionary);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(ScriptValueP,_("extra match"),extra_match);
|
||||
// remove old spelling error tags
|
||||
input = remove_tag(input, _("<error-spelling"));
|
||||
// no language -> spelling checking
|
||||
if (language.empty()) {
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
SpellChecker* checkers[3] = {nullptr};
|
||||
checkers[0] = &SpellChecker::get(language);
|
||||
if (!extra_dictionary.empty()) {
|
||||
checkers[1] = &SpellChecker::get(extra_dictionary,language);
|
||||
}
|
||||
// what will the missspelling tag be?
|
||||
String tag = _("error-spelling:");
|
||||
tag += language;
|
||||
if (!extra_dictionary.empty()) {
|
||||
tag += _(":") + extra_dictionary;
|
||||
}
|
||||
tag += _(">");
|
||||
// now walk over the words in the input, and mark misspellings
|
||||
String result;
|
||||
Char sep = 0;
|
||||
// indices are used as follows (at the time of check_word call):
|
||||
// input: "previous <tag>(<tag>word<tag>)<tag>next"
|
||||
// ^^ ^ ^ ^
|
||||
// || | | |
|
||||
// sep | | word_end pos
|
||||
// prev_end word_start
|
||||
//
|
||||
size_t prev_end = 0, word_start = 0, word_end = 0, pos = 0;
|
||||
while (pos < input.size()) {
|
||||
Char c = input.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
if (word_start == pos) {
|
||||
// prefer to place word start inside tags, i.e. as late as possible
|
||||
word_end = word_start = pos = skip_tag(input,pos);
|
||||
} else {
|
||||
pos = skip_tag(input,pos);
|
||||
}
|
||||
} else if (isSpace(c) || c == EM_DASH || c == EN_DASH) {
|
||||
// word boundary => check the word
|
||||
check_word(tag, input, result, sep, prev_end, word_start, word_end, pos, checkers, extra_match, ctx);
|
||||
// next
|
||||
sep = c;
|
||||
prev_end = word_start = word_end = pos = pos + 1;
|
||||
} else {
|
||||
pos++;
|
||||
if (word_start == pos-1 && is_word_start_punctuation(c)) {
|
||||
// skip punctuation at start of word
|
||||
word_end = word_start = pos;
|
||||
} else if (is_word_end_punctuation(c)) {
|
||||
// skip punctuation at end of word
|
||||
} else {
|
||||
word_end = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// last word
|
||||
check_word(tag, input, result, sep, prev_end, word_start, word_end, pos, checkers, extra_match, ctx);
|
||||
// done
|
||||
assert_tagged(result);
|
||||
SCRIPT_RETURN(result);
|
||||
SCRIPT_PARAM_C(StyleSheetP,stylesheet);
|
||||
SCRIPT_PARAM_C(String,language);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
assert_tagged(input);
|
||||
if (!settings.stylesheetSettingsFor(*stylesheet).card_spellcheck_enabled)
|
||||
SCRIPT_RETURN(input);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(String,_("extra dictionary"),extra_dictionary);
|
||||
SCRIPT_OPTIONAL_PARAM_N_(ScriptValueP,_("extra match"),extra_match);
|
||||
// remove old spelling error tags
|
||||
input = remove_tag(input, _("<error-spelling"));
|
||||
// no language -> spelling checking
|
||||
if (language.empty()) {
|
||||
SCRIPT_RETURN(input);
|
||||
}
|
||||
SpellChecker* checkers[3] = {nullptr};
|
||||
checkers[0] = &SpellChecker::get(language);
|
||||
if (!extra_dictionary.empty()) {
|
||||
checkers[1] = &SpellChecker::get(extra_dictionary,language);
|
||||
}
|
||||
// what will the missspelling tag be?
|
||||
String tag = _("error-spelling:");
|
||||
tag += language;
|
||||
if (!extra_dictionary.empty()) {
|
||||
tag += _(":") + extra_dictionary;
|
||||
}
|
||||
tag += _(">");
|
||||
// now walk over the words in the input, and mark misspellings
|
||||
String result;
|
||||
Char sep = 0;
|
||||
// indices are used as follows (at the time of check_word call):
|
||||
// input: "previous <tag>(<tag>word<tag>)<tag>next"
|
||||
// ^^ ^ ^ ^
|
||||
// || | | |
|
||||
// sep | | word_end pos
|
||||
// prev_end word_start
|
||||
//
|
||||
size_t prev_end = 0, word_start = 0, word_end = 0, pos = 0;
|
||||
while (pos < input.size()) {
|
||||
Char c = input.GetChar(pos);
|
||||
if (c == _('<')) {
|
||||
if (word_start == pos) {
|
||||
// prefer to place word start inside tags, i.e. as late as possible
|
||||
word_end = word_start = pos = skip_tag(input,pos);
|
||||
} else {
|
||||
pos = skip_tag(input,pos);
|
||||
}
|
||||
} else if (isSpace(c) || c == EM_DASH || c == EN_DASH) {
|
||||
// word boundary => check the word
|
||||
check_word(tag, input, result, sep, prev_end, word_start, word_end, pos, checkers, extra_match, ctx);
|
||||
// next
|
||||
sep = c;
|
||||
prev_end = word_start = word_end = pos = pos + 1;
|
||||
} else {
|
||||
pos++;
|
||||
if (word_start == pos-1 && is_word_start_punctuation(c)) {
|
||||
// skip punctuation at start of word
|
||||
word_end = word_start = pos;
|
||||
} else if (is_word_end_punctuation(c)) {
|
||||
// skip punctuation at end of word
|
||||
} else {
|
||||
word_end = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// last word
|
||||
check_word(tag, input, result, sep, prev_end, word_start, word_end, pos, checkers, extra_match, ctx);
|
||||
// done
|
||||
assert_tagged(result);
|
||||
SCRIPT_RETURN(result);
|
||||
}
|
||||
|
||||
SCRIPT_FUNCTION(check_spelling_word) {
|
||||
SCRIPT_PARAM_C(String,language);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
if (language.empty()) {
|
||||
// no language -> spelling checking
|
||||
SCRIPT_RETURN(true);
|
||||
} else {
|
||||
bool correct = SpellChecker::get(language).spell(input);
|
||||
SCRIPT_RETURN(correct);
|
||||
}
|
||||
SCRIPT_PARAM_C(String,language);
|
||||
SCRIPT_PARAM_C(String,input);
|
||||
if (language.empty()) {
|
||||
// no language -> spelling checking
|
||||
SCRIPT_RETURN(true);
|
||||
} else {
|
||||
bool correct = SpellChecker::get(language).spell(input);
|
||||
SCRIPT_RETURN(correct);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Init
|
||||
|
||||
void init_script_spelling_functions(Context& ctx) {
|
||||
ctx.setVariable(_("check spelling"), script_check_spelling);
|
||||
ctx.setVariable(_("check spelling word"), script_check_spelling_word);
|
||||
ctx.setVariable(_("check spelling"), script_check_spelling);
|
||||
ctx.setVariable(_("check spelling word"), script_check_spelling_word);
|
||||
}
|
||||
|
||||
+118
-118
@@ -38,31 +38,31 @@
|
||||
#define SCRIPT_FUNCTION(name) SCRIPT_FUNCTION_AUX(name,;)
|
||||
|
||||
/// Macro to declare a new script function with custom dependency handling
|
||||
#define SCRIPT_FUNCTION_WITH_DEP(name) \
|
||||
SCRIPT_FUNCTION_AUX(name, virtual ScriptValueP dependencies(Context&, const Dependency&) const;)
|
||||
#define SCRIPT_FUNCTION_WITH_DEP(name) \
|
||||
SCRIPT_FUNCTION_AUX(name, virtual ScriptValueP dependencies(Context&, const Dependency&) const;)
|
||||
|
||||
#define SCRIPT_FUNCTION_DEPENDENCIES(name) \
|
||||
ScriptValueP ScriptBuiltIn_##name::dependencies(Context& ctx, const Dependency& dep) const
|
||||
#define SCRIPT_FUNCTION_DEPENDENCIES(name) \
|
||||
ScriptValueP ScriptBuiltIn_##name::dependencies(Context& ctx, const Dependency& dep) const
|
||||
|
||||
/// Macro to declare a new script function with custom closure simplification
|
||||
#define SCRIPT_FUNCTION_WITH_SIMPLIFY(name) \
|
||||
SCRIPT_FUNCTION_AUX(name, virtual ScriptValueP simplifyClosure(ScriptClosure&) const;)
|
||||
#define SCRIPT_FUNCTION_WITH_SIMPLIFY(name) \
|
||||
SCRIPT_FUNCTION_AUX(name, virtual ScriptValueP simplifyClosure(ScriptClosure&) const;)
|
||||
|
||||
#define SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(name) \
|
||||
ScriptValueP ScriptBuiltIn_##name::simplifyClosure(ScriptClosure& closure) const
|
||||
#define SCRIPT_FUNCTION_SIMPLIFY_CLOSURE(name) \
|
||||
ScriptValueP ScriptBuiltIn_##name::simplifyClosure(ScriptClosure& closure) const
|
||||
|
||||
// helper for SCRIPT_FUNCTION and SCRIPT_FUNCTION_DEP
|
||||
#define SCRIPT_FUNCTION_AUX(name,dep) \
|
||||
class ScriptBuiltIn_##name : public ScriptValue { \
|
||||
dep \
|
||||
virtual ScriptType type() const \
|
||||
{ return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const \
|
||||
{ return _("built-in function '") _(#name) _("'"); } \
|
||||
virtual ScriptValueP do_eval(Context&, bool) const; \
|
||||
}; \
|
||||
ScriptValueP script_##name(new ScriptBuiltIn_##name); \
|
||||
ScriptValueP ScriptBuiltIn_##name::do_eval(Context& ctx, bool) const
|
||||
#define SCRIPT_FUNCTION_AUX(name,dep) \
|
||||
class ScriptBuiltIn_##name : public ScriptValue { \
|
||||
dep \
|
||||
virtual ScriptType type() const \
|
||||
{ return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const \
|
||||
{ return _("built-in function '") _(#name) _("'"); } \
|
||||
virtual ScriptValueP do_eval(Context&, bool) const; \
|
||||
}; \
|
||||
ScriptValueP script_##name(new ScriptBuiltIn_##name); \
|
||||
ScriptValueP ScriptBuiltIn_##name::do_eval(Context& ctx, bool) const
|
||||
|
||||
/// Return a value from a SCRIPT_FUNCTION
|
||||
#define SCRIPT_RETURN(value) return to_script(value)
|
||||
@@ -71,19 +71,19 @@
|
||||
|
||||
template <typename Type>
|
||||
inline Type from_script(const ScriptValueP& v, const String& str) {
|
||||
try {
|
||||
return from_script<Type>(v);
|
||||
} catch (ScriptError& e) {
|
||||
throw ScriptError(_ERROR_2_("in parameter", str, e.what()));
|
||||
}
|
||||
try {
|
||||
return from_script<Type>(v);
|
||||
} catch (ScriptError& e) {
|
||||
throw ScriptError(_ERROR_2_("in parameter", str, e.what()));
|
||||
}
|
||||
}
|
||||
template <typename Type>
|
||||
inline Type from_script(const ScriptValueP& v, Variable var) {
|
||||
try {
|
||||
return from_script<Type>(v);
|
||||
} catch (ScriptError& e) {
|
||||
throw ScriptError(_ERROR_2_("in parameter", variable_to_string(var), e.what()));
|
||||
}
|
||||
try {
|
||||
return from_script<Type>(v);
|
||||
} catch (ScriptError& e) {
|
||||
throw ScriptError(_ERROR_2_("in parameter", variable_to_string(var), e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve a parameter to a SCRIPT_FUNCTION with the given name and type
|
||||
@@ -96,14 +96,14 @@ inline Type from_script(const ScriptValueP& v, Variable var) {
|
||||
* @endcode
|
||||
* Throws an error if the parameter is not found.
|
||||
*/
|
||||
#define SCRIPT_PARAM(Type, name) \
|
||||
SCRIPT_PARAM_N(Type, _(#name), name)
|
||||
#define SCRIPT_PARAM_N(Type, str, name) \
|
||||
Type name = from_script<Type>(ctx.getVariable(str), str)
|
||||
#define SCRIPT_PARAM(Type, name) \
|
||||
SCRIPT_PARAM_N(Type, _(#name), name)
|
||||
#define SCRIPT_PARAM_N(Type, str, name) \
|
||||
Type name = from_script<Type>(ctx.getVariable(str), str)
|
||||
/// Faster variant of SCRIPT_PARAM when name is a CommonScriptVariable
|
||||
/** Doesn't require a runtime lookup of the name */
|
||||
#define SCRIPT_PARAM_C(Type, name) \
|
||||
SCRIPT_PARAM_N(Type, SCRIPT_VAR_ ## name, name)
|
||||
#define SCRIPT_PARAM_C(Type, name) \
|
||||
SCRIPT_PARAM_N(Type, SCRIPT_VAR_ ## name, name)
|
||||
|
||||
/// Retrieve an optional parameter
|
||||
/** Usage:
|
||||
@@ -116,112 +116,112 @@ inline Type from_script(const ScriptValueP& v, Variable var) {
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
#define SCRIPT_OPTIONAL_PARAM(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N(Type, _(#name), name)
|
||||
#define SCRIPT_OPTIONAL_PARAM(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N(Type, _(#name), name)
|
||||
/// Retrieve a named optional parameter
|
||||
#define SCRIPT_OPTIONAL_PARAM_N(Type, str, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, str, name) \
|
||||
if (name##_)
|
||||
#define SCRIPT_OPTIONAL_PARAM_C(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N(Type, SCRIPT_VAR_ ## name, name)
|
||||
#define SCRIPT_OPTIONAL_PARAM_N(Type, str, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, str, name) \
|
||||
if (name##_)
|
||||
#define SCRIPT_OPTIONAL_PARAM_C(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N(Type, SCRIPT_VAR_ ## name, name)
|
||||
|
||||
/// Retrieve an optional parameter, can't be used as an if statement
|
||||
#define SCRIPT_OPTIONAL_PARAM_(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, _(#name), name)
|
||||
#define SCRIPT_OPTIONAL_PARAM_(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, _(#name), name)
|
||||
/// Retrieve a named optional parameter, can't be used as an if statement
|
||||
#define SCRIPT_OPTIONAL_PARAM_N_(Type, str, name) \
|
||||
ScriptValueP name##_ = ctx.getVariableOpt(str); \
|
||||
Type name = name##_ && name##_ != script_nil \
|
||||
? from_script<Type>(name##_, str) : Type();
|
||||
#define SCRIPT_OPTIONAL_PARAM_C_(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, SCRIPT_VAR_ ## name, name)
|
||||
#define SCRIPT_OPTIONAL_PARAM_N_(Type, str, name) \
|
||||
ScriptValueP name##_ = ctx.getVariableOpt(str); \
|
||||
Type name = name##_ && name##_ != script_nil \
|
||||
? from_script<Type>(name##_, str) : Type();
|
||||
#define SCRIPT_OPTIONAL_PARAM_C_(Type, name) \
|
||||
SCRIPT_OPTIONAL_PARAM_N_(Type, SCRIPT_VAR_ ## name, name)
|
||||
|
||||
/// Retrieve an optional parameter with a default value
|
||||
#define SCRIPT_PARAM_DEFAULT(Type, name, def) \
|
||||
SCRIPT_PARAM_DEFAULT_N(Type, _(#name), name, def)
|
||||
#define SCRIPT_PARAM_DEFAULT(Type, name, def) \
|
||||
SCRIPT_PARAM_DEFAULT_N(Type, _(#name), name, def)
|
||||
/// Retrieve a named optional parameter with a default value
|
||||
#define SCRIPT_PARAM_DEFAULT_N(Type, str, name, def) \
|
||||
ScriptValueP name##_ = ctx.getVariableOpt(str); \
|
||||
Type name = name##_ ? from_script<Type>(name##_, str) : def
|
||||
#define SCRIPT_PARAM_DEFAULT_C(Type, name, def) \
|
||||
SCRIPT_PARAM_DEFAULT_N(Type, SCRIPT_VAR_ ## name, name, def)
|
||||
#define SCRIPT_PARAM_DEFAULT_N(Type, str, name, def) \
|
||||
ScriptValueP name##_ = ctx.getVariableOpt(str); \
|
||||
Type name = name##_ ? from_script<Type>(name##_, str) : def
|
||||
#define SCRIPT_PARAM_DEFAULT_C(Type, name, def) \
|
||||
SCRIPT_PARAM_DEFAULT_N(Type, SCRIPT_VAR_ ## name, name, def)
|
||||
|
||||
// ----------------------------------------------------------------------------- : Rules
|
||||
|
||||
/// Utility for defining a script rule with a single parameter
|
||||
#define SCRIPT_RULE_1(funname, type1, name1) \
|
||||
#define SCRIPT_RULE_1(funname, type1, name1) \
|
||||
SCRIPT_RULE_1_N(funname, type1, _(#name1), name1)
|
||||
#define SCRIPT_RULE_1_C(funname, type1, name1) \
|
||||
#define SCRIPT_RULE_1_C(funname, type1, name1) \
|
||||
SCRIPT_RULE_1_N(funname, type1, SCRIPT_VAR_ ## name1, name1)
|
||||
/// Utility for defining a script rule with a single named parameter
|
||||
#define SCRIPT_RULE_1_N(funname, type1, str1, name1) \
|
||||
class ScriptRule_##funname: public ScriptValue { \
|
||||
public: \
|
||||
inline ScriptRule_##funname(const type1& name1) : name1(name1) {} \
|
||||
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const { return _(#funname)_("_rule"); } \
|
||||
protected: \
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const; \
|
||||
private: \
|
||||
type1 name1; \
|
||||
}; \
|
||||
SCRIPT_FUNCTION(funname##_rule) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
return intrusive(new ScriptRule_##funname(name1)); \
|
||||
} \
|
||||
SCRIPT_FUNCTION(funname) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
return ScriptRule_##funname(name1).eval(ctx); \
|
||||
} \
|
||||
ScriptValueP ScriptRule_##funname::do_eval(Context& ctx, bool) const
|
||||
#define SCRIPT_RULE_1_N(funname, type1, str1, name1) \
|
||||
class ScriptRule_##funname: public ScriptValue { \
|
||||
public: \
|
||||
inline ScriptRule_##funname(const type1& name1) : name1(name1) {} \
|
||||
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const { return _(#funname)_("_rule"); } \
|
||||
protected: \
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const; \
|
||||
private: \
|
||||
type1 name1; \
|
||||
}; \
|
||||
SCRIPT_FUNCTION(funname##_rule) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
return intrusive(new ScriptRule_##funname(name1)); \
|
||||
} \
|
||||
SCRIPT_FUNCTION(funname) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
return ScriptRule_##funname(name1).eval(ctx); \
|
||||
} \
|
||||
ScriptValueP ScriptRule_##funname::do_eval(Context& ctx, bool) const
|
||||
|
||||
/// Utility for defining a script rule with two parameters
|
||||
#define SCRIPT_RULE_2(funname, type1, name1, type2, name2) \
|
||||
#define SCRIPT_RULE_2(funname, type1, name1, type2, name2) \
|
||||
SCRIPT_RULE_2_N(funname, type1, _(#name1), name1, type2, _(#name2), name2)
|
||||
#define SCRIPT_RULE_2_C(funname, type1, name1, type2, name2) \
|
||||
#define SCRIPT_RULE_2_C(funname, type1, name1, type2, name2) \
|
||||
SCRIPT_RULE_2_N(funname, type1, SCRIPT_VAR_ ## name1, name1, type2, SCRIPT_VAR_ ## name2, name2)
|
||||
/// Utility for defining a script rule with two named parameters
|
||||
#define SCRIPT_RULE_2_N(funname, type1, str1, name1, type2, str2, name2) \
|
||||
SCRIPT_RULE_2_N_AUX(funname, type1, str1, name1, type2, str2, name2, ;, ;)
|
||||
#define SCRIPT_RULE_2_N(funname, type1, str1, name1, type2, str2, name2) \
|
||||
SCRIPT_RULE_2_N_AUX(funname, type1, str1, name1, type2, str2, name2, ;, ;)
|
||||
/// Utility for defining a script rule with two named parameters, with dependencies
|
||||
#define SCRIPT_RULE_2_N_DEP(funname, type1, str1, name1, type2, str2, name2) \
|
||||
SCRIPT_RULE_2_N_AUX( funname, type1, str1, name1, type2, str2, name2, \
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;, \
|
||||
SCRIPT_FUNCTION_DEPENDENCIES(funname) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return ScriptRule_##funname(name1, name2).dependencies(ctx, dep); \
|
||||
})
|
||||
#define SCRIPT_RULE_2_N_DEP(funname, type1, str1, name1, type2, str2, name2) \
|
||||
SCRIPT_RULE_2_N_AUX( funname, type1, str1, name1, type2, str2, name2, \
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;, \
|
||||
SCRIPT_FUNCTION_DEPENDENCIES(funname) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return ScriptRule_##funname(name1, name2).dependencies(ctx, dep); \
|
||||
})
|
||||
|
||||
#define SCRIPT_RULE_2_N_AUX(funname, type1, str1, name1, type2, str2, name2, dep, more) \
|
||||
class ScriptRule_##funname: public ScriptValue { \
|
||||
public: \
|
||||
inline ScriptRule_##funname(const type1& name1, const type2& name2) \
|
||||
: name1(name1), name2(name2) {} \
|
||||
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const { return _(#funname)_("_rule"); } \
|
||||
dep \
|
||||
protected: \
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const; \
|
||||
private: \
|
||||
type1 name1; \
|
||||
type2 name2; \
|
||||
}; \
|
||||
SCRIPT_FUNCTION(funname##_rule) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return intrusive(new ScriptRule_##funname(name1, name2)); \
|
||||
} \
|
||||
SCRIPT_FUNCTION_AUX(funname, dep) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return ScriptRule_##funname(name1, name2).eval(ctx); \
|
||||
} \
|
||||
more \
|
||||
ScriptValueP ScriptRule_##funname::do_eval(Context& ctx, bool) const
|
||||
class ScriptRule_##funname: public ScriptValue { \
|
||||
public: \
|
||||
inline ScriptRule_##funname(const type1& name1, const type2& name2) \
|
||||
: name1(name1), name2(name2) {} \
|
||||
virtual ScriptType type() const { return SCRIPT_FUNCTION; } \
|
||||
virtual String typeName() const { return _(#funname)_("_rule"); } \
|
||||
dep \
|
||||
protected: \
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const; \
|
||||
private: \
|
||||
type1 name1; \
|
||||
type2 name2; \
|
||||
}; \
|
||||
SCRIPT_FUNCTION(funname##_rule) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return intrusive(new ScriptRule_##funname(name1, name2)); \
|
||||
} \
|
||||
SCRIPT_FUNCTION_AUX(funname, dep) { \
|
||||
SCRIPT_PARAM_N(type1, str1, name1); \
|
||||
SCRIPT_PARAM_N(type2, str2, name2); \
|
||||
return ScriptRule_##funname(name1, name2).eval(ctx); \
|
||||
} \
|
||||
more \
|
||||
ScriptValueP ScriptRule_##funname::do_eval(Context& ctx, bool) const
|
||||
|
||||
#define SCRIPT_RULE_2_DEPENDENCIES(name) \
|
||||
ScriptValueP ScriptRule_##name::dependencies(Context& ctx, const Dependency& dep) const
|
||||
#define SCRIPT_RULE_2_DEPENDENCIES(name) \
|
||||
ScriptValueP ScriptRule_##name::dependencies(Context& ctx, const Dependency& dep) const
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+147
-147
@@ -19,52 +19,52 @@
|
||||
|
||||
// convert any script value to a GeneratedImageP
|
||||
GeneratedImageP image_from_script(const ScriptValueP& value) {
|
||||
return value->toImage(value);
|
||||
return value->toImage(value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ScriptableImage
|
||||
|
||||
Image ScriptableImage::generate(const GeneratedImage::Options& options) const {
|
||||
// generate
|
||||
Image image;
|
||||
if (isReady()) {
|
||||
// note: Don't catch exceptions here, we don't want to return an invalid image.
|
||||
// We could return a blank one, but the thumbnail code does want an invalid
|
||||
// image in case of errors.
|
||||
// This allows the caller to catch errors.
|
||||
image = value->generate(options);
|
||||
} else {
|
||||
// error, return blank image
|
||||
Image i(1,1);
|
||||
i.InitAlpha();
|
||||
i.SetAlpha(0,0,0);
|
||||
image = i;
|
||||
}
|
||||
return conform_image(image, options);
|
||||
// generate
|
||||
Image image;
|
||||
if (isReady()) {
|
||||
// note: Don't catch exceptions here, we don't want to return an invalid image.
|
||||
// We could return a blank one, but the thumbnail code does want an invalid
|
||||
// image in case of errors.
|
||||
// This allows the caller to catch errors.
|
||||
image = value->generate(options);
|
||||
} else {
|
||||
// error, return blank image
|
||||
Image i(1,1);
|
||||
i.InitAlpha();
|
||||
i.SetAlpha(0,0,0);
|
||||
image = i;
|
||||
}
|
||||
return conform_image(image, options);
|
||||
}
|
||||
|
||||
ImageCombine ScriptableImage::combine() const {
|
||||
if (!isReady()) return COMBINE_DEFAULT;
|
||||
return value->combine();
|
||||
if (!isReady()) return COMBINE_DEFAULT;
|
||||
return value->combine();
|
||||
}
|
||||
|
||||
bool ScriptableImage::update(Context& ctx) {
|
||||
if (!isScripted()) return false;
|
||||
GeneratedImageP new_value = image_from_script(script.invoke(ctx));
|
||||
if (!new_value || !value || *new_value != *value) {
|
||||
value = new_value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (!isScripted()) return false;
|
||||
GeneratedImageP new_value = image_from_script(script.invoke(ctx));
|
||||
if (!new_value || !value || *new_value != *value) {
|
||||
value = new_value;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptP ScriptableImage::getValidScriptP() {
|
||||
if (script) return script.getScriptP();
|
||||
// return value or a blank image
|
||||
ScriptP s(new Script);
|
||||
s->addInstruction(I_PUSH_CONST, value ? static_pointer_cast<ScriptValue>(value) : script_nil);
|
||||
return s;
|
||||
if (script) return script.getScriptP();
|
||||
// return value or a blank image
|
||||
ScriptP s(new Script);
|
||||
s->addInstruction(I_PUSH_CONST, value ? static_pointer_cast<ScriptValue>(value) : script_nil);
|
||||
return s;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Reflection
|
||||
@@ -72,122 +72,122 @@ ScriptP ScriptableImage::getValidScriptP() {
|
||||
// we need some custom io, because the behaviour is different for each of Reader/Writer/GetMember
|
||||
|
||||
template <> void Reader::handle(ScriptableImage& s) {
|
||||
handle(s.script.unparsed);
|
||||
if (starts_with(s.script.unparsed, _("script:"))) {
|
||||
s.script.unparsed = s.script.unparsed.substr(7);
|
||||
s.script.parse(*this);
|
||||
} else if (s.script.unparsed.find_first_of('{') != String::npos) {
|
||||
s.script.parse(*this, true);
|
||||
} else {
|
||||
// a filename
|
||||
s.value = intrusive(new PackagedImage(s.script.unparsed));
|
||||
}
|
||||
handle(s.script.unparsed);
|
||||
if (starts_with(s.script.unparsed, _("script:"))) {
|
||||
s.script.unparsed = s.script.unparsed.substr(7);
|
||||
s.script.parse(*this);
|
||||
} else if (s.script.unparsed.find_first_of('{') != String::npos) {
|
||||
s.script.parse(*this, true);
|
||||
} else {
|
||||
// a filename
|
||||
s.value = intrusive(new PackagedImage(s.script.unparsed));
|
||||
}
|
||||
}
|
||||
template <> void Writer::handle(const ScriptableImage& s) {
|
||||
handle(s.script.unparsed);
|
||||
handle(s.script.unparsed);
|
||||
}
|
||||
template <> void GetDefaultMember::handle(const ScriptableImage& s) {
|
||||
handle(s.script.unparsed);
|
||||
handle(s.script.unparsed);
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------- : CachedScriptableImage
|
||||
|
||||
void CachedScriptableImage::generateCached(const GeneratedImage::Options& options,
|
||||
CachedScriptableMask* mask,
|
||||
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size) {
|
||||
// ready?
|
||||
if (!isReady()) {
|
||||
// error, return blank image
|
||||
Image i(1,1);
|
||||
i.InitAlpha();
|
||||
i.SetAlpha(0,0,0);
|
||||
*image = i;
|
||||
*size = RealSize(0,0);
|
||||
return;
|
||||
}
|
||||
// find combine mode
|
||||
ImageCombine combine_i = value->combine();
|
||||
if (combine_i != COMBINE_DEFAULT) *combine = combine_i;
|
||||
*size = cached_size;
|
||||
// does the size match?
|
||||
bool w_ok = cached_size.width == options.width,
|
||||
h_ok = cached_size.height == options.height;
|
||||
// image or bitmap?
|
||||
if (*combine <= COMBINE_NORMAL) {
|
||||
// bitmap
|
||||
if (cached_b.Ok() && options.angle == cached_angle) {
|
||||
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
|
||||
// cached, we are done
|
||||
*bitmap = cached_b;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// image
|
||||
Radians relative_rotation = options.angle + rad360 - cached_angle;
|
||||
if (cached_i.Ok() && is_straight(relative_rotation)) {
|
||||
// we need only an {0,90,180,270} degree rotation compared to the cached one, this doesn't reduce image quality
|
||||
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
|
||||
if (options.angle != cached_angle) {
|
||||
// rotate cached image
|
||||
cached_i = rotate_image(cached_i, relative_rotation);
|
||||
cached_angle = options.angle;
|
||||
}
|
||||
*image = cached_i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// hack(part1): temporarily set angle to 0, do actual rotation after applying mask
|
||||
Radians a = options.angle;
|
||||
const_cast<GeneratedImage::Options&>(options).angle = 0;
|
||||
// generate
|
||||
cached_i = generate(options);
|
||||
const_cast<GeneratedImage::Options&>(options).angle = cached_angle = a;
|
||||
*size = cached_size = RealSize(options.width, options.height);
|
||||
if (mask) {
|
||||
// apply mask
|
||||
GeneratedImage::Options mask_opts(options);
|
||||
mask_opts.width = cached_i.GetWidth();
|
||||
mask_opts.height = cached_i.GetHeight();
|
||||
mask_opts.angle = 0;
|
||||
mask->get(mask_opts).setAlpha(cached_i);
|
||||
}
|
||||
if (options.angle != 0) {
|
||||
// hack(part2) do the actual rotation now
|
||||
cached_i = rotate_image(cached_i, options.angle);
|
||||
}
|
||||
if (*combine <= COMBINE_NORMAL) {
|
||||
*bitmap = cached_b = Bitmap(cached_i);
|
||||
cached_i = Image();
|
||||
} else {
|
||||
*image = cached_i;
|
||||
}
|
||||
CachedScriptableMask* mask,
|
||||
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size) {
|
||||
// ready?
|
||||
if (!isReady()) {
|
||||
// error, return blank image
|
||||
Image i(1,1);
|
||||
i.InitAlpha();
|
||||
i.SetAlpha(0,0,0);
|
||||
*image = i;
|
||||
*size = RealSize(0,0);
|
||||
return;
|
||||
}
|
||||
// find combine mode
|
||||
ImageCombine combine_i = value->combine();
|
||||
if (combine_i != COMBINE_DEFAULT) *combine = combine_i;
|
||||
*size = cached_size;
|
||||
// does the size match?
|
||||
bool w_ok = cached_size.width == options.width,
|
||||
h_ok = cached_size.height == options.height;
|
||||
// image or bitmap?
|
||||
if (*combine <= COMBINE_NORMAL) {
|
||||
// bitmap
|
||||
if (cached_b.Ok() && options.angle == cached_angle) {
|
||||
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
|
||||
// cached, we are done
|
||||
*bitmap = cached_b;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// image
|
||||
Radians relative_rotation = options.angle + rad360 - cached_angle;
|
||||
if (cached_i.Ok() && is_straight(relative_rotation)) {
|
||||
// we need only an {0,90,180,270} degree rotation compared to the cached one, this doesn't reduce image quality
|
||||
if ((w_ok && h_ok) || (options.preserve_aspect == ASPECT_FIT && (w_ok || h_ok))) { // only one dimension has to fit when fitting
|
||||
if (options.angle != cached_angle) {
|
||||
// rotate cached image
|
||||
cached_i = rotate_image(cached_i, relative_rotation);
|
||||
cached_angle = options.angle;
|
||||
}
|
||||
*image = cached_i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// hack(part1): temporarily set angle to 0, do actual rotation after applying mask
|
||||
Radians a = options.angle;
|
||||
const_cast<GeneratedImage::Options&>(options).angle = 0;
|
||||
// generate
|
||||
cached_i = generate(options);
|
||||
const_cast<GeneratedImage::Options&>(options).angle = cached_angle = a;
|
||||
*size = cached_size = RealSize(options.width, options.height);
|
||||
if (mask) {
|
||||
// apply mask
|
||||
GeneratedImage::Options mask_opts(options);
|
||||
mask_opts.width = cached_i.GetWidth();
|
||||
mask_opts.height = cached_i.GetHeight();
|
||||
mask_opts.angle = 0;
|
||||
mask->get(mask_opts).setAlpha(cached_i);
|
||||
}
|
||||
if (options.angle != 0) {
|
||||
// hack(part2) do the actual rotation now
|
||||
cached_i = rotate_image(cached_i, options.angle);
|
||||
}
|
||||
if (*combine <= COMBINE_NORMAL) {
|
||||
*bitmap = cached_b = Bitmap(cached_i);
|
||||
cached_i = Image();
|
||||
} else {
|
||||
*image = cached_i;
|
||||
}
|
||||
}
|
||||
|
||||
bool CachedScriptableImage::update(Context& ctx) {
|
||||
bool change = ScriptableImage::update(ctx);
|
||||
if (change) {
|
||||
clearCache();
|
||||
}
|
||||
return change;
|
||||
bool change = ScriptableImage::update(ctx);
|
||||
if (change) {
|
||||
clearCache();
|
||||
}
|
||||
return change;
|
||||
}
|
||||
|
||||
void CachedScriptableImage::clearCache() {
|
||||
cached_i = Image();
|
||||
cached_b = Bitmap();
|
||||
cached_i = Image();
|
||||
cached_b = Bitmap();
|
||||
}
|
||||
|
||||
|
||||
template <> void Reader::handle(CachedScriptableImage& s) {
|
||||
handle((ScriptableImage&)s);
|
||||
handle((ScriptableImage&)s);
|
||||
}
|
||||
template <> void Writer::handle(const CachedScriptableImage& s) {
|
||||
handle((const ScriptableImage&)s);
|
||||
handle((const ScriptableImage&)s);
|
||||
}
|
||||
template <> void GetDefaultMember::handle(const CachedScriptableImage& s) {
|
||||
handle((const ScriptableImage&)s);
|
||||
handle((const ScriptableImage&)s);
|
||||
}
|
||||
|
||||
|
||||
@@ -195,39 +195,39 @@ template <> void GetDefaultMember::handle(const CachedScriptableImage& s) {
|
||||
|
||||
|
||||
bool CachedScriptableMask::update(Context& ctx) {
|
||||
if (script.update(ctx)) {
|
||||
mask.clear();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (script.update(ctx)) {
|
||||
mask.clear();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const AlphaMask& CachedScriptableMask::get(const GeneratedImage::Options& img_options) {
|
||||
if (mask.isLoaded()) {
|
||||
// already loaded?
|
||||
if (img_options.width == 0 && img_options.height == 0) return mask;
|
||||
if (mask.hasSize(wxSize(img_options.width,img_options.height))) return mask;
|
||||
}
|
||||
// load?
|
||||
getNoCache(img_options,mask);
|
||||
return mask;
|
||||
if (mask.isLoaded()) {
|
||||
// already loaded?
|
||||
if (img_options.width == 0 && img_options.height == 0) return mask;
|
||||
if (mask.hasSize(wxSize(img_options.width,img_options.height))) return mask;
|
||||
}
|
||||
// load?
|
||||
getNoCache(img_options,mask);
|
||||
return mask;
|
||||
}
|
||||
void CachedScriptableMask::getNoCache(const GeneratedImage::Options& img_options, AlphaMask& other_mask) const {
|
||||
if (script.isBlank()) {
|
||||
other_mask.clear();
|
||||
} else {
|
||||
Image image = script.generate(img_options);
|
||||
other_mask.load(image);
|
||||
}
|
||||
if (script.isBlank()) {
|
||||
other_mask.clear();
|
||||
} else {
|
||||
Image image = script.generate(img_options);
|
||||
other_mask.load(image);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void Reader::handle(CachedScriptableMask& i) {
|
||||
handle(i.script);
|
||||
handle(i.script);
|
||||
}
|
||||
template <> void Writer::handle(const CachedScriptableMask& i) {
|
||||
handle(i.script);
|
||||
handle(i.script);
|
||||
}
|
||||
template <> void GetDefaultMember::handle(const CachedScriptableMask& i) {
|
||||
handle(i.script);
|
||||
handle(i.script);
|
||||
}
|
||||
|
||||
+91
-91
@@ -26,46 +26,46 @@ class CachedScriptableMask;
|
||||
*/
|
||||
class ScriptableImage {
|
||||
public:
|
||||
inline ScriptableImage() {}
|
||||
inline ScriptableImage(const String& script) : script(script) {}
|
||||
inline ScriptableImage(const GeneratedImageP& gen) : value(gen) {}
|
||||
|
||||
/// Is there a scripted image set?
|
||||
inline bool isScripted() const { return script; }
|
||||
/// Is there an image generator available?
|
||||
inline bool isReady() const { return value; }
|
||||
/// Is there an image set?
|
||||
inline bool isSet() const { return script || value; }
|
||||
|
||||
/// Generate an image.
|
||||
Image generate(const GeneratedImage::Options& options) const;
|
||||
/// How should images be combined with the background?
|
||||
ImageCombine combine() const;
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
/// Can this be safely generated from another thread?
|
||||
inline bool threadSafe() const { return !value || value->threadSafe(); }
|
||||
/// Is this image specific to the set (the local_package)?
|
||||
inline bool local() const { return value && value->local(); }
|
||||
/// Is this image blank?
|
||||
inline bool isBlank() const { return !value || value->isBlank(); }
|
||||
|
||||
/// Get access to the script, be careful
|
||||
inline Script& getMutableScript() { return script.getMutableScript(); }
|
||||
/// Get access to the script, always returns a valid script
|
||||
ScriptP getValidScriptP();
|
||||
|
||||
inline ScriptableImage() {}
|
||||
inline ScriptableImage(const String& script) : script(script) {}
|
||||
inline ScriptableImage(const GeneratedImageP& gen) : value(gen) {}
|
||||
|
||||
/// Is there a scripted image set?
|
||||
inline bool isScripted() const { return script; }
|
||||
/// Is there an image generator available?
|
||||
inline bool isReady() const { return value; }
|
||||
/// Is there an image set?
|
||||
inline bool isSet() const { return script || value; }
|
||||
|
||||
/// Generate an image.
|
||||
Image generate(const GeneratedImage::Options& options) const;
|
||||
/// How should images be combined with the background?
|
||||
ImageCombine combine() const;
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
/// Can this be safely generated from another thread?
|
||||
inline bool threadSafe() const { return !value || value->threadSafe(); }
|
||||
/// Is this image specific to the set (the local_package)?
|
||||
inline bool local() const { return value && value->local(); }
|
||||
/// Is this image blank?
|
||||
inline bool isBlank() const { return !value || value->isBlank(); }
|
||||
|
||||
/// Get access to the script, be careful
|
||||
inline Script& getMutableScript() { return script.getMutableScript(); }
|
||||
/// Get access to the script, always returns a valid script
|
||||
ScriptP getValidScriptP();
|
||||
|
||||
protected:
|
||||
OptionalScript script; ///< The script, not really optional
|
||||
GeneratedImageP value; ///< The image generator
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
OptionalScript script; ///< The script, not really optional
|
||||
GeneratedImageP value; ///< The image generator
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
/// Missing for now
|
||||
@@ -79,33 +79,33 @@ GeneratedImageP image_from_script(const ScriptValueP& value);
|
||||
/// A version of ScriptableImage that does caching
|
||||
class CachedScriptableImage : public ScriptableImage {
|
||||
public:
|
||||
inline CachedScriptableImage() {}
|
||||
inline CachedScriptableImage(const String& script) : ScriptableImage(script) {}
|
||||
inline CachedScriptableImage(const GeneratedImageP& gen) : ScriptableImage(gen) {}
|
||||
|
||||
/// Generate an image, using caching if possible.
|
||||
/** *combine should be set to the combine value of the style.
|
||||
* It will be overwritten if the image specifies a non-default combine.
|
||||
* After this call, either:
|
||||
* - combine <= COMBINE_NORMAL && bitmap->Ok()
|
||||
* - or combine > COMBINE_NORMAL && image->Ok()
|
||||
* Optionally, an alpha mask is applied to the image.
|
||||
*/
|
||||
void generateCached(const GeneratedImage::Options& img_options,
|
||||
CachedScriptableMask* mask,
|
||||
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size);
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
/// Clears the cache
|
||||
void clearCache();
|
||||
|
||||
inline CachedScriptableImage() {}
|
||||
inline CachedScriptableImage(const String& script) : ScriptableImage(script) {}
|
||||
inline CachedScriptableImage(const GeneratedImageP& gen) : ScriptableImage(gen) {}
|
||||
|
||||
/// Generate an image, using caching if possible.
|
||||
/** *combine should be set to the combine value of the style.
|
||||
* It will be overwritten if the image specifies a non-default combine.
|
||||
* After this call, either:
|
||||
* - combine <= COMBINE_NORMAL && bitmap->Ok()
|
||||
* - or combine > COMBINE_NORMAL && image->Ok()
|
||||
* Optionally, an alpha mask is applied to the image.
|
||||
*/
|
||||
void generateCached(const GeneratedImage::Options& img_options,
|
||||
CachedScriptableMask* mask,
|
||||
ImageCombine* combine, wxBitmap* bitmap, wxImage* image, RealSize* size);
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
/// Clears the cache
|
||||
void clearCache();
|
||||
|
||||
private:
|
||||
Image cached_i; ///< The cached image
|
||||
Bitmap cached_b; ///< *or* the cached bitmap
|
||||
RealSize cached_size; ///< The size of the image before rotating
|
||||
Radians cached_angle;
|
||||
Image cached_i; ///< The cached image
|
||||
Bitmap cached_b; ///< *or* the cached bitmap
|
||||
RealSize cached_size; ///< The size of the image before rotating
|
||||
Radians cached_angle;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : CachedScriptableMask
|
||||
@@ -113,33 +113,33 @@ class CachedScriptableImage : public ScriptableImage {
|
||||
/// A version of ScriptableImage that caches an AlphaMask
|
||||
class CachedScriptableMask {
|
||||
public:
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
/// Get the alpha mask; with the given options
|
||||
/** if img_options.width == 0 and the mask is already loaded, just returns it.
|
||||
* Returns a reference, so calling again might change earlier results.
|
||||
*/
|
||||
const AlphaMask& get(const GeneratedImage::Options& img_options);
|
||||
|
||||
/// Get a mask that is not cached
|
||||
void getNoCache(const GeneratedImage::Options& img_options, AlphaMask& mask) const;
|
||||
|
||||
/// Get the mask directly from the cache, without updating
|
||||
/** Should only be used after get() was called before, otherwise an old mask might be returned */
|
||||
inline const AlphaMask& getFromCache() const { return mask; }
|
||||
|
||||
|
||||
/// Update the script, returns true if the value has changed
|
||||
bool update(Context& ctx);
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
/// Get the alpha mask; with the given options
|
||||
/** if img_options.width == 0 and the mask is already loaded, just returns it.
|
||||
* Returns a reference, so calling again might change earlier results.
|
||||
*/
|
||||
const AlphaMask& get(const GeneratedImage::Options& img_options);
|
||||
|
||||
/// Get a mask that is not cached
|
||||
void getNoCache(const GeneratedImage::Options& img_options, AlphaMask& mask) const;
|
||||
|
||||
/// Get the mask directly from the cache, without updating
|
||||
/** Should only be used after get() was called before, otherwise an old mask might be returned */
|
||||
inline const AlphaMask& getFromCache() const { return mask; }
|
||||
|
||||
private:
|
||||
ScriptableImage script;
|
||||
AlphaMask mask;
|
||||
friend class Reader;
|
||||
friend class Writer;
|
||||
friend class GetDefaultMember;
|
||||
ScriptableImage script;
|
||||
AlphaMask mask;
|
||||
friend class Reader;
|
||||
friend class Writer;
|
||||
friend class GetDefaultMember;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+683
-683
File diff suppressed because it is too large
Load Diff
+66
-66
@@ -13,7 +13,7 @@
|
||||
|
||||
// don't use script profiling in final build
|
||||
#ifndef UNICODE
|
||||
#error "It looks like you are building the final release; disable USE_SCRIPT_PROFILING!"
|
||||
#error "It looks like you are building the final release; disable USE_SCRIPT_PROFILING!"
|
||||
#endif
|
||||
|
||||
DECLARE_TYPEOF(map<size_t COMMA FunctionProfileP>);
|
||||
@@ -21,20 +21,20 @@ DECLARE_TYPEOF(map<size_t COMMA FunctionProfileP>);
|
||||
// ----------------------------------------------------------------------------- : Timer
|
||||
|
||||
Timer::Timer() {
|
||||
start = timer_now() + delta;
|
||||
start = timer_now() + delta;
|
||||
}
|
||||
|
||||
ProfileTime Timer::time() {
|
||||
ProfileTime end = timer_now() + delta;
|
||||
ProfileTime diff = end - start;
|
||||
start = end;
|
||||
return diff;
|
||||
ProfileTime end = timer_now() + delta;
|
||||
ProfileTime diff = end - start;
|
||||
start = end;
|
||||
return diff;
|
||||
}
|
||||
|
||||
void Timer::exclude_time() {
|
||||
ProfileTime delta_delta = time();
|
||||
delta -= delta_delta;
|
||||
start -= delta_delta;
|
||||
ProfileTime delta_delta = time();
|
||||
delta -= delta_delta;
|
||||
start -= delta_delta;
|
||||
}
|
||||
|
||||
ProfileTime Timer::delta = 0;
|
||||
@@ -44,13 +44,13 @@ ProfileTime Timer::delta = 0;
|
||||
FunctionProfile profile_root(_("root"));
|
||||
|
||||
inline bool compare_time(const FunctionProfileP& a, const FunctionProfileP& b) {
|
||||
return a->time_ticks < b->time_ticks;
|
||||
return a->time_ticks < b->time_ticks;
|
||||
}
|
||||
void FunctionProfile::get_children(vector<FunctionProfileP>& out) const {
|
||||
FOR_EACH_CONST(c,children) {
|
||||
out.push_back(c.second);
|
||||
}
|
||||
sort(out.begin(), out.end(), compare_time);
|
||||
FOR_EACH_CONST(c,children) {
|
||||
out.push_back(c.second);
|
||||
}
|
||||
sort(out.begin(), out.end(), compare_time);
|
||||
}
|
||||
|
||||
// note: not thread safe
|
||||
@@ -58,31 +58,31 @@ FunctionProfile profile_aggr(_("everywhere"));
|
||||
|
||||
void profile_aggregate(FunctionProfile& parent, int level, int max_level, const FunctionProfile& p);
|
||||
void profile_aggregate(FunctionProfile& parent, int level, int max_level, size_t idx, const FunctionProfile& p) {
|
||||
// add to item at idx
|
||||
FunctionProfileP& fpp = parent.children[idx];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(p.name));
|
||||
}
|
||||
fpp->time_ticks += p.time_ticks;
|
||||
fpp->calls += p.calls;
|
||||
// recurse
|
||||
if (level == 0) {
|
||||
profile_aggregate(parent, level, max_level, p);
|
||||
}
|
||||
if (level < max_level) {
|
||||
profile_aggregate(*fpp, level + 1, max_level, p);
|
||||
}
|
||||
// add to item at idx
|
||||
FunctionProfileP& fpp = parent.children[idx];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(p.name));
|
||||
}
|
||||
fpp->time_ticks += p.time_ticks;
|
||||
fpp->calls += p.calls;
|
||||
// recurse
|
||||
if (level == 0) {
|
||||
profile_aggregate(parent, level, max_level, p);
|
||||
}
|
||||
if (level < max_level) {
|
||||
profile_aggregate(*fpp, level + 1, max_level, p);
|
||||
}
|
||||
}
|
||||
void profile_aggregate(FunctionProfile& parent, int level, int max_level, const FunctionProfile& p) {
|
||||
FOR_EACH_CONST(c, p.children) {
|
||||
profile_aggregate(parent, level, max_level, c.first, *c.second);
|
||||
}
|
||||
FOR_EACH_CONST(c, p.children) {
|
||||
profile_aggregate(parent, level, max_level, c.first, *c.second);
|
||||
}
|
||||
}
|
||||
|
||||
const FunctionProfile& profile_aggregated(int max_level) {
|
||||
profile_aggr.children.clear();
|
||||
profile_aggregate(profile_aggr, 0, max_level, profile_root);
|
||||
return profile_aggr;
|
||||
profile_aggr.children.clear();
|
||||
profile_aggregate(profile_aggr, 0, max_level, profile_root);
|
||||
return profile_aggr;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Profiler
|
||||
@@ -91,53 +91,53 @@ FunctionProfile* Profiler::function = &profile_root;
|
||||
|
||||
// Enter a function
|
||||
Profiler::Profiler(Timer& timer, Variable function_name)
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
{
|
||||
if ((int)function_name >= 0) {
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_name << 1 | 1];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(variable_to_string(function_name)));
|
||||
}
|
||||
function = fpp.get();
|
||||
}
|
||||
timer.exclude_time();
|
||||
if ((int)function_name >= 0) {
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_name << 1 | 1];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(variable_to_string(function_name)));
|
||||
}
|
||||
function = fpp.get();
|
||||
}
|
||||
timer.exclude_time();
|
||||
}
|
||||
|
||||
// Enter a function
|
||||
Profiler::Profiler(Timer& timer, const Char* function_name)
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
{
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_name];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(function_name));
|
||||
}
|
||||
function = fpp.get();
|
||||
timer.exclude_time();
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_name];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(function_name));
|
||||
}
|
||||
function = fpp.get();
|
||||
timer.exclude_time();
|
||||
}
|
||||
|
||||
// Enter a function
|
||||
Profiler::Profiler(Timer& timer, void* function_object, const String& function_name)
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
: timer(timer)
|
||||
, parent(function) // push
|
||||
{
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_object];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(function_name));
|
||||
}
|
||||
function = fpp.get();
|
||||
timer.exclude_time();
|
||||
FunctionProfileP& fpp = parent->children[(size_t)function_object];
|
||||
if (!fpp) {
|
||||
fpp = intrusive(new FunctionProfile(function_name));
|
||||
}
|
||||
function = fpp.get();
|
||||
timer.exclude_time();
|
||||
}
|
||||
|
||||
// Leave a function
|
||||
Profiler::~Profiler() {
|
||||
ProfileTime time = timer.time();
|
||||
if (function == parent) return; // don't count
|
||||
function->time_ticks += time;
|
||||
function->time_ticks_max = max(function->time_ticks_max,time);
|
||||
function->calls += 1;
|
||||
function = parent; // pop
|
||||
ProfileTime time = timer.time();
|
||||
if (function == parent) return; // don't count
|
||||
function->time_ticks += time;
|
||||
function->time_ticks_max = max(function->time_ticks_max,time);
|
||||
function->calls += 1;
|
||||
function = parent; // pop
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+68
-68
@@ -24,50 +24,50 @@ DECLARE_POINTER_TYPE(FunctionProfile);
|
||||
// ----------------------------------------------------------------------------- : Timer
|
||||
|
||||
#ifdef _MSC_VER
|
||||
typedef LONGLONG ProfileTime;
|
||||
typedef LONGLONG ProfileTime;
|
||||
|
||||
inline ProfileTime timer_now() {
|
||||
LARGE_INTEGER i;
|
||||
QueryPerformanceCounter(&i);
|
||||
return i.QuadPart;
|
||||
}
|
||||
inline ProfileTime timer_resolution() {
|
||||
LARGE_INTEGER i;
|
||||
QueryPerformanceFrequency(&i);
|
||||
return i.QuadPart;
|
||||
}
|
||||
inline ProfileTime timer_now() {
|
||||
LARGE_INTEGER i;
|
||||
QueryPerformanceCounter(&i);
|
||||
return i.QuadPart;
|
||||
}
|
||||
inline ProfileTime timer_resolution() {
|
||||
LARGE_INTEGER i;
|
||||
QueryPerformanceFrequency(&i);
|
||||
return i.QuadPart;
|
||||
}
|
||||
|
||||
inline const char * mangled_name(const type_info& t) {
|
||||
return t.raw_name();
|
||||
}
|
||||
inline const char * mangled_name(const type_info& t) {
|
||||
return t.raw_name();
|
||||
}
|
||||
#else
|
||||
// clock() has nanosecond resolution on Linux
|
||||
// on any other platform, stil the best way.
|
||||
typedef clock_t ProfileTime;
|
||||
// clock() has nanosecond resolution on Linux
|
||||
// on any other platform, stil the best way.
|
||||
typedef clock_t ProfileTime;
|
||||
|
||||
inline ProfileTime timer_now() {
|
||||
return clock();
|
||||
}
|
||||
inline ProfileTime timer_resolution() {
|
||||
return CLOCKS_PER_SEC;
|
||||
}
|
||||
inline ProfileTime timer_now() {
|
||||
return clock();
|
||||
}
|
||||
inline ProfileTime timer_resolution() {
|
||||
return CLOCKS_PER_SEC;
|
||||
}
|
||||
|
||||
inline const char * mangled_name(const type_info& t) {
|
||||
return t.name();
|
||||
}
|
||||
inline const char * mangled_name(const type_info& t) {
|
||||
return t.name();
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Simple execution timer
|
||||
class Timer {
|
||||
public:
|
||||
Timer();
|
||||
/// The time the timer has been running, resets the timer
|
||||
inline ProfileTime time();
|
||||
/// Exclude the time since the last reset from ALL running timers
|
||||
inline void exclude_time();
|
||||
Timer();
|
||||
/// The time the timer has been running, resets the timer
|
||||
inline ProfileTime time();
|
||||
/// Exclude the time since the last reset from ALL running timers
|
||||
inline void exclude_time();
|
||||
private:
|
||||
ProfileTime start;
|
||||
static ProfileTime delta; ///< Time excluded
|
||||
ProfileTime start;
|
||||
static ProfileTime delta; ///< Time excluded
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : FunctionProfile
|
||||
@@ -75,26 +75,26 @@ class Timer {
|
||||
/// How much time was spent in a function?
|
||||
class FunctionProfile : public IntrusivePtrBase<FunctionProfile> {
|
||||
public:
|
||||
FunctionProfile(const String& name)
|
||||
: name(name), time_ticks(0), time_ticks_max(0), calls(0)
|
||||
{}
|
||||
FunctionProfile(const String& name)
|
||||
: name(name), time_ticks(0), time_ticks_max(0), calls(0)
|
||||
{}
|
||||
|
||||
String name;
|
||||
ProfileTime time_ticks;
|
||||
ProfileTime time_ticks_max;
|
||||
int calls;
|
||||
|
||||
/// for each id, called children
|
||||
/** we (ab)use the fact that all pointers are even to store both pointers and ids */
|
||||
map<size_t,FunctionProfileP> children;
|
||||
String name;
|
||||
ProfileTime time_ticks;
|
||||
ProfileTime time_ticks_max;
|
||||
int calls;
|
||||
|
||||
/// for each id, called children
|
||||
/** we (ab)use the fact that all pointers are even to store both pointers and ids */
|
||||
map<size_t,FunctionProfileP> children;
|
||||
|
||||
/// The children, sorted by time
|
||||
void get_children(vector<FunctionProfileP>& out) const;
|
||||
/// The children, sorted by time
|
||||
void get_children(vector<FunctionProfileP>& out) const;
|
||||
|
||||
/// Time in seconds
|
||||
inline double total_time() const { return time_ticks / (double)timer_resolution(); }
|
||||
inline double avg_time() const { return total_time() / calls; }
|
||||
inline double max_time() const { return time_ticks_max / (double)timer_resolution(); }
|
||||
/// Time in seconds
|
||||
inline double total_time() const { return time_ticks / (double)timer_resolution(); }
|
||||
inline double avg_time() const { return total_time() / calls; }
|
||||
inline double max_time() const { return time_ticks_max / (double)timer_resolution(); }
|
||||
};
|
||||
|
||||
/// The root profile
|
||||
@@ -108,30 +108,30 @@ const FunctionProfile& profile_aggregated(int level = 1);
|
||||
/// Profile a single function call
|
||||
class Profiler {
|
||||
public:
|
||||
/// Log the fact that the function function_name is entered, ends when profiler goes out of scope.
|
||||
/** Time between the construction of Timer and the construction of Profiler is excluded from ALL profiles.
|
||||
*/
|
||||
Profiler(Timer& timer, Variable function_name);
|
||||
/// As above, but with a constant name
|
||||
Profiler(Timer& timer, const Char* function_name);
|
||||
/// As above, but using a function object instead of a name,
|
||||
/** if we haven't seen the object before, it gets the given name. */
|
||||
Profiler(Timer& timer, void* function_object, const String& function_name);
|
||||
/// Log the fact that the function is left
|
||||
~Profiler();
|
||||
/// Log the fact that the function function_name is entered, ends when profiler goes out of scope.
|
||||
/** Time between the construction of Timer and the construction of Profiler is excluded from ALL profiles.
|
||||
*/
|
||||
Profiler(Timer& timer, Variable function_name);
|
||||
/// As above, but with a constant name
|
||||
Profiler(Timer& timer, const Char* function_name);
|
||||
/// As above, but using a function object instead of a name,
|
||||
/** if we haven't seen the object before, it gets the given name. */
|
||||
Profiler(Timer& timer, void* function_object, const String& function_name);
|
||||
/// Log the fact that the function is left
|
||||
~Profiler();
|
||||
private:
|
||||
Timer& timer;
|
||||
static FunctionProfile* function; ///< function we are in
|
||||
FunctionProfile* parent;
|
||||
Timer& timer;
|
||||
static FunctionProfile* function; ///< function we are in
|
||||
FunctionProfile* parent;
|
||||
};
|
||||
|
||||
// Profile the current function (all following code in the current block) under the given name
|
||||
#define PROFILER(name) \
|
||||
Timer profile_timer; \
|
||||
Profiler profiler(profile_timer, name)
|
||||
Timer profile_timer; \
|
||||
Profiler profiler(profile_timer, name)
|
||||
#define PROFILER2(name1,name2) \
|
||||
Timer profile_timer; \
|
||||
Profiler profiler(profile_timer, name1,name2)
|
||||
Timer profile_timer; \
|
||||
Profiler profiler(profile_timer, name1,name2)
|
||||
|
||||
#else // USE_SCRIPT_PROFILING
|
||||
|
||||
|
||||
+268
-268
@@ -18,134 +18,134 @@ typedef map<String, Variable> Variables;
|
||||
Variables variables;
|
||||
DECLARE_TYPEOF(Variables);
|
||||
#ifdef _DEBUG
|
||||
vector<String> variable_names;
|
||||
vector<String> variable_names;
|
||||
#endif
|
||||
|
||||
/// Return a unique name for a variable to allow for faster loopups
|
||||
Variable string_to_variable(const String& s) {
|
||||
Variables::iterator it = variables.find(s);
|
||||
if (it == variables.end()) {
|
||||
#ifdef _DEBUG
|
||||
variable_names.push_back(s);
|
||||
assert(s == canonical_name_form(s)); // only use cannocial names
|
||||
#endif
|
||||
Variable v = (Variable)variables.size();
|
||||
variables.insert(make_pair(s,v));
|
||||
return v;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
Variables::iterator it = variables.find(s);
|
||||
if (it == variables.end()) {
|
||||
#ifdef _DEBUG
|
||||
variable_names.push_back(s);
|
||||
assert(s == canonical_name_form(s)); // only use cannocial names
|
||||
#endif
|
||||
Variable v = (Variable)variables.size();
|
||||
variables.insert(make_pair(s,v));
|
||||
return v;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of a vaiable
|
||||
/** Warning: this function is slow, it should only be used for error messages and such.
|
||||
*/
|
||||
String variable_to_string(Variable v) {
|
||||
FOR_EACH(vi, variables) {
|
||||
if (vi.second == v) return replace_all(vi.first, _(" "), _("_"));
|
||||
}
|
||||
throw InternalError(String(_("Variable not found: ")) << v);
|
||||
FOR_EACH(vi, variables) {
|
||||
if (vi.second == v) return replace_all(vi.first, _(" "), _("_"));
|
||||
}
|
||||
throw InternalError(String(_("Variable not found: ")) << v);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : CommonVariables
|
||||
|
||||
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);
|
||||
VarN(in_context,_("in context"));
|
||||
Var(recursive);
|
||||
Var(order);
|
||||
Var(begin);
|
||||
Var(end);
|
||||
Var(filter);
|
||||
Var(choice);
|
||||
Var(choices);
|
||||
Var(format);
|
||||
Var(tag);
|
||||
Var(contents);
|
||||
Var(set);
|
||||
Var(game);
|
||||
Var(stylesheet);
|
||||
VarN(card_style,_("card style"));
|
||||
Var(card);
|
||||
Var(styling);
|
||||
Var(value);
|
||||
Var(condition);
|
||||
Var(language);
|
||||
assert(variables.size() == SCRIPT_VAR_CUSTOM_FIRST);
|
||||
#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);
|
||||
VarN(in_context,_("in context"));
|
||||
Var(recursive);
|
||||
Var(order);
|
||||
Var(begin);
|
||||
Var(end);
|
||||
Var(filter);
|
||||
Var(choice);
|
||||
Var(choices);
|
||||
Var(format);
|
||||
Var(tag);
|
||||
Var(contents);
|
||||
Var(set);
|
||||
Var(game);
|
||||
Var(stylesheet);
|
||||
VarN(card_style,_("card style"));
|
||||
Var(card);
|
||||
Var(styling);
|
||||
Var(value);
|
||||
Var(condition);
|
||||
Var(language);
|
||||
assert(variables.size() == SCRIPT_VAR_CUSTOM_FIRST);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Script
|
||||
|
||||
ScriptType Script::type() const {
|
||||
return SCRIPT_FUNCTION;
|
||||
return SCRIPT_FUNCTION;
|
||||
}
|
||||
String Script::typeName() const {
|
||||
return _("function");
|
||||
return _("function");
|
||||
}
|
||||
ScriptValueP Script::do_eval(Context& ctx, bool openScope) const {
|
||||
return ctx.eval(*this, openScope);
|
||||
return ctx.eval(*this, openScope);
|
||||
}
|
||||
ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const {
|
||||
return ctx.dependencies(dep, *this);
|
||||
return ctx.dependencies(dep, *this);
|
||||
}
|
||||
|
||||
static const unsigned int INVALID_ADDRESS = 0x03FFFFFF;
|
||||
|
||||
unsigned int Script::addInstruction(InstructionType t) {
|
||||
assert( t == I_JUMP
|
||||
|| t == I_JUMP_IF_NOT
|
||||
|| t == I_JUMP_SC_AND
|
||||
|| t == I_JUMP_SC_OR
|
||||
|| t == I_LOOP
|
||||
|| t == I_LOOP_WITH_KEY
|
||||
|| t == I_POP);
|
||||
Instruction i = {t, {INVALID_ADDRESS}};
|
||||
instructions.push_back(i);
|
||||
return getLabel() - 1;
|
||||
assert( t == I_JUMP
|
||||
|| t == I_JUMP_IF_NOT
|
||||
|| t == I_JUMP_SC_AND
|
||||
|| t == I_JUMP_SC_OR
|
||||
|| t == I_LOOP
|
||||
|| t == I_LOOP_WITH_KEY
|
||||
|| t == I_POP);
|
||||
Instruction i = {t, {INVALID_ADDRESS}};
|
||||
instructions.push_back(i);
|
||||
return getLabel() - 1;
|
||||
}
|
||||
void Script::addInstruction(InstructionType t, unsigned int d) {
|
||||
// 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
|
||||
instructions.back().instr = I_MEMBER_C;
|
||||
return;
|
||||
}*/
|
||||
Instruction i = {t, {d}};
|
||||
instructions.push_back(i);
|
||||
// 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
|
||||
instructions.back().instr = I_MEMBER_C;
|
||||
return;
|
||||
}*/
|
||||
Instruction i = {t, {d}};
|
||||
instructions.push_back(i);
|
||||
}
|
||||
void Script::addInstruction(InstructionType t, const ScriptValueP& c) {
|
||||
constants.push_back(c);
|
||||
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
||||
instructions.push_back(i);
|
||||
constants.push_back(c);
|
||||
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
||||
instructions.push_back(i);
|
||||
}
|
||||
void Script::addInstruction(InstructionType t, const String& s) {
|
||||
constants.push_back(to_script(s));
|
||||
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
||||
instructions.push_back(i);
|
||||
constants.push_back(to_script(s));
|
||||
Instruction i = {t, {(unsigned int)constants.size() - 1}};
|
||||
instructions.push_back(i);
|
||||
}
|
||||
|
||||
void Script::comeFrom(unsigned int pos) {
|
||||
assert( instructions.at(pos).instr == I_JUMP
|
||||
|| instructions.at(pos).instr == I_JUMP_IF_NOT
|
||||
|| instructions.at(pos).instr == I_JUMP_SC_AND
|
||||
|| instructions.at(pos).instr == I_JUMP_SC_OR
|
||||
|| instructions.at(pos).instr == I_LOOP
|
||||
|| instructions.at(pos).instr == I_LOOP_WITH_KEY);
|
||||
assert( instructions.at(pos).data == INVALID_ADDRESS );
|
||||
instructions.at(pos).data = (unsigned int)instructions.size();
|
||||
assert( instructions.at(pos).instr == I_JUMP
|
||||
|| instructions.at(pos).instr == I_JUMP_IF_NOT
|
||||
|| instructions.at(pos).instr == I_JUMP_SC_AND
|
||||
|| instructions.at(pos).instr == I_JUMP_SC_OR
|
||||
|| instructions.at(pos).instr == I_LOOP
|
||||
|| instructions.at(pos).instr == I_LOOP_WITH_KEY);
|
||||
assert( instructions.at(pos).data == INVALID_ADDRESS );
|
||||
instructions.at(pos).data = (unsigned int)instructions.size();
|
||||
}
|
||||
|
||||
unsigned int Script::getLabel() const {
|
||||
return (unsigned int)instructions.size();
|
||||
return (unsigned int)instructions.size();
|
||||
}
|
||||
|
||||
DECLARE_TYPEOF_COLLECTION(Instruction);
|
||||
@@ -153,91 +153,91 @@ DECLARE_TYPEOF_COLLECTION(Instruction);
|
||||
#ifdef _DEBUG // debugging
|
||||
|
||||
String Script::dumpScript() const {
|
||||
String ret;
|
||||
int pos = 0;
|
||||
FOR_EACH_CONST(i, instructions) {
|
||||
wxLogDebug(dumpInstr(pos, i));
|
||||
ret += dumpInstr(pos++, i) + _("\n");
|
||||
}
|
||||
return ret;
|
||||
String ret;
|
||||
int pos = 0;
|
||||
FOR_EACH_CONST(i, instructions) {
|
||||
wxLogDebug(dumpInstr(pos, i));
|
||||
ret += dumpInstr(pos++, i) + _("\n");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
||||
String ret = String::Format(_("%d:\t"),pos);
|
||||
// instruction
|
||||
switch (i.instr) {
|
||||
case I_NOP: ret += _("nop"); break;
|
||||
case I_PUSH_CONST: ret += _("push"); break;
|
||||
case I_JUMP: ret += _("jump"); break;
|
||||
case I_JUMP_IF_NOT: ret += _("jnz"); break;
|
||||
case I_JUMP_SC_AND: ret += _("jump sc and");break;
|
||||
case I_JUMP_SC_OR: ret += _("jump sc or"); break;
|
||||
case I_GET_VAR: ret += _("get"); break;
|
||||
case I_SET_VAR: ret += _("set"); break;
|
||||
case I_MEMBER_C: ret += _("member_c"); break;
|
||||
case I_LOOP: ret += _("loop"); break;
|
||||
case I_LOOP_WITH_KEY:ret += _("loop with key"); break;
|
||||
case I_MAKE_OBJECT: ret += _("make object");break;
|
||||
case I_CALL: ret += _("call"); break;
|
||||
case I_CLOSURE: ret += _("closure"); break;
|
||||
case I_UNARY: ret += _("unary\t");
|
||||
switch (i.instr1) {
|
||||
case I_ITERATOR_C: ret += _("iterator_c"); break;
|
||||
case I_NEGATE: ret += _("negate"); break;
|
||||
case I_NOT: ret += _("not"); break;
|
||||
}
|
||||
break;
|
||||
case I_BINARY: ret += _("binary\t");
|
||||
switch (i.instr2) {
|
||||
case I_ITERATOR_R: ret += _("iterator_r"); break;
|
||||
case I_MEMBER: ret += _("member"); break;
|
||||
case I_ADD: ret += _("+"); break;
|
||||
case I_SUB: ret += _("-"); break;
|
||||
case I_MUL: ret += _("*"); break;
|
||||
case I_DIV: ret += _("/"); break;
|
||||
case I_MOD: ret += _("mod"); break;
|
||||
case I_AND: ret += _("and"); break;
|
||||
case I_OR: ret += _("or"); break;
|
||||
case I_XOR: ret += _("xor"); break;
|
||||
case I_EQ: ret += _("=="); break;
|
||||
case I_NEQ: ret += _("!="); break;
|
||||
case I_LT: ret += _("<"); break;
|
||||
case I_GT: ret += _(">"); break;
|
||||
case I_LE: ret += _("<="); break;
|
||||
case I_GE: ret += _(">="); break;
|
||||
case I_OR_ELSE: ret += _("or else"); break;
|
||||
}
|
||||
break;
|
||||
case I_TERNARY: ret += _("ternary\t");
|
||||
switch (i.instr3) {
|
||||
case I_RGB: ret += _("rgb"); break;
|
||||
}
|
||||
break;
|
||||
case I_QUATERNARY: ret += _("quaternary\t");
|
||||
switch (i.instr3) {
|
||||
case I_RGBA: ret += _("rgba"); break;
|
||||
}
|
||||
break;
|
||||
case I_DUP: ret += _("dup"); break;
|
||||
case I_POP: ret += _("pop"); break;
|
||||
case I_TAILCALL: ret += _("tailcall"); break;
|
||||
}
|
||||
// arg
|
||||
switch (i.instr) {
|
||||
case I_PUSH_CONST: case I_MEMBER_C: // const
|
||||
ret += _("\t") + constants[i.data]->typeName();
|
||||
break;
|
||||
case I_JUMP: case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR:
|
||||
case I_LOOP: case I_LOOP_WITH_KEY:
|
||||
case I_MAKE_OBJECT:
|
||||
case I_CALL: case I_CLOSURE: case I_DUP: // int
|
||||
ret += String::Format(_("\t%d"), i.data);
|
||||
break;
|
||||
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
|
||||
ret += _("\t") + variable_to_string((Variable)i.data);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
String ret = String::Format(_("%d:\t"),pos);
|
||||
// instruction
|
||||
switch (i.instr) {
|
||||
case I_NOP: ret += _("nop"); break;
|
||||
case I_PUSH_CONST: ret += _("push"); break;
|
||||
case I_JUMP: ret += _("jump"); break;
|
||||
case I_JUMP_IF_NOT: ret += _("jnz"); break;
|
||||
case I_JUMP_SC_AND: ret += _("jump sc and");break;
|
||||
case I_JUMP_SC_OR: ret += _("jump sc or"); break;
|
||||
case I_GET_VAR: ret += _("get"); break;
|
||||
case I_SET_VAR: ret += _("set"); break;
|
||||
case I_MEMBER_C: ret += _("member_c"); break;
|
||||
case I_LOOP: ret += _("loop"); break;
|
||||
case I_LOOP_WITH_KEY:ret += _("loop with key"); break;
|
||||
case I_MAKE_OBJECT: ret += _("make object");break;
|
||||
case I_CALL: ret += _("call"); break;
|
||||
case I_CLOSURE: ret += _("closure"); break;
|
||||
case I_UNARY: ret += _("unary\t");
|
||||
switch (i.instr1) {
|
||||
case I_ITERATOR_C: ret += _("iterator_c"); break;
|
||||
case I_NEGATE: ret += _("negate"); break;
|
||||
case I_NOT: ret += _("not"); break;
|
||||
}
|
||||
break;
|
||||
case I_BINARY: ret += _("binary\t");
|
||||
switch (i.instr2) {
|
||||
case I_ITERATOR_R: ret += _("iterator_r"); break;
|
||||
case I_MEMBER: ret += _("member"); break;
|
||||
case I_ADD: ret += _("+"); break;
|
||||
case I_SUB: ret += _("-"); break;
|
||||
case I_MUL: ret += _("*"); break;
|
||||
case I_DIV: ret += _("/"); break;
|
||||
case I_MOD: ret += _("mod"); break;
|
||||
case I_AND: ret += _("and"); break;
|
||||
case I_OR: ret += _("or"); break;
|
||||
case I_XOR: ret += _("xor"); break;
|
||||
case I_EQ: ret += _("=="); break;
|
||||
case I_NEQ: ret += _("!="); break;
|
||||
case I_LT: ret += _("<"); break;
|
||||
case I_GT: ret += _(">"); break;
|
||||
case I_LE: ret += _("<="); break;
|
||||
case I_GE: ret += _(">="); break;
|
||||
case I_OR_ELSE: ret += _("or else"); break;
|
||||
}
|
||||
break;
|
||||
case I_TERNARY: ret += _("ternary\t");
|
||||
switch (i.instr3) {
|
||||
case I_RGB: ret += _("rgb"); break;
|
||||
}
|
||||
break;
|
||||
case I_QUATERNARY: ret += _("quaternary\t");
|
||||
switch (i.instr3) {
|
||||
case I_RGBA: ret += _("rgba"); break;
|
||||
}
|
||||
break;
|
||||
case I_DUP: ret += _("dup"); break;
|
||||
case I_POP: ret += _("pop"); break;
|
||||
case I_TAILCALL: ret += _("tailcall"); break;
|
||||
}
|
||||
// arg
|
||||
switch (i.instr) {
|
||||
case I_PUSH_CONST: case I_MEMBER_C: // const
|
||||
ret += _("\t") + constants[i.data]->typeName();
|
||||
break;
|
||||
case I_JUMP: case I_JUMP_IF_NOT: case I_JUMP_SC_AND: case I_JUMP_SC_OR:
|
||||
case I_LOOP: case I_LOOP_WITH_KEY:
|
||||
case I_MAKE_OBJECT:
|
||||
case I_CALL: case I_CLOSURE: case I_DUP: // int
|
||||
ret += String::Format(_("\t%d"), i.data);
|
||||
break;
|
||||
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
|
||||
ret += _("\t") + variable_to_string((Variable)i.data);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -246,108 +246,108 @@ String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
||||
// ----------------------------------------------------------------------------- : Backtracing
|
||||
|
||||
const Instruction* Script::backtraceSkip(const Instruction* instr, int to_skip) const {
|
||||
unsigned int initial = instr - &instructions[0];
|
||||
for (;instr >= &instructions[0] &&
|
||||
(to_skip || (// we have something to skip
|
||||
instr >= &instructions[1] && (
|
||||
(instr-1)->instr == I_JUMP // always look inside a jump
|
||||
|| (instr-1)->instr == I_NOP // and skip nops
|
||||
)
|
||||
)
|
||||
) ; --instr) {
|
||||
// skip an instruction
|
||||
switch (instr->instr) {
|
||||
case I_PUSH_CONST:
|
||||
case I_GET_VAR: case I_DUP:
|
||||
to_skip -= 1; break; // nett stack effect +1
|
||||
case I_BINARY:
|
||||
to_skip += 1; break; // nett stack effect 1-2 == -1
|
||||
case I_TERNARY:
|
||||
to_skip += 2; break; // nett stack effect 1-3 == -2
|
||||
case I_QUATERNARY:
|
||||
to_skip += 3; break; // nett stack effect 1-4 == -3
|
||||
case I_CALL: case I_CLOSURE:
|
||||
to_skip += instr->data; // arguments of call
|
||||
break;
|
||||
case I_MAKE_OBJECT:
|
||||
to_skip += 2 * instr->data - 1;
|
||||
break;
|
||||
case I_JUMP: {
|
||||
if (instr->data > initial) {
|
||||
// forward jump, so we were in an else branch all along, ignore this jump
|
||||
return instr + 1;
|
||||
}
|
||||
// there will be a way not to take this jump
|
||||
// the part in between will have no significant stack effect
|
||||
unsigned int after_jump = instr + 1 - &instructions[0];
|
||||
for (--instr ; instr >= &instructions[0] ; --instr) {
|
||||
if (instr->instr == I_LOOP && instr->data == after_jump) {
|
||||
// code looks like
|
||||
// 1 (nettstack+1) iterator
|
||||
// 2 (nettstack+1) accumulator (usually push nil)
|
||||
// loop:
|
||||
// 3 I_LOOP end
|
||||
// 4 (netstack+0)
|
||||
// 5 I_JUMP loop
|
||||
// end:
|
||||
// we have not handled anything for this loop, current position is 2,
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_LOOP_WITH_KEY && instr->data == after_jump) {
|
||||
// same as above,
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_JUMP_IF_NOT && instr->data == after_jump) {
|
||||
// code looks like
|
||||
// 1 (nettstack+1)
|
||||
// 2 I_JUMP_IF_NOT else
|
||||
// 3 (nettstack+1)
|
||||
// 4 I_JUMP end
|
||||
// else:
|
||||
// 5 (nettstack+1)
|
||||
// end:
|
||||
// we have already handled 4..5, current position is 2,
|
||||
// we need to skip an additional item for 1
|
||||
to_skip += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++instr; // compensate for the -- in the outer loop
|
||||
break;
|
||||
}
|
||||
case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY:
|
||||
return nullptr; // give up
|
||||
case I_JUMP_SC_AND: case I_JUMP_SC_OR:
|
||||
// assume the fallthrough case, in which case we compared and poped the top of the stack
|
||||
to_skip += 1; break;
|
||||
default:
|
||||
break; // nett stack effect 0
|
||||
}
|
||||
}
|
||||
return instr >= &instructions[0] ? instr : nullptr;
|
||||
unsigned int initial = instr - &instructions[0];
|
||||
for (;instr >= &instructions[0] &&
|
||||
(to_skip || (// we have something to skip
|
||||
instr >= &instructions[1] && (
|
||||
(instr-1)->instr == I_JUMP // always look inside a jump
|
||||
|| (instr-1)->instr == I_NOP // and skip nops
|
||||
)
|
||||
)
|
||||
) ; --instr) {
|
||||
// skip an instruction
|
||||
switch (instr->instr) {
|
||||
case I_PUSH_CONST:
|
||||
case I_GET_VAR: case I_DUP:
|
||||
to_skip -= 1; break; // nett stack effect +1
|
||||
case I_BINARY:
|
||||
to_skip += 1; break; // nett stack effect 1-2 == -1
|
||||
case I_TERNARY:
|
||||
to_skip += 2; break; // nett stack effect 1-3 == -2
|
||||
case I_QUATERNARY:
|
||||
to_skip += 3; break; // nett stack effect 1-4 == -3
|
||||
case I_CALL: case I_CLOSURE:
|
||||
to_skip += instr->data; // arguments of call
|
||||
break;
|
||||
case I_MAKE_OBJECT:
|
||||
to_skip += 2 * instr->data - 1;
|
||||
break;
|
||||
case I_JUMP: {
|
||||
if (instr->data > initial) {
|
||||
// forward jump, so we were in an else branch all along, ignore this jump
|
||||
return instr + 1;
|
||||
}
|
||||
// there will be a way not to take this jump
|
||||
// the part in between will have no significant stack effect
|
||||
unsigned int after_jump = instr + 1 - &instructions[0];
|
||||
for (--instr ; instr >= &instructions[0] ; --instr) {
|
||||
if (instr->instr == I_LOOP && instr->data == after_jump) {
|
||||
// code looks like
|
||||
// 1 (nettstack+1) iterator
|
||||
// 2 (nettstack+1) accumulator (usually push nil)
|
||||
// loop:
|
||||
// 3 I_LOOP end
|
||||
// 4 (netstack+0)
|
||||
// 5 I_JUMP loop
|
||||
// end:
|
||||
// we have not handled anything for this loop, current position is 2,
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_LOOP_WITH_KEY && instr->data == after_jump) {
|
||||
// same as above,
|
||||
// we need to skip two things (iterator+accumulator) instead of one
|
||||
to_skip += 1;
|
||||
break;
|
||||
} else if (instr->instr == I_JUMP_IF_NOT && instr->data == after_jump) {
|
||||
// code looks like
|
||||
// 1 (nettstack+1)
|
||||
// 2 I_JUMP_IF_NOT else
|
||||
// 3 (nettstack+1)
|
||||
// 4 I_JUMP end
|
||||
// else:
|
||||
// 5 (nettstack+1)
|
||||
// end:
|
||||
// we have already handled 4..5, current position is 2,
|
||||
// we need to skip an additional item for 1
|
||||
to_skip += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
++instr; // compensate for the -- in the outer loop
|
||||
break;
|
||||
}
|
||||
case I_JUMP_IF_NOT: case I_LOOP: case I_LOOP_WITH_KEY:
|
||||
return nullptr; // give up
|
||||
case I_JUMP_SC_AND: case I_JUMP_SC_OR:
|
||||
// assume the fallthrough case, in which case we compared and poped the top of the stack
|
||||
to_skip += 1; break;
|
||||
default:
|
||||
break; // nett stack effect 0
|
||||
}
|
||||
}
|
||||
return instr >= &instructions[0] ? instr : nullptr;
|
||||
}
|
||||
|
||||
String Script::instructionName(const Instruction* instr) const {
|
||||
if (instr < &instructions[0] || instr >= &instructions[0] + instructions.size()) return _("??\?");
|
||||
if (instr->instr == I_GET_VAR) {
|
||||
return variable_to_string((Variable)instr->data);
|
||||
} else if (instr->instr == I_MEMBER_C) {
|
||||
return instructionName(backtraceSkip(instr - 1, 0))
|
||||
+ _(".")
|
||||
+ constants[instr->data]->toString();
|
||||
} else if (instr->instr == I_BINARY && instr->instr2 == I_MEMBER) {
|
||||
return _("??\?[...]");
|
||||
} else if (instr->instr == I_BINARY && instr->instr2 == I_ADD) {
|
||||
return _("??? + ???");
|
||||
} else if (instr->instr == I_NOP) {
|
||||
return _("??\?(...)");
|
||||
} else if (instr->instr == I_CALL) {
|
||||
return instructionName(backtraceSkip(instr - 1, instr->data)) + _("(...)");
|
||||
} else if (instr->instr == I_CLOSURE) {
|
||||
return instructionName(backtraceSkip(instr - 1, instr->data)) + _("@(...)");
|
||||
} else {
|
||||
return _("??\?");
|
||||
}
|
||||
if (instr < &instructions[0] || instr >= &instructions[0] + instructions.size()) return _("??\?");
|
||||
if (instr->instr == I_GET_VAR) {
|
||||
return variable_to_string((Variable)instr->data);
|
||||
} else if (instr->instr == I_MEMBER_C) {
|
||||
return instructionName(backtraceSkip(instr - 1, 0))
|
||||
+ _(".")
|
||||
+ constants[instr->data]->toString();
|
||||
} else if (instr->instr == I_BINARY && instr->instr2 == I_MEMBER) {
|
||||
return _("??\?[...]");
|
||||
} else if (instr->instr == I_BINARY && instr->instr2 == I_ADD) {
|
||||
return _("??? + ???");
|
||||
} else if (instr->instr == I_NOP) {
|
||||
return _("??\?(...)");
|
||||
} else if (instr->instr == I_CALL) {
|
||||
return instructionName(backtraceSkip(instr - 1, instr->data)) + _("(...)");
|
||||
} else if (instr->instr == I_CLOSURE) {
|
||||
return instructionName(backtraceSkip(instr - 1, instr->data)) + _("@(...)");
|
||||
} else {
|
||||
return _("??\?");
|
||||
}
|
||||
}
|
||||
|
||||
+136
-136
@@ -21,79 +21,79 @@ DECLARE_POINTER_TYPE(Script);
|
||||
* Some stupid compilers use signed integers for bitfields, so values should be < 32
|
||||
*/
|
||||
enum InstructionType
|
||||
// Basic
|
||||
{ I_NOP = 0 ///< arg = * : no operation, used as placeholder for extra data values
|
||||
, I_PUSH_CONST = 1 ///< arg = const val : push a constant onto the stack
|
||||
, I_JUMP = 2 ///< arg = address : move the instruction pointer to the given position
|
||||
, I_JUMP_IF_NOT = 3 ///< arg = address : move the instruction pointer if the top of the stack is false
|
||||
, I_JUMP_SC_AND = 19 ///< arg = address : (short-circuiting and) jump and don't pop if the top of the stack is false
|
||||
, I_JUMP_SC_OR = 20 ///< arg = address : (short-circuiting or) jump and don't pop if the top of the stack is true
|
||||
// Variables
|
||||
, I_GET_VAR = 4 ///< arg = var : find a variable, push its value onto the stack, it is an error if the variable is not found
|
||||
, I_SET_VAR = 5 ///< arg = var : assign the top value from the stack to a variable (doesn't pop)
|
||||
// Objects
|
||||
, I_MEMBER_C = 6 ///< arg = const name : finds a member of the top of the stack replaces the top of the stack with the member
|
||||
, I_LOOP = 7 ///< arg = address : loop over the elements of an iterator, which is the *second* element of the stack (this allows for combing the results of multiple iterations)
|
||||
///< at the end performs a jump and pops the iterator. note: The second element of the stack must be an iterator!
|
||||
, I_LOOP_WITH_KEY = 8 ///< arg = address : loop, but also pushing the key
|
||||
, I_MAKE_OBJECT = 9 ///< arg = int : make a list/map with n elements, pops 2n values of the stack, n key/value pairs
|
||||
// Functions
|
||||
, I_CALL = 10 ///< arg = int, n*var : call the top item of the stack, with the given number of arguments (set with SET_VAR, but in the activation record of the call)
|
||||
, I_CLOSURE = 11 ///< arg = int, n*var : construct a call closure object with the given arguments
|
||||
, I_TAILCALL = 12 ///< arg = int, n*var : perform a tail call - like I_CALL, except faster
|
||||
// Simple instructions
|
||||
, I_UNARY = 13 ///< arg = 1ary instr : pop 1 value, apply a function, push the result
|
||||
, I_BINARY = 14 ///< arg = 2ary instr : pop 2 values, apply a function, push the result
|
||||
, I_TERNARY = 15 ///< arg = 3ary instr : pop 3 values, apply a function, push the result
|
||||
, I_QUATERNARY = 16 ///< arg = 4ary instr : pop 4 values, apply a function, push the result
|
||||
, I_DUP = 17 ///< arg = int : duplicate the k-from-top element of the stack
|
||||
, I_POP = 18 ///< arg = * : pop the top value off the stack.
|
||||
// Basic
|
||||
{ I_NOP = 0 ///< arg = * : no operation, used as placeholder for extra data values
|
||||
, I_PUSH_CONST = 1 ///< arg = const val : push a constant onto the stack
|
||||
, I_JUMP = 2 ///< arg = address : move the instruction pointer to the given position
|
||||
, I_JUMP_IF_NOT = 3 ///< arg = address : move the instruction pointer if the top of the stack is false
|
||||
, I_JUMP_SC_AND = 19 ///< arg = address : (short-circuiting and) jump and don't pop if the top of the stack is false
|
||||
, I_JUMP_SC_OR = 20 ///< arg = address : (short-circuiting or) jump and don't pop if the top of the stack is true
|
||||
// Variables
|
||||
, I_GET_VAR = 4 ///< arg = var : find a variable, push its value onto the stack, it is an error if the variable is not found
|
||||
, I_SET_VAR = 5 ///< arg = var : assign the top value from the stack to a variable (doesn't pop)
|
||||
// Objects
|
||||
, I_MEMBER_C = 6 ///< arg = const name : finds a member of the top of the stack replaces the top of the stack with the member
|
||||
, I_LOOP = 7 ///< arg = address : loop over the elements of an iterator, which is the *second* element of the stack (this allows for combing the results of multiple iterations)
|
||||
///< at the end performs a jump and pops the iterator. note: The second element of the stack must be an iterator!
|
||||
, I_LOOP_WITH_KEY = 8 ///< arg = address : loop, but also pushing the key
|
||||
, I_MAKE_OBJECT = 9 ///< arg = int : make a list/map with n elements, pops 2n values of the stack, n key/value pairs
|
||||
// Functions
|
||||
, I_CALL = 10 ///< arg = int, n*var : call the top item of the stack, with the given number of arguments (set with SET_VAR, but in the activation record of the call)
|
||||
, I_CLOSURE = 11 ///< arg = int, n*var : construct a call closure object with the given arguments
|
||||
, I_TAILCALL = 12 ///< arg = int, n*var : perform a tail call - like I_CALL, except faster
|
||||
// Simple instructions
|
||||
, I_UNARY = 13 ///< arg = 1ary instr : pop 1 value, apply a function, push the result
|
||||
, I_BINARY = 14 ///< arg = 2ary instr : pop 2 values, apply a function, push the result
|
||||
, I_TERNARY = 15 ///< arg = 3ary instr : pop 3 values, apply a function, push the result
|
||||
, I_QUATERNARY = 16 ///< arg = 4ary instr : pop 4 values, apply a function, push the result
|
||||
, I_DUP = 17 ///< arg = int : duplicate the k-from-top element of the stack
|
||||
, I_POP = 18 ///< arg = * : pop the top value off the stack.
|
||||
};
|
||||
|
||||
/// Types of unary instructions (taking one argument from the stack)
|
||||
enum UnaryInstructionType
|
||||
{ I_ITERATOR_C ///< Make an iterator for a collection
|
||||
, I_NEGATE
|
||||
, I_NOT
|
||||
{ I_ITERATOR_C ///< Make an iterator for a collection
|
||||
, I_NEGATE
|
||||
, I_NOT
|
||||
};
|
||||
|
||||
/// Types of binary instructions (taking two arguments from the stack)
|
||||
enum BinaryInstructionType
|
||||
{ I_ITERATOR_R ///< Make an iterator for a range (two integers)
|
||||
, I_MEMBER ///< Member of an object
|
||||
{ I_ITERATOR_R ///< Make an iterator for a range (two integers)
|
||||
, I_MEMBER ///< Member of an object
|
||||
// Arithmatic
|
||||
, I_ADD ///< add
|
||||
, I_SUB ///< subtract
|
||||
, I_MUL ///< multiply
|
||||
, I_FDIV ///< floating point division
|
||||
, I_DIV ///< integer division
|
||||
, I_MOD ///< modulus
|
||||
, I_POW ///< power
|
||||
, I_ADD ///< add
|
||||
, I_SUB ///< subtract
|
||||
, I_MUL ///< multiply
|
||||
, I_FDIV ///< floating point division
|
||||
, I_DIV ///< integer division
|
||||
, I_MOD ///< modulus
|
||||
, I_POW ///< power
|
||||
// Logical
|
||||
, I_AND ///< logical and
|
||||
, I_OR ///< logical or
|
||||
, I_XOR ///< logical xor
|
||||
, I_AND ///< logical and
|
||||
, I_OR ///< logical or
|
||||
, I_XOR ///< logical xor
|
||||
// Comparison
|
||||
, I_EQ ///< operator ==
|
||||
, I_NEQ ///< operator !=
|
||||
, I_LT ///< operator <
|
||||
, I_GT ///< operator >
|
||||
, I_LE ///< operator <=
|
||||
, I_GE ///< operator >=
|
||||
, I_MIN ///< minimum
|
||||
, I_MAX ///< maximum
|
||||
, I_EQ ///< operator ==
|
||||
, I_NEQ ///< operator !=
|
||||
, I_LT ///< operator <
|
||||
, I_GT ///< operator >
|
||||
, I_LE ///< operator <=
|
||||
, I_GE ///< operator >=
|
||||
, I_MIN ///< minimum
|
||||
, I_MAX ///< maximum
|
||||
// Error handling
|
||||
, I_OR_ELSE ///< if a != error then a else b
|
||||
, I_OR_ELSE ///< if a != error then a else b
|
||||
};
|
||||
|
||||
/// Types of ternary instructions (taking three arguments from the stack)
|
||||
enum TernaryInstructionType
|
||||
{ I_RGB ///< pop r,g,b, push a color value
|
||||
{ I_RGB ///< pop r,g,b, push a color value
|
||||
};
|
||||
|
||||
/// Types of quaternary instructions (taking four arguments from the stack)
|
||||
enum QuaternaryInstructionType
|
||||
{ I_RGBA ///< pop r,g,b,a, push an acolor value
|
||||
{ I_RGBA ///< pop r,g,b,a, push an acolor value
|
||||
};
|
||||
|
||||
/// An instruction in a script, consists of the opcode and data
|
||||
@@ -101,48 +101,48 @@ enum QuaternaryInstructionType
|
||||
* Then the instr? member gives the actual instruction to perform
|
||||
*/
|
||||
struct Instruction {
|
||||
InstructionType instr : 6;
|
||||
union {
|
||||
unsigned int data : 26;
|
||||
UnaryInstructionType instr1 : 26;
|
||||
BinaryInstructionType instr2 : 26;
|
||||
TernaryInstructionType instr3 : 26;
|
||||
QuaternaryInstructionType instr4 : 26;
|
||||
};
|
||||
InstructionType instr : 6;
|
||||
union {
|
||||
unsigned int data : 26;
|
||||
UnaryInstructionType instr1 : 26;
|
||||
BinaryInstructionType instr2 : 26;
|
||||
TernaryInstructionType instr3 : 26;
|
||||
QuaternaryInstructionType instr4 : 26;
|
||||
};
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Variables
|
||||
|
||||
// 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
|
||||
, 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
|
||||
, SCRIPT_VAR_format
|
||||
, SCRIPT_VAR_tag
|
||||
, SCRIPT_VAR_contents
|
||||
, SCRIPT_VAR_set
|
||||
, SCRIPT_VAR_game
|
||||
, SCRIPT_VAR_stylesheet
|
||||
, SCRIPT_VAR_card_style
|
||||
, SCRIPT_VAR_card
|
||||
, SCRIPT_VAR_styling
|
||||
, SCRIPT_VAR_value
|
||||
, SCRIPT_VAR_condition
|
||||
, SCRIPT_VAR_language
|
||||
, SCRIPT_VAR_CUSTOM_FIRST // other variables start from here
|
||||
, SCRIPT_VAR_CUSTOM_LOTS = 0xFFFFFF // ensure that sizeof(Variable) is large enough
|
||||
{ SCRIPT_VAR_input
|
||||
, SCRIPT_VAR__1
|
||||
, SCRIPT_VAR__2
|
||||
, SCRIPT_VAR_in
|
||||
, SCRIPT_VAR_match
|
||||
, SCRIPT_VAR_replace
|
||||
, 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
|
||||
, SCRIPT_VAR_format
|
||||
, SCRIPT_VAR_tag
|
||||
, SCRIPT_VAR_contents
|
||||
, SCRIPT_VAR_set
|
||||
, SCRIPT_VAR_game
|
||||
, SCRIPT_VAR_stylesheet
|
||||
, SCRIPT_VAR_card_style
|
||||
, SCRIPT_VAR_card
|
||||
, SCRIPT_VAR_styling
|
||||
, SCRIPT_VAR_value
|
||||
, SCRIPT_VAR_condition
|
||||
, SCRIPT_VAR_language
|
||||
, SCRIPT_VAR_CUSTOM_FIRST // other variables start from here
|
||||
, SCRIPT_VAR_CUSTOM_LOTS = 0xFFFFFF // ensure that sizeof(Variable) is large enough
|
||||
};
|
||||
|
||||
/// Return a unique name for a variable to allow for faster loopups
|
||||
@@ -165,57 +165,57 @@ void init_script_variables();
|
||||
*/
|
||||
class Script : public ScriptValue {
|
||||
public:
|
||||
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency&) const;
|
||||
|
||||
/// Add a jump instruction, later comeFrom should be called on the returned value
|
||||
unsigned int addInstruction(InstructionType t);
|
||||
/// Add an instruction with integer data
|
||||
void addInstruction(InstructionType t, unsigned int d);
|
||||
/// Add an instruction with constant data
|
||||
void addInstruction(InstructionType t, const ScriptValueP& c);
|
||||
/// Add an instruction with string data
|
||||
void addInstruction(InstructionType t, const String& s);
|
||||
|
||||
/// Update an instruction to point to the current position
|
||||
/** The instruction at pos must be a jumping instruction, it is changed so the current position
|
||||
* 'comes from' pos (in addition to other control flow).
|
||||
*/
|
||||
void comeFrom(unsigned int pos);
|
||||
/// Get the current instruction position
|
||||
unsigned int getLabel() const;
|
||||
|
||||
/// Get access to the vector of instructions
|
||||
inline vector<Instruction>& getInstructions() { return instructions; }
|
||||
/// Get access to the vector of constants
|
||||
inline vector<ScriptValueP>& getConstants() { return constants; }
|
||||
|
||||
/// Output the instructions in a human readable format
|
||||
String dumpScript() const;
|
||||
/// Output an instruction in a human readable format
|
||||
String dumpInstr(unsigned int pos, Instruction i) const;
|
||||
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency&) const;
|
||||
|
||||
/// Add a jump instruction, later comeFrom should be called on the returned value
|
||||
unsigned int addInstruction(InstructionType t);
|
||||
/// Add an instruction with integer data
|
||||
void addInstruction(InstructionType t, unsigned int d);
|
||||
/// Add an instruction with constant data
|
||||
void addInstruction(InstructionType t, const ScriptValueP& c);
|
||||
/// Add an instruction with string data
|
||||
void addInstruction(InstructionType t, const String& s);
|
||||
|
||||
/// Update an instruction to point to the current position
|
||||
/** The instruction at pos must be a jumping instruction, it is changed so the current position
|
||||
* 'comes from' pos (in addition to other control flow).
|
||||
*/
|
||||
void comeFrom(unsigned int pos);
|
||||
/// Get the current instruction position
|
||||
unsigned int getLabel() const;
|
||||
|
||||
/// Get access to the vector of instructions
|
||||
inline vector<Instruction>& getInstructions() { return instructions; }
|
||||
/// Get access to the vector of constants
|
||||
inline vector<ScriptValueP>& getConstants() { return constants; }
|
||||
|
||||
/// Output the instructions in a human readable format
|
||||
String dumpScript() const;
|
||||
/// Output an instruction in a human readable format
|
||||
String dumpInstr(unsigned int pos, Instruction i) const;
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
|
||||
private:
|
||||
/// Data of the instructions that make up this script
|
||||
vector<Instruction> instructions;
|
||||
/// Constant values that can be referred to from the script
|
||||
vector<ScriptValueP> constants;
|
||||
|
||||
/// Do a backtrace for error messages.
|
||||
/** Starting from instr, move backwards until the nett stack effect
|
||||
* of the skipped instructions is equal to_skip.
|
||||
* If the backtrace fails, returns nullptr
|
||||
*/
|
||||
const Instruction* backtraceSkip(const Instruction* instr, int to_skip) const;
|
||||
/// Find the name of an instruction
|
||||
String instructionName(const Instruction* instr) const;
|
||||
|
||||
friend class Context;
|
||||
/// Data of the instructions that make up this script
|
||||
vector<Instruction> instructions;
|
||||
/// Constant values that can be referred to from the script
|
||||
vector<ScriptValueP> constants;
|
||||
|
||||
/// Do a backtrace for error messages.
|
||||
/** Starting from instr, move backwards until the nett stack effect
|
||||
* of the skipped instructions is equal to_skip.
|
||||
* If the backtrace fails, returns nullptr
|
||||
*/
|
||||
const Instruction* backtraceSkip(const Instruction* instr, int to_skip) const;
|
||||
/// Find the name of an instruction
|
||||
String instructionName(const Instruction* instr) const;
|
||||
|
||||
friend class Context;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+330
-330
@@ -34,392 +34,392 @@ DECLARE_TYPEOF_NO_REV(IndexMap<FieldP COMMA ValueP>);
|
||||
// ----------------------------------------------------------------------------- : SetScriptContext : initialization
|
||||
|
||||
SetScriptContext::SetScriptContext(Set& set)
|
||||
: set(set)
|
||||
: set(set)
|
||||
{}
|
||||
|
||||
SetScriptContext::~SetScriptContext() {
|
||||
// destroy contexts
|
||||
FOR_EACH(sc, contexts) {
|
||||
delete sc.second;
|
||||
}
|
||||
// destroy contexts
|
||||
FOR_EACH(sc, contexts) {
|
||||
delete sc.second;
|
||||
}
|
||||
}
|
||||
|
||||
Context& SetScriptContext::getContext(const StyleSheetP& stylesheet) {
|
||||
Contexts::iterator it = contexts.find(stylesheet.get());
|
||||
if (it != contexts.end()) {
|
||||
return *it->second; // we already have a context
|
||||
} else {
|
||||
// create a new context
|
||||
Context* ctx = new Context();
|
||||
contexts.insert(make_pair(stylesheet.get(), ctx));
|
||||
// variables
|
||||
// NOTE: do not use a smart pointer for the pointer to the set, because the set owns this
|
||||
// which would lead to a reference cycle.
|
||||
init_script_functions(*ctx);
|
||||
ctx->setVariable(SCRIPT_VAR_set, intrusive(new ScriptObject<Set*>(&set)));
|
||||
ctx->setVariable(SCRIPT_VAR_game, to_script(set.game));
|
||||
ctx->setVariable(SCRIPT_VAR_stylesheet, to_script(stylesheet));
|
||||
ctx->setVariable(SCRIPT_VAR_card_style, to_script(&stylesheet->card_style));
|
||||
ctx->setVariable(SCRIPT_VAR_card, set.cards.empty() ? script_nil : to_script(set.cards.front())); // dummy value
|
||||
ctx->setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(*stylesheet)));
|
||||
try {
|
||||
// perform init scripts, don't use a scope, variables stay bound in the context
|
||||
set.game ->init_script.invoke(*ctx, false);
|
||||
stylesheet->init_script.invoke(*ctx, false);
|
||||
} catch (const Error& e) {
|
||||
handle_error(e);
|
||||
}
|
||||
onInit(stylesheet, ctx);
|
||||
return *ctx;
|
||||
}
|
||||
Contexts::iterator it = contexts.find(stylesheet.get());
|
||||
if (it != contexts.end()) {
|
||||
return *it->second; // we already have a context
|
||||
} else {
|
||||
// create a new context
|
||||
Context* ctx = new Context();
|
||||
contexts.insert(make_pair(stylesheet.get(), ctx));
|
||||
// variables
|
||||
// NOTE: do not use a smart pointer for the pointer to the set, because the set owns this
|
||||
// which would lead to a reference cycle.
|
||||
init_script_functions(*ctx);
|
||||
ctx->setVariable(SCRIPT_VAR_set, intrusive(new ScriptObject<Set*>(&set)));
|
||||
ctx->setVariable(SCRIPT_VAR_game, to_script(set.game));
|
||||
ctx->setVariable(SCRIPT_VAR_stylesheet, to_script(stylesheet));
|
||||
ctx->setVariable(SCRIPT_VAR_card_style, to_script(&stylesheet->card_style));
|
||||
ctx->setVariable(SCRIPT_VAR_card, set.cards.empty() ? script_nil : to_script(set.cards.front())); // dummy value
|
||||
ctx->setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(*stylesheet)));
|
||||
try {
|
||||
// perform init scripts, don't use a scope, variables stay bound in the context
|
||||
set.game ->init_script.invoke(*ctx, false);
|
||||
stylesheet->init_script.invoke(*ctx, false);
|
||||
} catch (const Error& e) {
|
||||
handle_error(e);
|
||||
}
|
||||
onInit(stylesheet, ctx);
|
||||
return *ctx;
|
||||
}
|
||||
}
|
||||
Context& SetScriptContext::getContext(const CardP& card) {
|
||||
StyleSheetP stylesheet = set.stylesheetForP(card);
|
||||
Context& ctx = getContext(stylesheet);
|
||||
if (card) {
|
||||
ctx.setVariable(SCRIPT_VAR_card, to_script(card));
|
||||
ctx.setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(card)));
|
||||
} else {
|
||||
ctx.setVariable(SCRIPT_VAR_card, script_nil);
|
||||
ctx.setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(*stylesheet)));
|
||||
}
|
||||
return ctx;
|
||||
StyleSheetP stylesheet = set.stylesheetForP(card);
|
||||
Context& ctx = getContext(stylesheet);
|
||||
if (card) {
|
||||
ctx.setVariable(SCRIPT_VAR_card, to_script(card));
|
||||
ctx.setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(card)));
|
||||
} else {
|
||||
ctx.setVariable(SCRIPT_VAR_card, script_nil);
|
||||
ctx.setVariable(SCRIPT_VAR_styling, to_script(&set.stylingDataFor(*stylesheet)));
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : SetScriptManager : initialization
|
||||
|
||||
SetScriptManager::SetScriptManager(Set& set)
|
||||
: SetScriptContext(set)
|
||||
, delay(0)
|
||||
: SetScriptContext(set)
|
||||
, delay(0)
|
||||
{
|
||||
// add as an action listener for the set, so we receive actions
|
||||
set.actions.addListener(this);
|
||||
// add as an action listener for the set, so we receive actions
|
||||
set.actions.addListener(this);
|
||||
}
|
||||
|
||||
SetScriptManager::~SetScriptManager() {
|
||||
set.actions.removeListener(this);
|
||||
set.actions.removeListener(this);
|
||||
}
|
||||
|
||||
void SetScriptManager::onInit(const StyleSheetP& stylesheet, Context* ctx) {
|
||||
assert(wxThread::IsMain());
|
||||
// initialize dependencies
|
||||
try {
|
||||
// find script dependencies
|
||||
initDependencies(*ctx, *set.game);
|
||||
initDependencies(*ctx, *stylesheet);
|
||||
} catch (const Error& e) {
|
||||
handle_error(e);
|
||||
}
|
||||
assert(wxThread::IsMain());
|
||||
// initialize dependencies
|
||||
try {
|
||||
// find script dependencies
|
||||
initDependencies(*ctx, *set.game);
|
||||
initDependencies(*ctx, *stylesheet);
|
||||
} catch (const Error& e) {
|
||||
handle_error(e);
|
||||
}
|
||||
}
|
||||
|
||||
void SetScriptManager::initDependencies(Context& ctx, Game& game) {
|
||||
if (game.dependencies_initialized) return;
|
||||
game.dependencies_initialized = true;
|
||||
// find dependencies of card fields
|
||||
FOR_EACH(f, game.card_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_CARD_FIELD, f->index));
|
||||
}
|
||||
// find dependencies of set fields
|
||||
FOR_EACH(f, game.set_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_SET_FIELD, f->index));
|
||||
}
|
||||
if (game.dependencies_initialized) return;
|
||||
game.dependencies_initialized = true;
|
||||
// find dependencies of card fields
|
||||
FOR_EACH(f, game.card_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_CARD_FIELD, f->index));
|
||||
}
|
||||
// find dependencies of set fields
|
||||
FOR_EACH(f, game.set_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_SET_FIELD, f->index));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SetScriptManager::initDependencies(Context& ctx, StyleSheet& stylesheet) {
|
||||
if (stylesheet.dependencies_initialized) return;
|
||||
stylesheet.dependencies_initialized = true;
|
||||
// find dependencies of extra card fields
|
||||
FOR_EACH(f, stylesheet.extra_card_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_EXTRA_CARD_FIELD, f->index, &stylesheet));
|
||||
}
|
||||
// find dependencies of choice images and other style stuff
|
||||
FOR_EACH(s, stylesheet.card_style) {
|
||||
s->initDependencies(ctx, Dependency(DEP_CARD_STYLE, s->fieldP->index, &stylesheet));
|
||||
// are there dependencies of this style on other style properties?
|
||||
Dependency test(DEP_DUMMY, false);
|
||||
s->checkContentDependencies(ctx, test);
|
||||
if (test.index) s->content_dependent = true;
|
||||
}
|
||||
FOR_EACH(s, stylesheet.extra_card_style) {
|
||||
// are there dependencies of this style on other style properties?
|
||||
Dependency test(DEP_DUMMY, false);
|
||||
s->checkContentDependencies(ctx, test);
|
||||
if (test.index) s->content_dependent = true;
|
||||
}
|
||||
if (stylesheet.dependencies_initialized) return;
|
||||
stylesheet.dependencies_initialized = true;
|
||||
// find dependencies of extra card fields
|
||||
FOR_EACH(f, stylesheet.extra_card_fields) {
|
||||
f->initDependencies(ctx, Dependency(DEP_EXTRA_CARD_FIELD, f->index, &stylesheet));
|
||||
}
|
||||
// find dependencies of choice images and other style stuff
|
||||
FOR_EACH(s, stylesheet.card_style) {
|
||||
s->initDependencies(ctx, Dependency(DEP_CARD_STYLE, s->fieldP->index, &stylesheet));
|
||||
// are there dependencies of this style on other style properties?
|
||||
Dependency test(DEP_DUMMY, false);
|
||||
s->checkContentDependencies(ctx, test);
|
||||
if (test.index) s->content_dependent = true;
|
||||
}
|
||||
FOR_EACH(s, stylesheet.extra_card_style) {
|
||||
// are there dependencies of this style on other style properties?
|
||||
Dependency test(DEP_DUMMY, false);
|
||||
s->checkContentDependencies(ctx, test);
|
||||
if (test.index) s->content_dependent = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : ScriptManager : updating
|
||||
|
||||
void SetScriptManager::onAction(const Action& action, bool undone) {
|
||||
TYPE_CASE(action, ValueAction) {
|
||||
if (action.card) {
|
||||
#ifdef USE_INTRUSIVE_PTR
|
||||
// we can just turn the Card* into a CardP
|
||||
updateValue(*action.valueP, CardP(const_cast<Card*>(action.card)));
|
||||
return;
|
||||
#else
|
||||
// find the affected card
|
||||
FOR_EACH(card, set.cards) {
|
||||
if (card->data.contains(action.valueP)) {
|
||||
updateValue(*action.valueP, card);
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
#endif
|
||||
} else {
|
||||
// is it a keyword's fake value?
|
||||
KeywordTextValue* value = dynamic_cast<KeywordTextValue*>(action.valueP.get());
|
||||
if (value) {
|
||||
if (value->underlying == &value->keyword.match) {
|
||||
// script
|
||||
Context& ctx = getContext(set.stylesheet);
|
||||
value->update(ctx);
|
||||
// changed the 'match' string of a keyword, rebuild database and regex so matching is correct
|
||||
value->keyword.prepare(set.game->keyword_parameter_types, true);
|
||||
set.keyword_db.clear();
|
||||
}
|
||||
delay |= DELAY_KEYWORDS;
|
||||
return;
|
||||
}
|
||||
// a set or styling value
|
||||
updateValue(*action.valueP, CardP());
|
||||
}
|
||||
}
|
||||
TYPE_CASE_(action, ScriptValueEvent) {
|
||||
return; // Don't go into an infinite loop because of our own events
|
||||
}
|
||||
TYPE_CASE(action, AddCardAction) {
|
||||
if (action.action.adding != undone) {
|
||||
// update the added cards specificly
|
||||
FOR_EACH_CONST(step, action.action.steps) {
|
||||
const CardP& card = step.item;
|
||||
Context& ctx = getContext(card);
|
||||
FOR_EACH(v, card->data) {
|
||||
v->update(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// note: fallthrough
|
||||
}
|
||||
TYPE_CASE_(action, CardListAction) {
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Card dependencies"));
|
||||
#endif
|
||||
updateAllDependend(set.game->dependent_scripts_cards);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
}
|
||||
TYPE_CASE_(action, KeywordListAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
return;
|
||||
}
|
||||
TYPE_CASE_(action, ChangeKeywordModeAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
return;
|
||||
}
|
||||
TYPE_CASE(action, ChangeCardStyleAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_stylesheet, action.card);
|
||||
}
|
||||
TYPE_CASE_(action, ChangeSetStyleAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_stylesheet);
|
||||
return;
|
||||
}
|
||||
TYPE_CASE(action, ValueAction) {
|
||||
if (action.card) {
|
||||
#ifdef USE_INTRUSIVE_PTR
|
||||
// we can just turn the Card* into a CardP
|
||||
updateValue(*action.valueP, CardP(const_cast<Card*>(action.card)));
|
||||
return;
|
||||
#else
|
||||
// find the affected card
|
||||
FOR_EACH(card, set.cards) {
|
||||
if (card->data.contains(action.valueP)) {
|
||||
updateValue(*action.valueP, card);
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
#endif
|
||||
} else {
|
||||
// is it a keyword's fake value?
|
||||
KeywordTextValue* value = dynamic_cast<KeywordTextValue*>(action.valueP.get());
|
||||
if (value) {
|
||||
if (value->underlying == &value->keyword.match) {
|
||||
// script
|
||||
Context& ctx = getContext(set.stylesheet);
|
||||
value->update(ctx);
|
||||
// changed the 'match' string of a keyword, rebuild database and regex so matching is correct
|
||||
value->keyword.prepare(set.game->keyword_parameter_types, true);
|
||||
set.keyword_db.clear();
|
||||
}
|
||||
delay |= DELAY_KEYWORDS;
|
||||
return;
|
||||
}
|
||||
// a set or styling value
|
||||
updateValue(*action.valueP, CardP());
|
||||
}
|
||||
}
|
||||
TYPE_CASE_(action, ScriptValueEvent) {
|
||||
return; // Don't go into an infinite loop because of our own events
|
||||
}
|
||||
TYPE_CASE(action, AddCardAction) {
|
||||
if (action.action.adding != undone) {
|
||||
// update the added cards specificly
|
||||
FOR_EACH_CONST(step, action.action.steps) {
|
||||
const CardP& card = step.item;
|
||||
Context& ctx = getContext(card);
|
||||
FOR_EACH(v, card->data) {
|
||||
v->update(ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
// note: fallthrough
|
||||
}
|
||||
TYPE_CASE_(action, CardListAction) {
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Card dependencies"));
|
||||
#endif
|
||||
updateAllDependend(set.game->dependent_scripts_cards);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
}
|
||||
TYPE_CASE_(action, KeywordListAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
return;
|
||||
}
|
||||
TYPE_CASE_(action, ChangeKeywordModeAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
return;
|
||||
}
|
||||
TYPE_CASE(action, ChangeCardStyleAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_stylesheet, action.card);
|
||||
}
|
||||
TYPE_CASE_(action, ChangeSetStyleAction) {
|
||||
updateAllDependend(set.game->dependent_scripts_stylesheet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void SetScriptManager::updateStyles(const CardP& card, bool only_content_dependent) {
|
||||
assert(card);
|
||||
const StyleSheet& stylesheet = set.stylesheetFor(card);
|
||||
Context& ctx = getContext(card);
|
||||
if (!only_content_dependent) {
|
||||
// update extra card fields
|
||||
IndexMap<FieldP,ValueP>& extra_data = card->extraDataFor(stylesheet);
|
||||
FOR_EACH(v, extra_data) {
|
||||
if (v->update(ctx)) {
|
||||
// changed, send event
|
||||
ScriptValueEvent change(card.get(), v.get());
|
||||
set.actions.tellListeners(change, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// update all styles
|
||||
updateStyles(ctx, stylesheet.card_style, only_content_dependent);
|
||||
updateStyles(ctx, stylesheet.extra_card_style, only_content_dependent);
|
||||
assert(card);
|
||||
const StyleSheet& stylesheet = set.stylesheetFor(card);
|
||||
Context& ctx = getContext(card);
|
||||
if (!only_content_dependent) {
|
||||
// update extra card fields
|
||||
IndexMap<FieldP,ValueP>& extra_data = card->extraDataFor(stylesheet);
|
||||
FOR_EACH(v, extra_data) {
|
||||
if (v->update(ctx)) {
|
||||
// changed, send event
|
||||
ScriptValueEvent change(card.get(), v.get());
|
||||
set.actions.tellListeners(change, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// update all styles
|
||||
updateStyles(ctx, stylesheet.card_style, only_content_dependent);
|
||||
updateStyles(ctx, stylesheet.extra_card_style, only_content_dependent);
|
||||
}
|
||||
void SetScriptManager::updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>& styles, bool only_content_dependent) {
|
||||
FOR_EACH_CONST(s, styles) {
|
||||
if (only_content_dependent && !s->content_dependent) continue;
|
||||
try {
|
||||
if (int change = s->update(ctx)) {
|
||||
// style has changed, tell listeners
|
||||
s->tellListeners(change | (only_content_dependent ? CHANGE_ALREADY_PREPARED : 0) );
|
||||
}
|
||||
} catch (const ScriptError& e) {
|
||||
// NOTE: don't handle errors now, we are likely in an onPaint handler
|
||||
handle_error(ScriptError(e.what() + _("\n while updating styles for '") + s->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
FOR_EACH_CONST(s, styles) {
|
||||
if (only_content_dependent && !s->content_dependent) continue;
|
||||
try {
|
||||
if (int change = s->update(ctx)) {
|
||||
// style has changed, tell listeners
|
||||
s->tellListeners(change | (only_content_dependent ? CHANGE_ALREADY_PREPARED : 0) );
|
||||
}
|
||||
} catch (const ScriptError& e) {
|
||||
// NOTE: don't handle errors now, we are likely in an onPaint handler
|
||||
handle_error(ScriptError(e.what() + _("\n while updating styles for '") + s->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetScriptManager::updateDelayed() {
|
||||
if (delay & DELAY_KEYWORDS) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
}
|
||||
delay = 0;
|
||||
if (delay & DELAY_KEYWORDS) {
|
||||
updateAllDependend(set.game->dependent_scripts_keywords);
|
||||
}
|
||||
delay = 0;
|
||||
}
|
||||
|
||||
void SetScriptManager::updateValue(Value& value, const CardP& card) {
|
||||
Age starting_age; // the start of the update process
|
||||
deque<ToUpdate> to_update;
|
||||
// execute script for initial changed value
|
||||
value.update(getContext(card));
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Start: %s"), value.fieldP->name);
|
||||
#endif
|
||||
// update dependent scripts
|
||||
alsoUpdate(to_update, value.fieldP->dependent_scripts, card);
|
||||
updateRecursive(to_update, starting_age);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
Age starting_age; // the start of the update process
|
||||
deque<ToUpdate> to_update;
|
||||
// execute script for initial changed value
|
||||
value.update(getContext(card));
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Start: %s"), value.fieldP->name);
|
||||
#endif
|
||||
// update dependent scripts
|
||||
alsoUpdate(to_update, value.fieldP->dependent_scripts, card);
|
||||
updateRecursive(to_update, starting_age);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetScriptManager::updateAll() {
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Update all"));
|
||||
#endif
|
||||
wxBusyCursor busy;
|
||||
// update set data
|
||||
Context& ctx = getContext(set.stylesheet);
|
||||
FOR_EACH(v, set.data) {
|
||||
try {
|
||||
PROFILER2( v->fieldP.get(), _("update set.") + v->fieldP->name );
|
||||
v->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating set value '") + v->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
// update card data of all cards
|
||||
FOR_EACH(card, set.cards) {
|
||||
Context& ctx = getContext(card);
|
||||
FOR_EACH(v, card->data) {
|
||||
try {
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, v->fieldP.get(), _("update card.") + v->fieldP->name);
|
||||
#endif
|
||||
v->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating card value '") + v->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
}
|
||||
// update things that depend on the card list
|
||||
updateAllDependend(set.game->dependent_scripts_cards);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Update all"));
|
||||
#endif
|
||||
wxBusyCursor busy;
|
||||
// update set data
|
||||
Context& ctx = getContext(set.stylesheet);
|
||||
FOR_EACH(v, set.data) {
|
||||
try {
|
||||
PROFILER2( v->fieldP.get(), _("update set.") + v->fieldP->name );
|
||||
v->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating set value '") + v->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
// update card data of all cards
|
||||
FOR_EACH(card, set.cards) {
|
||||
Context& ctx = getContext(card);
|
||||
FOR_EACH(v, card->data) {
|
||||
try {
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, v->fieldP.get(), _("update card.") + v->fieldP->name);
|
||||
#endif
|
||||
v->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating card value '") + v->fieldP->name + _("'")));
|
||||
}
|
||||
}
|
||||
}
|
||||
// update things that depend on the card list
|
||||
updateAllDependend(set.game->dependent_scripts_cards);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("-------------------------------\n"));
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetScriptManager::updateAllDependend(const vector<Dependency>& dependent_scripts, const CardP& card) {
|
||||
deque<ToUpdate> to_update;
|
||||
Age starting_age;
|
||||
alsoUpdate(to_update, dependent_scripts, card);
|
||||
updateRecursive(to_update, starting_age);
|
||||
deque<ToUpdate> to_update;
|
||||
Age starting_age;
|
||||
alsoUpdate(to_update, dependent_scripts, card);
|
||||
updateRecursive(to_update, starting_age);
|
||||
}
|
||||
|
||||
void SetScriptManager::updateRecursive(deque<ToUpdate>& to_update, Age starting_age) {
|
||||
if (to_update.empty()) return;
|
||||
set.clearOrderCache(); // clear caches before evaluating a round of scripts
|
||||
while (!to_update.empty()) {
|
||||
updateToUpdate(to_update.front(), to_update, starting_age);
|
||||
to_update.pop_front();
|
||||
}
|
||||
if (to_update.empty()) return;
|
||||
set.clearOrderCache(); // clear caches before evaluating a round of scripts
|
||||
while (!to_update.empty()) {
|
||||
updateToUpdate(to_update.front(), to_update, starting_age);
|
||||
to_update.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void SetScriptManager::updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update, Age starting_age) {
|
||||
Age age = u.value->last_script_update;
|
||||
if (starting_age < age) return; // this value was already updated
|
||||
Context& ctx = getContext(u.card);
|
||||
bool changes = false;
|
||||
try {
|
||||
changes = u.value->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating value '") + u.value->fieldP->name + _("'")));
|
||||
}
|
||||
if (changes) {
|
||||
// changed, send event
|
||||
ScriptValueEvent change(u.card.get(), u.value);
|
||||
set.actions.tellListeners(change, false);
|
||||
// u.value has changed, also update values with a dependency on u.value
|
||||
alsoUpdate(to_update, u.value->fieldP->dependent_scripts, u.card);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Changed: %s"), u.value->fieldP->name);
|
||||
#endif
|
||||
}
|
||||
#ifdef LOG_UPDATES
|
||||
else
|
||||
wxLogDebug(_("Same: %s"), u.value->fieldP->name);
|
||||
#endif
|
||||
Age age = u.value->last_script_update;
|
||||
if (starting_age < age) return; // this value was already updated
|
||||
Context& ctx = getContext(u.card);
|
||||
bool changes = false;
|
||||
try {
|
||||
changes = u.value->update(ctx);
|
||||
} catch (const ScriptError& e) {
|
||||
handle_error(ScriptError(e.what() + _("\n while updating value '") + u.value->fieldP->name + _("'")));
|
||||
}
|
||||
if (changes) {
|
||||
// changed, send event
|
||||
ScriptValueEvent change(u.card.get(), u.value);
|
||||
set.actions.tellListeners(change, false);
|
||||
// u.value has changed, also update values with a dependency on u.value
|
||||
alsoUpdate(to_update, u.value->fieldP->dependent_scripts, u.card);
|
||||
#ifdef LOG_UPDATES
|
||||
wxLogDebug(_("Changed: %s"), u.value->fieldP->name);
|
||||
#endif
|
||||
}
|
||||
#ifdef LOG_UPDATES
|
||||
else
|
||||
wxLogDebug(_("Same: %s"), u.value->fieldP->name);
|
||||
#endif
|
||||
}
|
||||
|
||||
void SetScriptManager::alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependency>& deps, const CardP& card) {
|
||||
FOR_EACH_CONST(d, deps) {
|
||||
switch (d.type) {
|
||||
case DEP_SET_FIELD: {
|
||||
ValueP value = set.data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), CardP()));
|
||||
break;
|
||||
} case DEP_CARD_FIELD: {
|
||||
if (card) {
|
||||
ValueP value = card->data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
break;
|
||||
} else {
|
||||
// There is no card, so the update should affect all cards (fall through).
|
||||
}
|
||||
} case DEP_CARDS_FIELD: {
|
||||
// something invalidates a card value for all cards, so all cards need updating
|
||||
FOR_EACH(card, set.cards) {
|
||||
ValueP value = card->data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
}
|
||||
break;
|
||||
} case DEP_CARD_STYLE: {
|
||||
// a generated image has become invalid, there is not much we can do
|
||||
// because the index is not exact enough, it only gives the field
|
||||
StyleSheet* stylesheet = reinterpret_cast<StyleSheet*>(d.data);
|
||||
StyleP style = stylesheet->card_style.at(d.index);
|
||||
style->invalidate();
|
||||
// something changed, send event
|
||||
ScriptStyleEvent change(stylesheet, style.get());
|
||||
set.actions.tellListeners(change, false);
|
||||
break;
|
||||
} case DEP_EXTRA_CARD_FIELD: {
|
||||
/* // Not needed, extra card fields are handled in updateStyles()
|
||||
if (card) {
|
||||
StyleSheet* stylesheet = reinterpret_cast<StyleSheet*>(d.data);
|
||||
StyleSheet* stylesheet_card = &set.stylesheetFor(card);
|
||||
if (stylesheet == stylesheet_card) {
|
||||
ValueP value = card->extra_data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
} case DEP_CARD_COPY_DEP: {
|
||||
// propagate dependencies from another field
|
||||
FieldP f = set.game->card_fields[d.index];
|
||||
alsoUpdate(to_update, f->dependent_scripts, card);
|
||||
break;
|
||||
} case DEP_SET_COPY_DEP: {
|
||||
// propagate dependencies from another field
|
||||
FieldP f = set.game->set_fields[d.index];
|
||||
alsoUpdate(to_update, f->dependent_scripts, card);
|
||||
break;
|
||||
} default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
FOR_EACH_CONST(d, deps) {
|
||||
switch (d.type) {
|
||||
case DEP_SET_FIELD: {
|
||||
ValueP value = set.data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), CardP()));
|
||||
break;
|
||||
} case DEP_CARD_FIELD: {
|
||||
if (card) {
|
||||
ValueP value = card->data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
break;
|
||||
} else {
|
||||
// There is no card, so the update should affect all cards (fall through).
|
||||
}
|
||||
} case DEP_CARDS_FIELD: {
|
||||
// something invalidates a card value for all cards, so all cards need updating
|
||||
FOR_EACH(card, set.cards) {
|
||||
ValueP value = card->data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
}
|
||||
break;
|
||||
} case DEP_CARD_STYLE: {
|
||||
// a generated image has become invalid, there is not much we can do
|
||||
// because the index is not exact enough, it only gives the field
|
||||
StyleSheet* stylesheet = reinterpret_cast<StyleSheet*>(d.data);
|
||||
StyleP style = stylesheet->card_style.at(d.index);
|
||||
style->invalidate();
|
||||
// something changed, send event
|
||||
ScriptStyleEvent change(stylesheet, style.get());
|
||||
set.actions.tellListeners(change, false);
|
||||
break;
|
||||
} case DEP_EXTRA_CARD_FIELD: {
|
||||
/* // Not needed, extra card fields are handled in updateStyles()
|
||||
if (card) {
|
||||
StyleSheet* stylesheet = reinterpret_cast<StyleSheet*>(d.data);
|
||||
StyleSheet* stylesheet_card = &set.stylesheetFor(card);
|
||||
if (stylesheet == stylesheet_card) {
|
||||
ValueP value = card->extra_data.at(d.index);
|
||||
to_update.push_back(ToUpdate(value.get(), card));
|
||||
}
|
||||
}*/
|
||||
break;
|
||||
} case DEP_CARD_COPY_DEP: {
|
||||
// propagate dependencies from another field
|
||||
FieldP f = set.game->card_fields[d.index];
|
||||
alsoUpdate(to_update, f->dependent_scripts, card);
|
||||
break;
|
||||
} case DEP_SET_COPY_DEP: {
|
||||
// propagate dependencies from another field
|
||||
FieldP f = set.game->set_fields[d.index];
|
||||
alsoUpdate(to_update, f->dependent_scripts, card);
|
||||
break;
|
||||
} default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,20 +29,20 @@ DECLARE_POINTER_TYPE(Style);
|
||||
/// Manager of the script context for a set
|
||||
class SetScriptContext {
|
||||
public:
|
||||
SetScriptContext(Set& set);
|
||||
virtual ~SetScriptContext();
|
||||
|
||||
/// Get a context to use for the set, for a given stylesheet
|
||||
Context& getContext(const StyleSheetP&);
|
||||
/// Get a context to use for the set, for a given card
|
||||
Context& getContext(const CardP&);
|
||||
|
||||
SetScriptContext(Set& set);
|
||||
virtual ~SetScriptContext();
|
||||
|
||||
/// Get a context to use for the set, for a given stylesheet
|
||||
Context& getContext(const StyleSheetP&);
|
||||
/// Get a context to use for the set, for a given card
|
||||
Context& getContext(const CardP&);
|
||||
|
||||
protected:
|
||||
Set& set; ///< Set for which we are managing scripts
|
||||
map<const StyleSheet*,Context*> contexts; ///< Context for evaluating scripts that use a given stylesheet
|
||||
|
||||
/// Called when a new context for a stylesheet is initialized
|
||||
virtual void onInit(const StyleSheetP& stylesheet, Context* ctx) {}
|
||||
Set& set; ///< Set for which we are managing scripts
|
||||
map<const StyleSheet*,Context*> contexts; ///< Context for evaluating scripts that use a given stylesheet
|
||||
|
||||
/// Called when a new context for a stylesheet is initialized
|
||||
virtual void onInit(const StyleSheetP& stylesheet, Context* ctx) {}
|
||||
};
|
||||
|
||||
|
||||
@@ -57,59 +57,59 @@ class SetScriptContext {
|
||||
*/
|
||||
class SetScriptManager : public SetScriptContext, public ActionListener {
|
||||
public:
|
||||
SetScriptManager(Set& set);
|
||||
~SetScriptManager();
|
||||
|
||||
/// Update all styles for a particular card
|
||||
void updateStyles(const CardP& card, bool only_content_dependent);
|
||||
|
||||
/// Update expensive things that were previously delayed
|
||||
void updateDelayed();
|
||||
|
||||
/// Update all fields of all cards
|
||||
/** Update all set info fields
|
||||
* Doesn't update styles
|
||||
*/
|
||||
void updateAll();
|
||||
|
||||
SetScriptManager(Set& set);
|
||||
~SetScriptManager();
|
||||
|
||||
/// Update all styles for a particular card
|
||||
void updateStyles(const CardP& card, bool only_content_dependent);
|
||||
|
||||
/// Update expensive things that were previously delayed
|
||||
void updateDelayed();
|
||||
|
||||
/// Update all fields of all cards
|
||||
/** Update all set info fields
|
||||
* Doesn't update styles
|
||||
*/
|
||||
void updateAll();
|
||||
|
||||
private:
|
||||
virtual void onInit(const StyleSheetP& stylesheet, Context* ctx);
|
||||
|
||||
void initDependencies(Context&, Game&);
|
||||
void initDependencies(Context&, StyleSheet&);
|
||||
|
||||
/// Update a map of styles
|
||||
void updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>& styles, bool only_content_dependent);
|
||||
/// Updates scripts, starting at some value
|
||||
/** if the value changes any dependend values are updated as well */
|
||||
void updateValue(Value& value, const CardP& card);
|
||||
// Update all values with a specific dependency
|
||||
void updateAllDependend(const vector<Dependency>& dependent_scripts, const CardP& card = CardP());
|
||||
|
||||
// Something that needs to be updated
|
||||
struct ToUpdate {
|
||||
ToUpdate(Value* value, CardP card) : value(value), card(card) {}
|
||||
Value* value; ///< value to update
|
||||
CardP card; ///< card the value is in, or CadP() if it is not a card field
|
||||
};
|
||||
/// Update all things in to_update, and things that depent on them, etc.
|
||||
/** Only update things that are older than starting_age. */
|
||||
void updateRecursive(deque<ToUpdate>& to_update, Age starting_age);
|
||||
/// Update a value given by a ToUpdate object, and add things depending on it to to_update
|
||||
void updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update, Age starting_age);
|
||||
/// Schedule all things in deps to be updated by adding them to to_update
|
||||
void alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependency>& deps, const CardP& card);
|
||||
|
||||
/// Delayed update for (bitmask)...
|
||||
enum Delay
|
||||
{ DELAY_KEYWORDS = 0x01
|
||||
, DELAY_CARDS = 0x02
|
||||
};
|
||||
int delay;
|
||||
|
||||
virtual void onInit(const StyleSheetP& stylesheet, Context* ctx);
|
||||
|
||||
void initDependencies(Context&, Game&);
|
||||
void initDependencies(Context&, StyleSheet&);
|
||||
|
||||
/// Update a map of styles
|
||||
void updateStyles(Context& ctx, const IndexMap<FieldP,StyleP>& styles, bool only_content_dependent);
|
||||
/// Updates scripts, starting at some value
|
||||
/** if the value changes any dependend values are updated as well */
|
||||
void updateValue(Value& value, const CardP& card);
|
||||
// Update all values with a specific dependency
|
||||
void updateAllDependend(const vector<Dependency>& dependent_scripts, const CardP& card = CardP());
|
||||
|
||||
// Something that needs to be updated
|
||||
struct ToUpdate {
|
||||
ToUpdate(Value* value, CardP card) : value(value), card(card) {}
|
||||
Value* value; ///< value to update
|
||||
CardP card; ///< card the value is in, or CadP() if it is not a card field
|
||||
};
|
||||
/// Update all things in to_update, and things that depent on them, etc.
|
||||
/** Only update things that are older than starting_age. */
|
||||
void updateRecursive(deque<ToUpdate>& to_update, Age starting_age);
|
||||
/// Update a value given by a ToUpdate object, and add things depending on it to to_update
|
||||
void updateToUpdate(const ToUpdate& u, deque<ToUpdate>& to_update, Age starting_age);
|
||||
/// Schedule all things in deps to be updated by adding them to to_update
|
||||
void alsoUpdate(deque<ToUpdate>& to_update, const vector<Dependency>& deps, const CardP& card);
|
||||
|
||||
/// Delayed update for (bitmask)...
|
||||
enum Delay
|
||||
{ DELAY_KEYWORDS = 0x01
|
||||
, DELAY_CARDS = 0x02
|
||||
};
|
||||
int delay;
|
||||
|
||||
protected:
|
||||
/// Respond to actions by updating scripts
|
||||
void onAction(const Action&, bool undone);
|
||||
/// Respond to actions by updating scripts
|
||||
void onAction(const Action&, bool undone);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+53
-53
@@ -36,101 +36,101 @@ void store(const ScriptValueP& val, Direction& var) { parse_enum(val->
|
||||
// ----------------------------------------------------------------------------- : OptionalScript
|
||||
|
||||
OptionalScript::OptionalScript(const String& script_)
|
||||
: script(::parse(script_))
|
||||
, unparsed(script_)
|
||||
: script(::parse(script_))
|
||||
, unparsed(script_)
|
||||
{}
|
||||
|
||||
OptionalScript::~OptionalScript() {}
|
||||
|
||||
ScriptValueP OptionalScript::invoke(Context& ctx, bool open_scope) const {
|
||||
if (script) {
|
||||
return ctx.eval(*script, open_scope);
|
||||
} else {
|
||||
return script_nil;
|
||||
}
|
||||
if (script) {
|
||||
return ctx.eval(*script, open_scope);
|
||||
} else {
|
||||
return script_nil;
|
||||
}
|
||||
}
|
||||
|
||||
void OptionalScript::parse(Reader& reader, bool string_mode) {
|
||||
vector<ScriptParseError> errors;
|
||||
script = ::parse(unparsed, reader.getPackage(), string_mode, errors);
|
||||
// show parse errors as warnings
|
||||
String include_warnings;
|
||||
for (size_t i = 0 ; i < errors.size() ; ++i) {
|
||||
const ScriptParseError& e = errors[i];
|
||||
if (!e.filename.empty()) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
vector<ScriptParseError> errors;
|
||||
script = ::parse(unparsed, reader.getPackage(), string_mode, errors);
|
||||
// show parse errors as warnings
|
||||
String include_warnings;
|
||||
for (size_t i = 0 ; i < errors.size() ; ++i) {
|
||||
const ScriptParseError& e = errors[i];
|
||||
if (!e.filename.empty()) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OptionalScript::initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
if (script) {
|
||||
ctx.dependencies(dep, *script);
|
||||
}
|
||||
if (script) {
|
||||
ctx.dependencies(dep, *script);
|
||||
}
|
||||
}
|
||||
|
||||
Script& OptionalScript::getMutableScript() {
|
||||
if (!script) script = intrusive(new Script());
|
||||
return *script;
|
||||
if (!script) script = intrusive(new Script());
|
||||
return *script;
|
||||
}
|
||||
|
||||
// custom reflection, different for each type
|
||||
|
||||
template <> void Reader::handle(OptionalScript& os) {
|
||||
handle(os.unparsed);
|
||||
os.parse(*this);
|
||||
handle(os.unparsed);
|
||||
os.parse(*this);
|
||||
}
|
||||
|
||||
template <> void Writer::handle(const OptionalScript& os) {
|
||||
if (os.script) {
|
||||
handle(os.unparsed);
|
||||
}
|
||||
if (os.script) {
|
||||
handle(os.unparsed);
|
||||
}
|
||||
}
|
||||
|
||||
template <> void GetDefaultMember::handle(const OptionalScript& os) {
|
||||
// reflect as the script itself
|
||||
if (os.script) {
|
||||
handle(os.script);
|
||||
} else {
|
||||
handle(script_nil);
|
||||
}
|
||||
// reflect as the script itself
|
||||
if (os.script) {
|
||||
handle(os.script);
|
||||
} else {
|
||||
handle(script_nil);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : StringScript
|
||||
|
||||
const String& StringScript::get() const {
|
||||
return unparsed;
|
||||
return unparsed;
|
||||
}
|
||||
|
||||
void StringScript::set(const String& s) {
|
||||
unparsed = s;
|
||||
script = ::parse(unparsed, nullptr, true);
|
||||
unparsed = s;
|
||||
script = ::parse(unparsed, nullptr, true);
|
||||
}
|
||||
|
||||
template <> void Reader::handle(StringScript& os) {
|
||||
handle(os.unparsed);
|
||||
os.parse(*this, true);
|
||||
handle(os.unparsed);
|
||||
os.parse(*this, true);
|
||||
}
|
||||
|
||||
// same as OptionalScript
|
||||
|
||||
template <> void Writer::handle(const StringScript& os) {
|
||||
handle(os.unparsed);
|
||||
handle(os.unparsed);
|
||||
}
|
||||
|
||||
template <> void GetDefaultMember::handle(const StringScript& os) {
|
||||
// reflect as the script itself
|
||||
if (os.script) {
|
||||
handle(os.script);
|
||||
} else {
|
||||
handle(script_nil);
|
||||
}
|
||||
// reflect as the script itself
|
||||
if (os.script) {
|
||||
handle(os.script);
|
||||
} else {
|
||||
handle(script_nil);
|
||||
}
|
||||
}
|
||||
|
||||
+101
-101
@@ -45,61 +45,61 @@ inline void change(Defaultable<T>& v, const Defaultable<T>& new_v) { v.assignDon
|
||||
/// An optional script
|
||||
class OptionalScript {
|
||||
public:
|
||||
inline OptionalScript() {}
|
||||
OptionalScript(const String& script_);
|
||||
~OptionalScript();
|
||||
/// Is the script set?
|
||||
inline operator bool() const { return !!script; }
|
||||
|
||||
/// Invoke the script, return the result, or script_nil if there is no script
|
||||
ScriptValueP invoke(Context& ctx, bool open_scope = true) const;
|
||||
|
||||
/// Invoke the script on a value
|
||||
/** Assigns the result to value if it has changed.
|
||||
* Returns true if the value has changed.
|
||||
*/
|
||||
template <typename T>
|
||||
bool invokeOn(Context& ctx, T& value) const {
|
||||
if (script) {
|
||||
T new_value;
|
||||
ctx.setVariable(SCRIPT_VAR_value, to_script(value));
|
||||
store(ctx.eval(*script), new_value);
|
||||
if (value != new_value) {
|
||||
change(value, new_value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// Invoke the script on a value if it is in the default state
|
||||
template <typename T>
|
||||
bool invokeOnDefault(Context& ctx, Defaultable<T>& value) const {
|
||||
if (value.isDefault()) {
|
||||
return invokeOn(ctx, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize things this script depends on by adding dep to their list of dependent scripts
|
||||
void initDependencies(Context&, const Dependency& dep) const;
|
||||
|
||||
/// Get access to the script, be careful
|
||||
Script& getMutableScript();
|
||||
inline ScriptP getScriptP() const { return script; }
|
||||
inline void setScriptP(const ScriptP& new_script) { script = new_script; }
|
||||
/// Get access to the unparsed value
|
||||
inline const String& getUnparsed() const { return unparsed; }
|
||||
inline String& getMutableUnparsed() { return unparsed; }
|
||||
inline void setUnparsed(String& new_unparsed) { unparsed = new_unparsed; }
|
||||
|
||||
inline OptionalScript() {}
|
||||
OptionalScript(const String& script_);
|
||||
~OptionalScript();
|
||||
/// Is the script set?
|
||||
inline operator bool() const { return !!script; }
|
||||
|
||||
/// Invoke the script, return the result, or script_nil if there is no script
|
||||
ScriptValueP invoke(Context& ctx, bool open_scope = true) const;
|
||||
|
||||
/// Invoke the script on a value
|
||||
/** Assigns the result to value if it has changed.
|
||||
* Returns true if the value has changed.
|
||||
*/
|
||||
template <typename T>
|
||||
bool invokeOn(Context& ctx, T& value) const {
|
||||
if (script) {
|
||||
T new_value;
|
||||
ctx.setVariable(SCRIPT_VAR_value, to_script(value));
|
||||
store(ctx.eval(*script), new_value);
|
||||
if (value != new_value) {
|
||||
change(value, new_value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/// Invoke the script on a value if it is in the default state
|
||||
template <typename T>
|
||||
bool invokeOnDefault(Context& ctx, Defaultable<T>& value) const {
|
||||
if (value.isDefault()) {
|
||||
return invokeOn(ctx, value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize things this script depends on by adding dep to their list of dependent scripts
|
||||
void initDependencies(Context&, const Dependency& dep) const;
|
||||
|
||||
/// Get access to the script, be careful
|
||||
Script& getMutableScript();
|
||||
inline ScriptP getScriptP() const { return script; }
|
||||
inline void setScriptP(const ScriptP& new_script) { script = new_script; }
|
||||
/// Get access to the unparsed value
|
||||
inline const String& getUnparsed() const { return unparsed; }
|
||||
inline String& getMutableUnparsed() { return unparsed; }
|
||||
inline void setUnparsed(String& new_unparsed) { unparsed = new_unparsed; }
|
||||
|
||||
protected:
|
||||
ScriptP script; ///< The script, may be null if there is no script
|
||||
String unparsed; ///< Unparsed script, for writing back to a file
|
||||
// parse the unparsed string, while reading
|
||||
void parse(Reader&, bool string_mode = false);
|
||||
DECLARE_REFLECTION();
|
||||
template <typename T> friend class Scriptable;
|
||||
ScriptP script; ///< The script, may be null if there is no script
|
||||
String unparsed; ///< Unparsed script, for writing back to a file
|
||||
// parse the unparsed string, while reading
|
||||
void parse(Reader&, bool string_mode = false);
|
||||
DECLARE_REFLECTION();
|
||||
template <typename T> friend class Scriptable;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : StringScript
|
||||
@@ -107,9 +107,9 @@ class OptionalScript {
|
||||
/// An optional script which is parsed in string mode
|
||||
class StringScript : public OptionalScript {
|
||||
public:
|
||||
const String& get() const;
|
||||
void set(const String&);
|
||||
DECLARE_REFLECTION();
|
||||
const String& get() const;
|
||||
void set(const String&);
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Scriptable
|
||||
@@ -119,63 +119,63 @@ class StringScript : public OptionalScript {
|
||||
template <typename T>
|
||||
class Scriptable {
|
||||
public:
|
||||
Scriptable() : value() {}
|
||||
Scriptable(const T& value) : value(value) {}
|
||||
|
||||
inline operator const T& () const { return value; }
|
||||
inline const T& operator ()() const { return value; }
|
||||
inline T& mutate () { return value; }
|
||||
inline void operator = (const T& value) {
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
inline bool isScripted() const { return script; }
|
||||
/// Has this value been read from a Reader?
|
||||
inline bool hasBeenRead() const { return !script.unparsed.empty(); }
|
||||
|
||||
/// Updates the value by executing the script, returns true if the value has changed
|
||||
inline bool update(Context& ctx) {
|
||||
return script.invokeOn(ctx, value);
|
||||
}
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
Scriptable() : value() {}
|
||||
Scriptable(const T& value) : value(value) {}
|
||||
|
||||
inline operator const T& () const { return value; }
|
||||
inline const T& operator ()() const { return value; }
|
||||
inline T& mutate () { return value; }
|
||||
inline void operator = (const T& value) {
|
||||
this->value = value;
|
||||
}
|
||||
|
||||
inline bool isScripted() const { return script; }
|
||||
/// Has this value been read from a Reader?
|
||||
inline bool hasBeenRead() const { return !script.unparsed.empty(); }
|
||||
|
||||
/// Updates the value by executing the script, returns true if the value has changed
|
||||
inline bool update(Context& ctx) {
|
||||
return script.invokeOn(ctx, value);
|
||||
}
|
||||
|
||||
inline void initDependencies(Context& ctx, const Dependency& dep) const {
|
||||
script.initDependencies(ctx, dep);
|
||||
}
|
||||
|
||||
private:
|
||||
T value; ///< The actual value
|
||||
OptionalScript script; ///< The optional script
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
T value; ///< The actual value
|
||||
OptionalScript script; ///< The optional script
|
||||
|
||||
DECLARE_REFLECTION();
|
||||
};
|
||||
|
||||
// we need some custom io, because the behaviour is different for each of Reader/Writer/GetMember
|
||||
|
||||
template <typename T>
|
||||
void Reader::handle(Scriptable<T>& s) {
|
||||
handle(s.script.unparsed);
|
||||
if (starts_with(s.script.unparsed, _("script:"))) {
|
||||
s.script.unparsed = s.script.unparsed.substr(7);
|
||||
s.script.parse(*this);
|
||||
} else if (s.script.unparsed.find_first_of('{') != String::npos) {
|
||||
s.script.parse(*this, true);
|
||||
} else {
|
||||
unhandle();
|
||||
handle(s.value);
|
||||
}
|
||||
handle(s.script.unparsed);
|
||||
if (starts_with(s.script.unparsed, _("script:"))) {
|
||||
s.script.unparsed = s.script.unparsed.substr(7);
|
||||
s.script.parse(*this);
|
||||
} else if (s.script.unparsed.find_first_of('{') != String::npos) {
|
||||
s.script.parse(*this, true);
|
||||
} else {
|
||||
unhandle();
|
||||
handle(s.value);
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void Writer::handle(const Scriptable<T>& s) {
|
||||
if (s.script) {
|
||||
handle(s.script);
|
||||
} else {
|
||||
handle(s.value);
|
||||
}
|
||||
if (s.script) {
|
||||
handle(s.script);
|
||||
} else {
|
||||
handle(s.value);
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
void GetDefaultMember::handle(const Scriptable<T>& s) {
|
||||
// just handle as the value
|
||||
handle(s.value);
|
||||
// just handle as the value
|
||||
handle(s.value);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : EOF
|
||||
|
||||
+220
-220
@@ -17,7 +17,7 @@
|
||||
#include <util/io/get_member.hpp>
|
||||
#include <gfx/generated_image.hpp> // we need the dtor of GeneratedImage
|
||||
#if USE_SCRIPT_PROFILING
|
||||
#include <typeinfo>
|
||||
#include <typeinfo>
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------- : Overloadable templates
|
||||
@@ -25,12 +25,12 @@
|
||||
/// Number of items in some collection like object, can be overloaded
|
||||
template <typename T>
|
||||
int item_count(const T& v) {
|
||||
return -1;
|
||||
return -1;
|
||||
}
|
||||
/// Return an iterator for some collection, can be overloaded
|
||||
template <typename T>
|
||||
ScriptValueP make_iterator(const T& v) {
|
||||
return ScriptValueP();
|
||||
return ScriptValueP();
|
||||
}
|
||||
|
||||
/// Mark a dependency on a member of value, can be overloaded
|
||||
@@ -43,21 +43,21 @@ void mark_dependency_value(const T& value, const Dependency& dep) {}
|
||||
|
||||
/// Type name of an object, for error messages
|
||||
template <typename T> inline String type_name(const T&) {
|
||||
return _TYPE_("object");
|
||||
return _TYPE_("object");
|
||||
}
|
||||
template <typename T> inline String type_name(const intrusive_ptr<T>& p) {
|
||||
return type_name(*p.get());
|
||||
return type_name(*p.get());
|
||||
}
|
||||
template <typename K, typename V> inline String type_name(const pair<K,V>& p) {
|
||||
return type_name(p.second); // for maps
|
||||
return type_name(p.second); // for maps
|
||||
}
|
||||
|
||||
/// Script code for an object, or anything else we can show
|
||||
template <typename T> inline String to_code(const T& v) {
|
||||
return format_string(_("<%s>"),type_name(v));
|
||||
return format_string(_("<%s>"),type_name(v));
|
||||
}
|
||||
template <typename T> inline String to_code(const intrusive_ptr<T>& p) {
|
||||
return type_name(*p.get());
|
||||
return type_name(*p.get());
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Errors
|
||||
@@ -68,50 +68,50 @@ template <typename T> inline String to_code(const intrusive_ptr<T>& p) {
|
||||
*/
|
||||
class ScriptDelayedError : public ScriptValue {
|
||||
public:
|
||||
inline ScriptDelayedError(const ScriptError& error) : error(error) {}
|
||||
inline ScriptDelayedError(const ScriptError& error) : error(error) {}
|
||||
|
||||
virtual ScriptType type() const;// { return SCRIPT_ERROR; }
|
||||
virtual ScriptType type() const;// { return SCRIPT_ERROR; }
|
||||
|
||||
// all of these throw
|
||||
virtual String typeName() const;
|
||||
virtual operator String() const;
|
||||
virtual operator double() const;
|
||||
virtual operator int() const;
|
||||
virtual operator bool() const;
|
||||
virtual operator AColor() const;
|
||||
virtual int itemCount() const;
|
||||
virtual CompareWhat compareAs(String&, void const*&) const;
|
||||
// these can propagate the error
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency&) const;
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const;
|
||||
// all of these throw
|
||||
virtual String typeName() const;
|
||||
virtual operator String() const;
|
||||
virtual operator double() const;
|
||||
virtual operator int() const;
|
||||
virtual operator bool() const;
|
||||
virtual operator AColor() const;
|
||||
virtual int itemCount() const;
|
||||
virtual CompareWhat compareAs(String&, void const*&) const;
|
||||
// these can propagate the error
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency&) const;
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const;
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context&, bool openScope) const;
|
||||
virtual ScriptValueP do_eval(Context&, bool openScope) const;
|
||||
|
||||
private:
|
||||
ScriptError error; // the error message
|
||||
ScriptError error; // the error message
|
||||
};
|
||||
|
||||
inline ScriptValueP delay_error(const String& m) {
|
||||
return intrusive(new ScriptDelayedError(ScriptError(m)));
|
||||
return intrusive(new ScriptDelayedError(ScriptError(m)));
|
||||
}
|
||||
inline ScriptValueP delay_error(const ScriptError& error) {
|
||||
return intrusive(new ScriptDelayedError(error));
|
||||
return intrusive(new ScriptDelayedError(error));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Iterators
|
||||
|
||||
// Iterator over a collection
|
||||
struct ScriptIterator : public ScriptValue {
|
||||
virtual ScriptType type() const;// { return SCRIPT_ITERATOR; }
|
||||
virtual String typeName() const;// { return "iterator"; }
|
||||
virtual CompareWhat compareAs(String&, void const*&) const; // { return COMPARE_NO; }
|
||||
virtual ScriptType type() const;// { return SCRIPT_ITERATOR; }
|
||||
virtual String typeName() const;// { return "iterator"; }
|
||||
virtual CompareWhat compareAs(String&, void const*&) const; // { return COMPARE_NO; }
|
||||
|
||||
/// 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;
|
||||
/// 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
|
||||
@@ -123,101 +123,101 @@ ScriptValueP to_script(int);
|
||||
|
||||
class ScriptCollectionBase : public ScriptValue {
|
||||
public:
|
||||
virtual ScriptType type() const { return SCRIPT_COLLECTION; }
|
||||
virtual String typeName() const { return _TYPE_("collection"); }
|
||||
virtual String toCode() const;
|
||||
virtual ScriptType type() const { return SCRIPT_COLLECTION; }
|
||||
virtual String typeName() const { return _TYPE_("collection"); }
|
||||
virtual String toCode() const;
|
||||
};
|
||||
|
||||
// Iterator over a collection
|
||||
template <typename Collection>
|
||||
class ScriptCollectionIterator : public ScriptIterator {
|
||||
public:
|
||||
ScriptCollectionIterator(const Collection* col) : pos(0), col(col) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos < col->size()) {
|
||||
if (key_out) *key_out = to_script((int)pos);
|
||||
return to_script(col->at(pos++));
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
ScriptCollectionIterator(const Collection* col) : pos(0), col(col) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos < col->size()) {
|
||||
if (key_out) *key_out = to_script((int)pos);
|
||||
return to_script(col->at(pos++));
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
private:
|
||||
size_t pos;
|
||||
const Collection* col;
|
||||
size_t pos;
|
||||
const Collection* col;
|
||||
};
|
||||
|
||||
/// Script value containing a collection
|
||||
template <typename Collection>
|
||||
class ScriptCollection : public ScriptCollectionBase {
|
||||
public:
|
||||
inline ScriptCollection(const Collection* v) : value(v) {}
|
||||
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()) {
|
||||
return to_script(value->at(index));
|
||||
} else {
|
||||
return ScriptValue::getIndex(index);
|
||||
}
|
||||
}
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const {
|
||||
return intrusive(new ScriptCollectionIterator<Collection>(value));
|
||||
}
|
||||
virtual int itemCount() const { return (int)value->size(); }
|
||||
/// Collections can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
inline ScriptCollection(const Collection* v) : value(v) {}
|
||||
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()) {
|
||||
return to_script(value->at(index));
|
||||
} else {
|
||||
return ScriptValue::getIndex(index);
|
||||
}
|
||||
}
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const {
|
||||
return intrusive(new ScriptCollectionIterator<Collection>(value));
|
||||
}
|
||||
virtual int itemCount() const { return (int)value->size(); }
|
||||
/// Collections can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
private:
|
||||
/// Store a pointer to a collection, collections are only ever used for structures owned outside the script
|
||||
const Collection* value;
|
||||
/// Store a pointer to a collection, collections are only ever used for structures owned outside the script
|
||||
const Collection* value;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Collections : maps
|
||||
|
||||
template <typename V>
|
||||
ScriptValueP get_member(const map<String,V>& m, const String& name) {
|
||||
typename map<String,V>::const_iterator it = m.find(name);
|
||||
if (it != m.end()) {
|
||||
return to_script(it->second);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name));
|
||||
}
|
||||
typename map<String,V>::const_iterator it = m.find(name);
|
||||
if (it != m.end()) {
|
||||
return to_script(it->second);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename K, typename V>
|
||||
ScriptValueP get_member(const IndexMap<K,V>& m, const String& name) {
|
||||
typename IndexMap<K,V>::const_iterator it = m.find(name);
|
||||
if (it != m.end()) {
|
||||
return to_script(*it);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name));
|
||||
}
|
||||
typename IndexMap<K,V>::const_iterator it = m.find(name);
|
||||
if (it != m.end()) {
|
||||
return to_script(*it);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(_TYPE_("collection"), name));
|
||||
}
|
||||
}
|
||||
|
||||
/// Script value containing a map-like collection
|
||||
template <typename Collection>
|
||||
class ScriptMap : public ScriptValue {
|
||||
public:
|
||||
inline ScriptMap(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 {
|
||||
return get_member(*value, name);
|
||||
}
|
||||
virtual int itemCount() const { return (int)value->size(); }
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency& dep) const {
|
||||
mark_dependency_member(*value, name, dep);
|
||||
return getMember(name);
|
||||
}
|
||||
/// Collections can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
inline ScriptMap(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 {
|
||||
return get_member(*value, name);
|
||||
}
|
||||
virtual int itemCount() const { return (int)value->size(); }
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency& dep) const {
|
||||
mark_dependency_member(*value, name, dep);
|
||||
return getMember(name);
|
||||
}
|
||||
/// Collections can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
private:
|
||||
/// Store a pointer to a collection, collections are only ever used for structures owned outside the script
|
||||
const Collection* value;
|
||||
/// Store a pointer to a collection, collections are only ever used for structures owned outside the script
|
||||
const Collection* value;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Collections : from script
|
||||
@@ -225,20 +225,20 @@ class ScriptMap : public ScriptValue {
|
||||
/// Script value containing a custom collection, returned from script functions
|
||||
class ScriptCustomCollection : public ScriptCollectionBase {
|
||||
public:
|
||||
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
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = this;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
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
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = this;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
|
||||
/// The collection as a list (contains only the values that don't have a key)
|
||||
vector<ScriptValueP> value;
|
||||
/// The collection as a map (contains only the values that have a key)
|
||||
map<String,ScriptValueP> key_value;
|
||||
/// The collection as a list (contains only the values that don't have a key)
|
||||
vector<ScriptValueP> value;
|
||||
/// The collection as a map (contains only the values that have a key)
|
||||
map<String,ScriptValueP> key_value;
|
||||
};
|
||||
|
||||
DECLARE_POINTER_TYPE(ScriptCustomCollection);
|
||||
@@ -248,20 +248,20 @@ DECLARE_POINTER_TYPE(ScriptCustomCollection);
|
||||
/// Script value containing the concatenation of two collections
|
||||
class ScriptConcatCollection : public ScriptCollectionBase {
|
||||
public:
|
||||
inline ScriptConcatCollection(ScriptValueP a, ScriptValueP b) : a(a), b(b) {}
|
||||
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
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = this;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
inline ScriptConcatCollection(ScriptValueP a, ScriptValueP b) : a(a), b(b) {}
|
||||
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
|
||||
virtual CompareWhat compareAs(String&, void const*& compare_ptr) const {
|
||||
compare_ptr = this;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
|
||||
private:
|
||||
ScriptValueP a,b;
|
||||
friend class ScriptConcatCollectionIterator;
|
||||
ScriptValueP a,b;
|
||||
friend class ScriptConcatCollectionIterator;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Objects
|
||||
@@ -270,77 +270,77 @@ class ScriptConcatCollection : public ScriptCollectionBase {
|
||||
template <typename T>
|
||||
class ScriptObject : public ScriptValue {
|
||||
public:
|
||||
inline ScriptObject(const T& v) : value(v) {}
|
||||
virtual ScriptType type() const { ScriptValueP d = getDefault(); return d ? d->type() : SCRIPT_OBJECT; }
|
||||
virtual String typeName() const { return type_name(*value); }
|
||||
virtual operator String() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator String(); }
|
||||
virtual operator double() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator double(); }
|
||||
virtual operator int() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator int(); }
|
||||
virtual operator bool() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator bool(); }
|
||||
virtual operator AColor() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator AColor(); }
|
||||
virtual String toCode() const { ScriptValueP d = getDefault(); return d ? d->toCode() : to_code(*value); }
|
||||
virtual GeneratedImageP toImage(const ScriptValueP& thisP) const {
|
||||
ScriptValueP d = getDefault(); return d ? d->toImage(d) : ScriptValue::toImage(thisP);
|
||||
}
|
||||
virtual ScriptValueP getMember(const String& name) const {
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, (void*)mangled_name(typeid(T)), _("get member of ") + type_name(*value));
|
||||
#endif
|
||||
GetMember gm(name);
|
||||
gm.handle(*value);
|
||||
if (gm.result()) return gm.result();
|
||||
else {
|
||||
// try nameless member
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) {
|
||||
return d->getMember(name);
|
||||
} else {
|
||||
return ScriptValue::getMember(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
virtual void dependencyThis(const Dependency& dep) {
|
||||
mark_dependency_value(*value, dep);
|
||||
}
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const {
|
||||
ScriptValueP it = make_iterator(*value);
|
||||
if (it) return it;
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) return d->makeIterator(d);
|
||||
return ScriptValue::makeIterator(thisP);
|
||||
}
|
||||
virtual int itemCount() const {
|
||||
int i = item_count(*value);
|
||||
if (i >= 0) return i;
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) return d->itemCount();
|
||||
return ScriptValue::itemCount();
|
||||
}
|
||||
/// Objects can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String& compare_str, void const*& compare_ptr) const {
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) {
|
||||
return d->compareAs(compare_str, compare_ptr);
|
||||
} else {
|
||||
compare_ptr = &*value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
}
|
||||
/// Get access to the value
|
||||
inline T getValue() const { return value; }
|
||||
inline ScriptObject(const T& v) : value(v) {}
|
||||
virtual ScriptType type() const { ScriptValueP d = getDefault(); return d ? d->type() : SCRIPT_OBJECT; }
|
||||
virtual String typeName() const { return type_name(*value); }
|
||||
virtual operator String() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator String(); }
|
||||
virtual operator double() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator double(); }
|
||||
virtual operator int() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator int(); }
|
||||
virtual operator bool() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator bool(); }
|
||||
virtual operator AColor() const { ScriptValueP d = getDefault(); return d ? *d : ScriptValue::operator AColor(); }
|
||||
virtual String toCode() const { ScriptValueP d = getDefault(); return d ? d->toCode() : to_code(*value); }
|
||||
virtual GeneratedImageP toImage(const ScriptValueP& thisP) const {
|
||||
ScriptValueP d = getDefault(); return d ? d->toImage(d) : ScriptValue::toImage(thisP);
|
||||
}
|
||||
virtual ScriptValueP getMember(const String& name) const {
|
||||
#if USE_SCRIPT_PROFILING
|
||||
Timer t;
|
||||
Profiler prof(t, (void*)mangled_name(typeid(T)), _("get member of ") + type_name(*value));
|
||||
#endif
|
||||
GetMember gm(name);
|
||||
gm.handle(*value);
|
||||
if (gm.result()) return gm.result();
|
||||
else {
|
||||
// try nameless member
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) {
|
||||
return d->getMember(name);
|
||||
} else {
|
||||
return ScriptValue::getMember(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
virtual void dependencyThis(const Dependency& dep) {
|
||||
mark_dependency_value(*value, dep);
|
||||
}
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const {
|
||||
ScriptValueP it = make_iterator(*value);
|
||||
if (it) return it;
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) return d->makeIterator(d);
|
||||
return ScriptValue::makeIterator(thisP);
|
||||
}
|
||||
virtual int itemCount() const {
|
||||
int i = item_count(*value);
|
||||
if (i >= 0) return i;
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) return d->itemCount();
|
||||
return ScriptValue::itemCount();
|
||||
}
|
||||
/// Objects can be compared by comparing pointers
|
||||
virtual CompareWhat compareAs(String& compare_str, void const*& compare_ptr) const {
|
||||
ScriptValueP d = getDefault();
|
||||
if (d) {
|
||||
return d->compareAs(compare_str, compare_ptr);
|
||||
} else {
|
||||
compare_ptr = &*value;
|
||||
return COMPARE_AS_POINTER;
|
||||
}
|
||||
}
|
||||
/// Get access to the value
|
||||
inline T getValue() const { return value; }
|
||||
private:
|
||||
T value; ///< The object
|
||||
ScriptValueP getDefault() const {
|
||||
GetDefaultMember gdm;
|
||||
gdm.handle(*value);
|
||||
return gdm.result();
|
||||
}
|
||||
T value; ///< The object
|
||||
ScriptValueP getDefault() const {
|
||||
GetDefaultMember gdm;
|
||||
gdm.handle(*value);
|
||||
return gdm.result();
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Default arguments / closure
|
||||
@@ -348,45 +348,45 @@ class ScriptObject : public ScriptValue {
|
||||
/// A wrapper around a function that gives default arguments
|
||||
class ScriptClosure : public ScriptValue {
|
||||
public:
|
||||
ScriptClosure(ScriptValueP fun) : fun(fun) {}
|
||||
ScriptClosure(ScriptValueP fun) : fun(fun) {}
|
||||
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency& dep) const;
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
virtual ScriptValueP dependencies(Context& ctx, const Dependency& dep) const;
|
||||
|
||||
/// Add a binding
|
||||
void addBinding(Variable v, const ScriptValueP& value);
|
||||
/// Is there a binding for the given variable? If so, retrieve it
|
||||
ScriptValueP getBinding(Variable v) const;
|
||||
/// Add a binding
|
||||
void addBinding(Variable v, const ScriptValueP& value);
|
||||
/// Is there a binding for the given variable? If so, retrieve it
|
||||
ScriptValueP getBinding(Variable v) const;
|
||||
|
||||
/// Try to simplify this closure, returns a value if successful
|
||||
ScriptValueP simplify();
|
||||
/// Try to simplify this closure, returns a value if successful
|
||||
ScriptValueP simplify();
|
||||
|
||||
/// The wrapped function
|
||||
ScriptValueP fun;
|
||||
/// The default argument bindings
|
||||
vector<pair<Variable,ScriptValueP> > bindings;
|
||||
/// The wrapped function
|
||||
ScriptValueP fun;
|
||||
/// The default argument bindings
|
||||
vector<pair<Variable,ScriptValueP> > bindings;
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
|
||||
private:
|
||||
/// Apply the bindings in a context
|
||||
void applyBindings(Context& ctx) const;
|
||||
/// Apply the bindings in a context
|
||||
void applyBindings(Context& ctx) const;
|
||||
};
|
||||
|
||||
/// Turn a script function into a rule, a.k.a. a delayed closure
|
||||
class ScriptRule : public ScriptValue {
|
||||
public:
|
||||
inline ScriptRule(const ScriptValueP& fun) : fun(fun) {}
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
inline ScriptRule(const ScriptValueP& fun) : fun(fun) {}
|
||||
virtual ScriptType type() const;
|
||||
virtual String typeName() const;
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
|
||||
private:
|
||||
ScriptValueP fun;
|
||||
ScriptValueP fun;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------- : Creating
|
||||
@@ -415,11 +415,11 @@ inline ScriptValueP to_script(const Defaultable<T>& v) { return to_script(v());
|
||||
|
||||
/// Convert a value from a script value to a normal value
|
||||
template <typename T> inline T from_script (const ScriptValueP& value) {
|
||||
ScriptObject<T>* o = dynamic_cast<ScriptObject<T>*>(value.get());
|
||||
if (!o) {
|
||||
throw ScriptErrorConversion(value->typeName(), _TYPE_("object" ));
|
||||
}
|
||||
return o->getValue();
|
||||
ScriptObject<T>* o = dynamic_cast<ScriptObject<T>*>(value.get());
|
||||
if (!o) {
|
||||
throw ScriptErrorConversion(value->typeName(), _TYPE_("object" ));
|
||||
}
|
||||
return o->getValue();
|
||||
}
|
||||
template <> inline ScriptValueP from_script<ScriptValueP>(const ScriptValueP& value) { return value; }
|
||||
template <> inline String from_script<String> (const ScriptValueP& value) { return *value; }
|
||||
|
||||
+292
-292
@@ -32,19 +32,19 @@ int ScriptValue::itemCount() const { throw Script
|
||||
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();
|
||||
return COMPARE_AS_STRING;
|
||||
compare_str = toCode();
|
||||
return COMPARE_AS_STRING;
|
||||
}
|
||||
ScriptValueP ScriptValue::getMember(const String& name) const {
|
||||
long index;
|
||||
if (name.ToLong(&index)) {
|
||||
return getIndex(index);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(typeName(), name));
|
||||
}
|
||||
long index;
|
||||
if (name.ToLong(&index)) {
|
||||
return getIndex(index);
|
||||
} else {
|
||||
return delay_error(ScriptErrorNoMember(typeName(), name));
|
||||
}
|
||||
}
|
||||
ScriptValueP ScriptValue::getIndex(int index) const {
|
||||
return delay_error(ScriptErrorNoMember(typeName(), String()<<index));
|
||||
return delay_error(ScriptErrorNoMember(typeName(), String()<<index));
|
||||
}
|
||||
|
||||
|
||||
@@ -52,51 +52,51 @@ ScriptValueP ScriptValue::simplifyClosure(ScriptClosure&) const { return ScriptV
|
||||
|
||||
ScriptValueP ScriptValue::dependencyMember(const String& name, const Dependency&) const { return dependency_dummy; }
|
||||
ScriptValueP ScriptValue::dependencyName(const ScriptValue& container, const Dependency& dep) const {
|
||||
return container.dependencyMember(toString(),dep);
|
||||
return container.dependencyMember(toString(),dep);
|
||||
}
|
||||
ScriptValueP ScriptValue::dependencies(Context&, const Dependency&) const { return dependency_dummy; }
|
||||
void ScriptValue::dependencyThis(const Dependency& dep) {}
|
||||
|
||||
bool approx_equal(double a, double b) {
|
||||
return a == b || fabs(a - b) < 1e-14;
|
||||
return a == b || fabs(a - b) < 1e-14;
|
||||
}
|
||||
|
||||
/// compare script values for equallity
|
||||
bool equal(const ScriptValueP& a, const ScriptValueP& b) {
|
||||
if (a == b) return true;
|
||||
ScriptType at = a->type(), bt = b->type();
|
||||
if (at == bt && at == SCRIPT_INT) {
|
||||
return (int)*a == (int)*b;
|
||||
} else if (at == bt && at == SCRIPT_BOOL) {
|
||||
return (bool)*a == (bool)*b;
|
||||
} else if ((at == SCRIPT_INT || at == SCRIPT_DOUBLE) &&
|
||||
(bt == SCRIPT_INT || bt == SCRIPT_DOUBLE)) {
|
||||
return approx_equal( (double)*a, (double)*b);
|
||||
} else if (at == SCRIPT_COLLECTION && bt == SCRIPT_COLLECTION) {
|
||||
// compare each element
|
||||
if (a->itemCount() != b->itemCount()) return false;
|
||||
ScriptValueP a_it = a->makeIterator(a);
|
||||
ScriptValueP b_it = b->makeIterator(b);
|
||||
while (true) {
|
||||
ScriptValueP a_v = a_it->next();
|
||||
ScriptValueP b_v = b_it->next();
|
||||
if (!a_v || !b_v) return a_v == b_v;
|
||||
if (!equal(a_v, b_v)) return false;
|
||||
}
|
||||
} else {
|
||||
String as, bs;
|
||||
const void* ap, *bp;
|
||||
CompareWhat aw = a->compareAs(as, ap);
|
||||
CompareWhat bw = b->compareAs(bs, bp);
|
||||
// compare pointers or strings
|
||||
if (aw == COMPARE_AS_STRING || bw == COMPARE_AS_STRING) {
|
||||
return as == bs;
|
||||
} else if (aw == COMPARE_AS_POINTER || bw == COMPARE_AS_POINTER) {
|
||||
return ap == bp;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (a == b) return true;
|
||||
ScriptType at = a->type(), bt = b->type();
|
||||
if (at == bt && at == SCRIPT_INT) {
|
||||
return (int)*a == (int)*b;
|
||||
} else if (at == bt && at == SCRIPT_BOOL) {
|
||||
return (bool)*a == (bool)*b;
|
||||
} else if ((at == SCRIPT_INT || at == SCRIPT_DOUBLE) &&
|
||||
(bt == SCRIPT_INT || bt == SCRIPT_DOUBLE)) {
|
||||
return approx_equal( (double)*a, (double)*b);
|
||||
} else if (at == SCRIPT_COLLECTION && bt == SCRIPT_COLLECTION) {
|
||||
// compare each element
|
||||
if (a->itemCount() != b->itemCount()) return false;
|
||||
ScriptValueP a_it = a->makeIterator(a);
|
||||
ScriptValueP b_it = b->makeIterator(b);
|
||||
while (true) {
|
||||
ScriptValueP a_v = a_it->next();
|
||||
ScriptValueP b_v = b_it->next();
|
||||
if (!a_v || !b_v) return a_v == b_v;
|
||||
if (!equal(a_v, b_v)) return false;
|
||||
}
|
||||
} else {
|
||||
String as, bs;
|
||||
const void* ap, *bp;
|
||||
CompareWhat aw = a->compareAs(as, ap);
|
||||
CompareWhat bw = b->compareAs(bs, bp);
|
||||
// compare pointers or strings
|
||||
if (aw == COMPARE_AS_STRING || bw == COMPARE_AS_STRING) {
|
||||
return as == bs;
|
||||
} else if (aw == COMPARE_AS_POINTER || bw == COMPARE_AS_POINTER) {
|
||||
return ap == bp;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Errors
|
||||
@@ -128,23 +128,23 @@ ScriptValueP ScriptIterator::makeIterator(const ScriptValueP& thisP) const { ret
|
||||
// Iterator over a range of integers
|
||||
class ScriptRangeIterator : public ScriptIterator {
|
||||
public:
|
||||
// Construct a range iterator with the given bounds (inclusive)
|
||||
ScriptRangeIterator(int start, int end)
|
||||
: pos(start), start(start), end(end) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos <= end) {
|
||||
if (key_out) *key_out = to_script(pos-start);
|
||||
return to_script(pos++);
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
// Construct a range iterator with the given bounds (inclusive)
|
||||
ScriptRangeIterator(int start, int end)
|
||||
: pos(start), start(start), end(end) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos <= end) {
|
||||
if (key_out) *key_out = to_script(pos-start);
|
||||
return to_script(pos++);
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
private:
|
||||
int pos, start, end;
|
||||
int pos, start, end;
|
||||
};
|
||||
|
||||
ScriptValueP rangeIterator(int start, int end) {
|
||||
return intrusive(new ScriptRangeIterator(start, end));
|
||||
return intrusive(new ScriptRangeIterator(start, end));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Integers
|
||||
@@ -154,43 +154,43 @@ ScriptValueP rangeIterator(int start, int end) {
|
||||
// Integer values
|
||||
class ScriptInt : public ScriptValue {
|
||||
public:
|
||||
ScriptInt(int v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_INT; }
|
||||
virtual String typeName() const { return _TYPE_("integer"); }
|
||||
virtual operator String() const { return String() << value; }
|
||||
virtual operator double() const { return value; }
|
||||
virtual operator int() const { return value; }
|
||||
ScriptInt(int v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_INT; }
|
||||
virtual String typeName() const { return _TYPE_("integer"); }
|
||||
virtual operator String() const { return String() << value; }
|
||||
virtual operator double() const { return value; }
|
||||
virtual operator int() const { return value; }
|
||||
protected:
|
||||
#ifdef USE_POOL_ALLOCATOR
|
||||
virtual void destroy() {
|
||||
boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::free(this);
|
||||
}
|
||||
virtual void destroy() {
|
||||
boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::free(this);
|
||||
}
|
||||
#endif
|
||||
private:
|
||||
int value;
|
||||
int value;
|
||||
};
|
||||
|
||||
#if defined(USE_POOL_ALLOCATOR) && !defined(USE_INTRUSIVE_PTR)
|
||||
// deallocation function for pool allocated integers
|
||||
void destroy_value(ScriptInt* v) {
|
||||
boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::free(v);
|
||||
}
|
||||
// deallocation function for pool allocated integers
|
||||
void destroy_value(ScriptInt* v) {
|
||||
boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::free(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
ScriptValueP to_script(int v) {
|
||||
#ifdef USE_POOL_ALLOCATOR
|
||||
#ifdef USE_INTRUSIVE_PTR
|
||||
return ScriptValueP(
|
||||
new(boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::malloc())
|
||||
ScriptInt(v));
|
||||
#else
|
||||
return ScriptValueP(
|
||||
new(boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::malloc())
|
||||
ScriptInt(v),
|
||||
destroy_value); // deallocation function
|
||||
#endif
|
||||
#ifdef USE_INTRUSIVE_PTR
|
||||
return ScriptValueP(
|
||||
new(boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::malloc())
|
||||
ScriptInt(v));
|
||||
#else
|
||||
return ScriptValueP(
|
||||
new(boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::malloc())
|
||||
ScriptInt(v),
|
||||
destroy_value); // deallocation function
|
||||
#endif
|
||||
#else
|
||||
return intrusive(new ScriptInt(v));
|
||||
return intrusive(new ScriptInt(v));
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -199,14 +199,14 @@ ScriptValueP to_script(int v) {
|
||||
// Boolean values
|
||||
class ScriptBool : public ScriptValue {
|
||||
public:
|
||||
ScriptBool(bool v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_BOOL; }
|
||||
virtual String typeName() const { return _TYPE_("boolean"); }
|
||||
virtual operator String() const { return value ? _("true") : _("false"); }
|
||||
// bools don't autoconvert to int
|
||||
virtual operator bool() const { return value; }
|
||||
ScriptBool(bool v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_BOOL; }
|
||||
virtual String typeName() const { return _TYPE_("boolean"); }
|
||||
virtual operator String() const { return value ? _("true") : _("false"); }
|
||||
// bools don't autoconvert to int
|
||||
virtual operator bool() const { return value; }
|
||||
private:
|
||||
bool value;
|
||||
bool value;
|
||||
};
|
||||
|
||||
// use integers to represent true/false
|
||||
@@ -222,18 +222,18 @@ ScriptValueP script_false(new ScriptBool(false));
|
||||
// Double values
|
||||
class ScriptDouble : public ScriptValue {
|
||||
public:
|
||||
ScriptDouble(double v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_DOUBLE; }
|
||||
virtual String typeName() const { return _TYPE_("double"); }
|
||||
virtual operator String() const { return String() << value; }
|
||||
virtual operator double() const { return value; }
|
||||
virtual operator int() const { return (int)value; }
|
||||
ScriptDouble(double v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_DOUBLE; }
|
||||
virtual String typeName() const { return _TYPE_("double"); }
|
||||
virtual operator String() const { return String() << value; }
|
||||
virtual operator double() const { return value; }
|
||||
virtual operator int() const { return (int)value; }
|
||||
private:
|
||||
double value;
|
||||
double value;
|
||||
};
|
||||
|
||||
ScriptValueP to_script(double v) {
|
||||
return intrusive(new ScriptDouble(v));
|
||||
return intrusive(new ScriptDouble(v));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : String type
|
||||
@@ -241,72 +241,72 @@ ScriptValueP to_script(double v) {
|
||||
// String values
|
||||
class ScriptString : public ScriptValue {
|
||||
public:
|
||||
ScriptString(const String& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_STRING; }
|
||||
virtual String typeName() const { return _TYPE_("string") + _(" (\"") + (value.size() < 30 ? value : value.substr(0,30) + _("...")) + _("\")"); }
|
||||
virtual operator String() const { return value; }
|
||||
virtual operator double() const {
|
||||
double d;
|
||||
if (value.ToDouble(&d)) {
|
||||
return d;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("double"));
|
||||
}
|
||||
}
|
||||
virtual operator int() const {
|
||||
long l;
|
||||
if (value.ToLong(&l)) {
|
||||
return l;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("integer"));
|
||||
}
|
||||
}
|
||||
virtual operator bool() const {
|
||||
if (value == _("yes") || value == _("true")) {
|
||||
return true;
|
||||
} else if (value == _("no") || value == _("false") || value.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("boolean"));
|
||||
}
|
||||
}
|
||||
virtual operator AColor() const {
|
||||
AColor c = parse_acolor(value);
|
||||
if (!c.Ok()) {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("color"));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
virtual operator wxDateTime() const {
|
||||
wxDateTime date;
|
||||
if (!date.ParseDateTime(value.c_str())) {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("date"));
|
||||
}
|
||||
return date;
|
||||
}
|
||||
virtual GeneratedImageP toImage(const ScriptValueP&) const {
|
||||
if (value.empty()) {
|
||||
return intrusive(new BlankImage());
|
||||
} else {
|
||||
return intrusive(new PackagedImage(value));
|
||||
}
|
||||
}
|
||||
virtual int itemCount() const { return (int)value.size(); }
|
||||
virtual ScriptValueP getMember(const String& name) const {
|
||||
// get member returns characters
|
||||
long index;
|
||||
if (name.ToLong(&index) && index >= 0 && (size_t)index < value.size()) {
|
||||
return to_script(String(1,value[index]));
|
||||
} else {
|
||||
return delay_error(_ERROR_2_("has no member value", value, name));
|
||||
}
|
||||
}
|
||||
ScriptString(const String& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_STRING; }
|
||||
virtual String typeName() const { return _TYPE_("string") + _(" (\"") + (value.size() < 30 ? value : value.substr(0,30) + _("...")) + _("\")"); }
|
||||
virtual operator String() const { return value; }
|
||||
virtual operator double() const {
|
||||
double d;
|
||||
if (value.ToDouble(&d)) {
|
||||
return d;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("double"));
|
||||
}
|
||||
}
|
||||
virtual operator int() const {
|
||||
long l;
|
||||
if (value.ToLong(&l)) {
|
||||
return l;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("integer"));
|
||||
}
|
||||
}
|
||||
virtual operator bool() const {
|
||||
if (value == _("yes") || value == _("true")) {
|
||||
return true;
|
||||
} else if (value == _("no") || value == _("false") || value.empty()) {
|
||||
return false;
|
||||
} else {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("boolean"));
|
||||
}
|
||||
}
|
||||
virtual operator AColor() const {
|
||||
AColor c = parse_acolor(value);
|
||||
if (!c.Ok()) {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("color"));
|
||||
}
|
||||
return c;
|
||||
}
|
||||
virtual operator wxDateTime() const {
|
||||
wxDateTime date;
|
||||
if (!date.ParseDateTime(value.c_str())) {
|
||||
throw ScriptErrorConversion(value, typeName(), _TYPE_("date"));
|
||||
}
|
||||
return date;
|
||||
}
|
||||
virtual GeneratedImageP toImage(const ScriptValueP&) const {
|
||||
if (value.empty()) {
|
||||
return intrusive(new BlankImage());
|
||||
} else {
|
||||
return intrusive(new PackagedImage(value));
|
||||
}
|
||||
}
|
||||
virtual int itemCount() const { return (int)value.size(); }
|
||||
virtual ScriptValueP getMember(const String& name) const {
|
||||
// get member returns characters
|
||||
long index;
|
||||
if (name.ToLong(&index) && index >= 0 && (size_t)index < value.size()) {
|
||||
return to_script(String(1,value[index]));
|
||||
} else {
|
||||
return delay_error(_ERROR_2_("has no member value", value, name));
|
||||
}
|
||||
}
|
||||
private:
|
||||
String value;
|
||||
String value;
|
||||
};
|
||||
|
||||
ScriptValueP to_script(const String& v) {
|
||||
return intrusive(new ScriptString(v));
|
||||
return intrusive(new ScriptString(v));
|
||||
}
|
||||
|
||||
|
||||
@@ -315,23 +315,23 @@ ScriptValueP to_script(const String& v) {
|
||||
// AColor values
|
||||
class ScriptAColor : public ScriptValue {
|
||||
public:
|
||||
ScriptAColor(const AColor& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_COLOR; }
|
||||
virtual String typeName() const { return _TYPE_("color"); }
|
||||
virtual operator AColor() const { return value; }
|
||||
// colors don't auto convert to int, use to_int to force
|
||||
virtual operator String() const {
|
||||
return format_acolor(value);
|
||||
}
|
||||
ScriptAColor(const AColor& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_COLOR; }
|
||||
virtual String typeName() const { return _TYPE_("color"); }
|
||||
virtual operator AColor() const { return value; }
|
||||
// colors don't auto convert to int, use to_int to force
|
||||
virtual operator String() const {
|
||||
return format_acolor(value);
|
||||
}
|
||||
private:
|
||||
AColor value;
|
||||
AColor value;
|
||||
};
|
||||
|
||||
ScriptValueP to_script(Color v) {
|
||||
return intrusive(new ScriptAColor(v));
|
||||
return intrusive(new ScriptAColor(v));
|
||||
}
|
||||
ScriptValueP to_script(AColor v) {
|
||||
return intrusive(new ScriptAColor(v));
|
||||
return intrusive(new ScriptAColor(v));
|
||||
}
|
||||
|
||||
|
||||
@@ -340,19 +340,19 @@ ScriptValueP to_script(AColor v) {
|
||||
// wxDateTime values
|
||||
class ScriptDateTime : public ScriptValue {
|
||||
public:
|
||||
ScriptDateTime(const wxDateTime& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_DATETIME; }
|
||||
virtual String typeName() const { return _TYPE_("date"); }
|
||||
virtual operator wxDateTime() const { return value; }
|
||||
virtual operator String() const {
|
||||
return value.Format(_("%Y-%m-%d %H:%M:%S"));
|
||||
}
|
||||
ScriptDateTime(const wxDateTime& v) : value(v) {}
|
||||
virtual ScriptType type() const { return SCRIPT_DATETIME; }
|
||||
virtual String typeName() const { return _TYPE_("date"); }
|
||||
virtual operator wxDateTime() const { return value; }
|
||||
virtual operator String() const {
|
||||
return value.Format(_("%Y-%m-%d %H:%M:%S"));
|
||||
}
|
||||
private:
|
||||
wxDateTime value;
|
||||
wxDateTime value;
|
||||
};
|
||||
|
||||
ScriptValueP to_script(wxDateTime v) {
|
||||
return intrusive(new ScriptDateTime(v));
|
||||
return intrusive(new ScriptDateTime(v));
|
||||
}
|
||||
|
||||
|
||||
@@ -361,21 +361,21 @@ ScriptValueP to_script(wxDateTime v) {
|
||||
// the nil object
|
||||
class ScriptNil : public ScriptValue {
|
||||
public:
|
||||
virtual ScriptType type() const { return SCRIPT_NIL; }
|
||||
virtual String typeName() const { return _TYPE_("nil"); }
|
||||
virtual operator String() const { return wxEmptyString; }
|
||||
virtual operator double() const { return 0.0; }
|
||||
virtual operator int() const { return 0; }
|
||||
virtual operator bool() const { return false; }
|
||||
virtual GeneratedImageP toImage(const ScriptValueP&) const {
|
||||
return intrusive(new BlankImage());
|
||||
}
|
||||
virtual ScriptType type() const { return SCRIPT_NIL; }
|
||||
virtual String typeName() const { return _TYPE_("nil"); }
|
||||
virtual operator String() const { return wxEmptyString; }
|
||||
virtual operator double() const { return 0.0; }
|
||||
virtual operator int() const { return 0; }
|
||||
virtual operator bool() const { return false; }
|
||||
virtual GeneratedImageP toImage(const ScriptValueP&) const {
|
||||
return intrusive(new BlankImage());
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const {
|
||||
// nil(input) == input
|
||||
return ctx.getVariable(SCRIPT_VAR_input);
|
||||
}
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool) const {
|
||||
// nil(input) == input
|
||||
return ctx.getVariable(SCRIPT_VAR_input);
|
||||
}
|
||||
};
|
||||
|
||||
/// The preallocated nil value
|
||||
@@ -384,159 +384,159 @@ 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*>(static_cast<const ScriptValue*>(this))));
|
||||
#else
|
||||
#error "makeIterator needs a ScriptValueP :("
|
||||
#endif
|
||||
while (ScriptValueP v = it->next()) {
|
||||
if (!first) ret += _(",");
|
||||
first = false;
|
||||
// todo: include keys
|
||||
ret += v->toCode();
|
||||
}
|
||||
ret += _("]");
|
||||
return ret;
|
||||
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*>(static_cast<const ScriptValue*>(this))));
|
||||
#else
|
||||
#error "makeIterator needs a ScriptValueP :("
|
||||
#endif
|
||||
while (ScriptValueP v = it->next()) {
|
||||
if (!first) ret += _(",");
|
||||
first = false;
|
||||
// todo: include keys
|
||||
ret += v->toCode();
|
||||
}
|
||||
ret += _("]");
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Custom collection
|
||||
|
||||
// Iterator over a custom collection
|
||||
class ScriptCustomCollectionIterator : public ScriptIterator {
|
||||
public:
|
||||
ScriptCustomCollectionIterator(ScriptCustomCollectionP col)
|
||||
: col(col), pos(0), it(col->key_value.begin()) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos < col->value.size()) {
|
||||
if (key_out) *key_out = to_script((int)pos);
|
||||
return col->value.at(pos++);
|
||||
} else if (it != col->key_value.end()) {
|
||||
if (key_out) *key_out = to_script(it->first);
|
||||
return (it++)->second;
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
public:
|
||||
ScriptCustomCollectionIterator(ScriptCustomCollectionP col)
|
||||
: col(col), pos(0), it(col->key_value.begin()) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (pos < col->value.size()) {
|
||||
if (key_out) *key_out = to_script((int)pos);
|
||||
return col->value.at(pos++);
|
||||
} else if (it != col->key_value.end()) {
|
||||
if (key_out) *key_out = to_script(it->first);
|
||||
return (it++)->second;
|
||||
} else {
|
||||
return ScriptValueP();
|
||||
}
|
||||
}
|
||||
private:
|
||||
ScriptCustomCollectionP col;
|
||||
size_t pos;
|
||||
map<String,ScriptValueP>::const_iterator it;
|
||||
ScriptCustomCollectionP col;
|
||||
size_t pos;
|
||||
map<String,ScriptValueP>::const_iterator it;
|
||||
};
|
||||
|
||||
ScriptValueP ScriptCustomCollection::getMember(const String& name) const {
|
||||
map<String,ScriptValueP>::const_iterator it = key_value.find(name);
|
||||
if (it != key_value.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
return ScriptValue::getMember(name);
|
||||
}
|
||||
map<String,ScriptValueP>::const_iterator it = key_value.find(name);
|
||||
if (it != key_value.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
if (index >= 0 && (size_t)index < value.size()) {
|
||||
return value.at(index);
|
||||
} else {
|
||||
return ScriptValue::getIndex(index);
|
||||
}
|
||||
}
|
||||
ScriptValueP ScriptCustomCollection::makeIterator(const ScriptValueP& thisP) const {
|
||||
return intrusive(new ScriptCustomCollectionIterator(
|
||||
static_pointer_cast<ScriptCustomCollection>(thisP)
|
||||
));
|
||||
return intrusive(new ScriptCustomCollectionIterator(
|
||||
static_pointer_cast<ScriptCustomCollection>(thisP)
|
||||
));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Concat collection
|
||||
|
||||
// Iterator over a concatenated collection
|
||||
class ScriptConcatCollectionIterator : public ScriptIterator {
|
||||
public:
|
||||
ScriptConcatCollectionIterator(const ScriptValueP& itA, const ScriptValueP& itB)
|
||||
: itA(itA), itB(itB) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (itA) {
|
||||
ScriptValueP v = itA->next(key_out);
|
||||
if (v) return v;
|
||||
else itA = ScriptValueP();
|
||||
}
|
||||
// TODO: somehow fix up the keys
|
||||
return itB->next(key_out);
|
||||
}
|
||||
public:
|
||||
ScriptConcatCollectionIterator(const ScriptValueP& itA, const ScriptValueP& itB)
|
||||
: itA(itA), itB(itB) {}
|
||||
virtual ScriptValueP next(ScriptValueP* key_out) {
|
||||
if (itA) {
|
||||
ScriptValueP v = itA->next(key_out);
|
||||
if (v) return v;
|
||||
else itA = ScriptValueP();
|
||||
}
|
||||
// TODO: somehow fix up the keys
|
||||
return itB->next(key_out);
|
||||
}
|
||||
private:
|
||||
ScriptValueP itA, itB;
|
||||
ScriptValueP itA, itB;
|
||||
};
|
||||
|
||||
ScriptValueP ScriptConcatCollection::getMember(const String& name) const {
|
||||
ScriptValueP member = a->getMember(name);
|
||||
if (member->type() != SCRIPT_ERROR) return member;
|
||||
long index;
|
||||
int itemsInA = a->itemCount();
|
||||
if (name.ToLong(&index) && index - itemsInA >= 0 && index - itemsInA < b->itemCount()) {
|
||||
// adjust integer index
|
||||
return b->getMember(String() << (index - itemsInA));
|
||||
} else {
|
||||
return b->getMember(name);
|
||||
}
|
||||
ScriptValueP member = a->getMember(name);
|
||||
if (member->type() != SCRIPT_ERROR) return member;
|
||||
long index;
|
||||
int itemsInA = a->itemCount();
|
||||
if (name.ToLong(&index) && index - itemsInA >= 0 && index - itemsInA < b->itemCount()) {
|
||||
// adjust integer index
|
||||
return b->getMember(String() << (index - itemsInA));
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
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 intrusive(new ScriptConcatCollectionIterator(a->makeIterator(a), b->makeIterator(b)));
|
||||
return intrusive(new ScriptConcatCollectionIterator(a->makeIterator(a), b->makeIterator(b)));
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------- : Default arguments / closure
|
||||
|
||||
ScriptType ScriptClosure::type() const {
|
||||
return SCRIPT_FUNCTION;
|
||||
return SCRIPT_FUNCTION;
|
||||
}
|
||||
String ScriptClosure::typeName() const {
|
||||
return fun->typeName() + _(" closure");
|
||||
return fun->typeName() + _(" closure");
|
||||
}
|
||||
|
||||
void ScriptClosure::addBinding(Variable v, const ScriptValueP& value) {
|
||||
bindings.push_back(make_pair(v,value));
|
||||
bindings.push_back(make_pair(v,value));
|
||||
}
|
||||
ScriptValueP ScriptClosure::getBinding(Variable v) const {
|
||||
FOR_EACH_CONST(b, bindings) {
|
||||
if (b.first == v) return b.second;
|
||||
}
|
||||
return ScriptValueP();
|
||||
FOR_EACH_CONST(b, bindings) {
|
||||
if (b.first == v) return b.second;
|
||||
}
|
||||
return ScriptValueP();
|
||||
}
|
||||
|
||||
ScriptValueP ScriptClosure::simplify() {
|
||||
return fun->simplifyClosure(*this);
|
||||
return fun->simplifyClosure(*this);
|
||||
}
|
||||
|
||||
ScriptValueP ScriptClosure::do_eval(Context& ctx, bool openScope) const {
|
||||
scoped_ptr<LocalScope> scope(openScope ? new LocalScope(ctx) : nullptr);
|
||||
applyBindings(ctx);
|
||||
return fun->eval(ctx, openScope);
|
||||
scoped_ptr<LocalScope> scope(openScope ? new LocalScope(ctx) : nullptr);
|
||||
applyBindings(ctx);
|
||||
return fun->eval(ctx, openScope);
|
||||
}
|
||||
ScriptValueP ScriptClosure::dependencies(Context& ctx, const Dependency& dep) const {
|
||||
LocalScope scope(ctx);
|
||||
applyBindings(ctx);
|
||||
return fun->dependencies(ctx, dep);
|
||||
LocalScope scope(ctx);
|
||||
applyBindings(ctx);
|
||||
return fun->dependencies(ctx, dep);
|
||||
}
|
||||
void ScriptClosure::applyBindings(Context& ctx) const {
|
||||
FOR_EACH_CONST(b, bindings) {
|
||||
if (ctx.getVariableScope(b.first) != 0) {
|
||||
// variables passed as arguments (i.e. in scope 0) override these default bindings
|
||||
ctx.setVariable(b.first, b.second);
|
||||
}
|
||||
}
|
||||
FOR_EACH_CONST(b, bindings) {
|
||||
if (ctx.getVariableScope(b.first) != 0) {
|
||||
// variables passed as arguments (i.e. in scope 0) override these default bindings
|
||||
ctx.setVariable(b.first, b.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ScriptType ScriptRule::type() const { return SCRIPT_FUNCTION; }
|
||||
String ScriptRule::typeName() const { return fun->typeName() + _(" rule"); }
|
||||
ScriptValueP ScriptRule::do_eval(Context& ctx, bool openScope) const {
|
||||
return ctx.makeClosure(fun);
|
||||
return ctx.makeClosure(fun);
|
||||
}
|
||||
|
||||
+82
-82
@@ -21,107 +21,107 @@ DECLARE_POINTER_TYPE(GeneratedImage);
|
||||
DECLARE_POINTER_TYPE(ScriptValue);
|
||||
|
||||
enum ScriptType
|
||||
{ SCRIPT_NIL
|
||||
, SCRIPT_INT
|
||||
, SCRIPT_BOOL
|
||||
, SCRIPT_DOUBLE
|
||||
, SCRIPT_STRING
|
||||
, SCRIPT_COLOR
|
||||
, SCRIPT_IMAGE
|
||||
, SCRIPT_FUNCTION
|
||||
, SCRIPT_OBJECT // Only ScriptObject
|
||||
, SCRIPT_COLLECTION
|
||||
, SCRIPT_REGEX
|
||||
, SCRIPT_DATETIME
|
||||
, SCRIPT_ITERATOR
|
||||
, SCRIPT_DUMMY
|
||||
, SCRIPT_ERROR
|
||||
{ SCRIPT_NIL
|
||||
, SCRIPT_INT
|
||||
, SCRIPT_BOOL
|
||||
, SCRIPT_DOUBLE
|
||||
, SCRIPT_STRING
|
||||
, SCRIPT_COLOR
|
||||
, SCRIPT_IMAGE
|
||||
, SCRIPT_FUNCTION
|
||||
, SCRIPT_OBJECT // Only ScriptObject
|
||||
, SCRIPT_COLLECTION
|
||||
, SCRIPT_REGEX
|
||||
, SCRIPT_DATETIME
|
||||
, SCRIPT_ITERATOR
|
||||
, SCRIPT_DUMMY
|
||||
, SCRIPT_ERROR
|
||||
};
|
||||
|
||||
enum CompareWhat
|
||||
{ COMPARE_NO
|
||||
, COMPARE_AS_STRING
|
||||
, COMPARE_AS_POINTER
|
||||
{ COMPARE_NO
|
||||
, COMPARE_AS_STRING
|
||||
, COMPARE_AS_POINTER
|
||||
};
|
||||
|
||||
/// A value that can be handled by the scripting engine.
|
||||
/// Actual values are derived types
|
||||
class ScriptValue : public IntrusivePtrBaseWithDelete {
|
||||
public:
|
||||
virtual ~ScriptValue() {}
|
||||
virtual ~ScriptValue() {}
|
||||
|
||||
/// Information on the type of this value
|
||||
virtual ScriptType type() const = 0;
|
||||
/// Name of the type of value
|
||||
virtual String typeName() const = 0;
|
||||
/// How to compare this object?
|
||||
/** Returns 1 if the pointer should be used, 0 otherwise */
|
||||
virtual CompareWhat compareAs(String& compare_str, void const*& compare_ptr) const;
|
||||
/// Information on the type of this value
|
||||
virtual ScriptType type() const = 0;
|
||||
/// Name of the type of value
|
||||
virtual String typeName() const = 0;
|
||||
/// How to compare this object?
|
||||
/** Returns 1 if the pointer should be used, 0 otherwise */
|
||||
virtual CompareWhat compareAs(String& compare_str, void const*& compare_ptr) const;
|
||||
|
||||
/// Convert this value to a string
|
||||
virtual operator String() const;
|
||||
/// Convert this value to a double
|
||||
virtual operator double() const;
|
||||
/// Convert this value to an integer
|
||||
virtual operator int() const;
|
||||
/// Convert this value to a boolean
|
||||
virtual operator bool() const;
|
||||
/// Convert this value to a color
|
||||
virtual operator AColor() const;
|
||||
/// Convert this value to a wxDateTime
|
||||
virtual operator wxDateTime() const;
|
||||
/// Convert this value to a string
|
||||
virtual operator String() const;
|
||||
/// Convert this value to a double
|
||||
virtual operator double() const;
|
||||
/// Convert this value to an integer
|
||||
virtual operator int() const;
|
||||
/// Convert this value to a boolean
|
||||
virtual operator bool() const;
|
||||
/// Convert this value to a color
|
||||
virtual operator AColor() const;
|
||||
/// Convert this value to a wxDateTime
|
||||
virtual operator wxDateTime() const;
|
||||
|
||||
/// Script code to generate this value
|
||||
virtual String toCode() 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. */
|
||||
inline String toString() const { return *this; }
|
||||
/// Explicit overload to convert to a wxDateTime
|
||||
/** Overload resolution is sometimes confused by other conversions */
|
||||
inline wxDateTime toDateTime() const { return *this; }
|
||||
/// Convert this value to an image
|
||||
virtual GeneratedImageP toImage(const ScriptValueP& thisP) const;
|
||||
/// Explicit overload to convert to a string
|
||||
/** This is sometimes necessary, because wxString has an int constructor,
|
||||
* which confuses gcc. */
|
||||
inline String toString() const { return *this; }
|
||||
/// Explicit overload to convert to a wxDateTime
|
||||
/** Overload resolution is sometimes confused by other conversions */
|
||||
inline wxDateTime toDateTime() const { return *this; }
|
||||
/// Convert this value to an image
|
||||
virtual GeneratedImageP toImage(const ScriptValueP& thisP) const;
|
||||
|
||||
/// Get a member variable from this value
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
/// Get a member variable from this value
|
||||
virtual ScriptValueP getMember(const String& name) const;
|
||||
|
||||
/// Signal that a script depends on this value itself
|
||||
virtual void dependencyThis(const Dependency& dep);
|
||||
/// Signal that a script depends on a member of this value
|
||||
/** It is the abstract version of getMember*/
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency&) const;
|
||||
/// Signal that a script depends on a member of container, with the name of this
|
||||
/** This function allows for a kind of visitor pattern over dependencyMember */
|
||||
virtual ScriptValueP dependencyName(const ScriptValue& container, const Dependency&) const;
|
||||
/// Signal that a script depends on this value itself
|
||||
virtual void dependencyThis(const Dependency& dep);
|
||||
/// Signal that a script depends on a member of this value
|
||||
/** It is the abstract version of getMember*/
|
||||
virtual ScriptValueP dependencyMember(const String& name, const Dependency&) const;
|
||||
/// Signal that a script depends on a member of container, with the name of this
|
||||
/** This function allows for a kind of visitor pattern over dependencyMember */
|
||||
virtual ScriptValueP dependencyName(const ScriptValue& container, const Dependency&) const;
|
||||
|
||||
/// Evaluate this value (if it is a function)
|
||||
ScriptValueP eval(Context& ctx, bool openScope = true) const {
|
||||
return do_eval(ctx, openScope);
|
||||
}
|
||||
/// Mark the scripts that this function depends on
|
||||
/** Return value is an abstract version of the return value of eval */
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;
|
||||
/// Simplify/optimize a default argument closure of this function.
|
||||
/** Should return a simplification of the closure or null to keep the closure.
|
||||
* Alternatively, the closure may be modified in place.
|
||||
*/
|
||||
virtual ScriptValueP simplifyClosure(ScriptClosure&) const;
|
||||
/// Evaluate this value (if it is a function)
|
||||
ScriptValueP eval(Context& ctx, bool openScope = true) const {
|
||||
return do_eval(ctx, openScope);
|
||||
}
|
||||
/// Mark the scripts that this function depends on
|
||||
/** Return value is an abstract version of the return value of eval */
|
||||
virtual ScriptValueP dependencies(Context&, const Dependency&) const;
|
||||
/// Simplify/optimize a default argument closure of this function.
|
||||
/** Should return a simplification of the closure or null to keep the closure.
|
||||
* Alternatively, the closure may be modified in place.
|
||||
*/
|
||||
virtual ScriptValueP simplifyClosure(ScriptClosure&) const;
|
||||
|
||||
/// Return an iterator for the current collection, an iterator is a value that has next()
|
||||
/** thisP can be used to prevent destruction of the collection */
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const;
|
||||
/// Return the next item for this iterator, or ScriptValueP() if there is no such item
|
||||
/** If key_out != 0, then it will recieve the key of the item */
|
||||
virtual ScriptValueP next(ScriptValueP* key_out = nullptr);
|
||||
/// 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;
|
||||
/// Return an iterator for the current collection, an iterator is a value that has next()
|
||||
/** thisP can be used to prevent destruction of the collection */
|
||||
virtual ScriptValueP makeIterator(const ScriptValueP& thisP) const;
|
||||
/// Return the next item for this iterator, or ScriptValueP() if there is no such item
|
||||
/** If key_out != 0, then it will recieve the key of the item */
|
||||
virtual ScriptValueP next(ScriptValueP* key_out = nullptr);
|
||||
/// 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;
|
||||
|
||||
protected:
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
virtual ScriptValueP do_eval(Context& ctx, bool openScope) const;
|
||||
};
|
||||
|
||||
extern ScriptValueP script_nil; ///< The preallocated nil value
|
||||
|
||||
Reference in New Issue
Block a user