Change tabs to two spaces.

This commit is contained in:
Lymia Aluysia
2017-01-18 08:43:21 -06:00
parent d7f5f0dc3b
commit d2c635f739
329 changed files with 41307 additions and 41496 deletions
+474 -474
View File
File diff suppressed because it is too large Load Diff
+99 -99
View File
@@ -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
View File
@@ -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
View File
@@ -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
File diff suppressed because it is too large Load Diff
+30 -30
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 += _("&lt;");
} else if (c == _('>')) { // escape >
ret += _("&gt;");
} else if (c == _('&')) { // escape &
ret += _("&amp;");
} else if (c == _('\'')) { // escape '
ret += _("&#39;");
} else if (c == _('\"')) { // escape "
ret += _("&quot;");
} 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 += _("&lt;");
} else if (c == _('>')) { // escape >
ret += _("&gt;");
} else if (c == _('&')) { // escape &
ret += _("&amp;");
} else if (c == _('\'')) { // escape '
ret += _("&#39;");
} else if (c == _('\"')) { // escape "
ret += _("&quot;");
} 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 += _("&lt;");
} else if (c == _('&')) { // escape &
ret += _("&amp;");
} 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 += _("&lt;");
} else if (c == _('&')) { // escape &
ret += _("&amp;");
} 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);
}
+8 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+66 -66
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
}
+64 -64
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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