mirror of
https://github.com/amyinspace/MagicSetEditor2.git
synced 2026-06-10 04:57:00 -04:00
Added scripting support; not yet integrated with the rest of the app.
git-svn-id: svn://svn.code.sf.net/p/magicseteditor/code/trunk@13 0fc631ac-6414-0410-93d0-97cfa31319b6
This commit is contained in:
@@ -0,0 +1,291 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/context.hpp>
|
||||||
|
#include <util/error.hpp>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Context
|
||||||
|
|
||||||
|
Context::Context()
|
||||||
|
: level(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Evaluate
|
||||||
|
|
||||||
|
// Perform a unary simple instruction, store the result in a (not in *a)
|
||||||
|
void instrUnary (UnaryInstructionType i, ScriptValueP& a);
|
||||||
|
|
||||||
|
// Perform a binary simple instruction, store the result in a (not in *a)
|
||||||
|
void instrBinary (BinaryInstructionType i, ScriptValueP& a, const ScriptValueP& b);
|
||||||
|
|
||||||
|
// Perform a ternary simple instruction, store the result in a (not in *a)
|
||||||
|
void instrTernary(TernaryInstructionType i, ScriptValueP& a, const ScriptValueP& b, const ScriptValueP& c);
|
||||||
|
|
||||||
|
|
||||||
|
ScriptValueP Context::eval(const Script& script, bool useScope) {
|
||||||
|
size_t stack_size = stack.size();
|
||||||
|
size_t scope = useScope ? openScope() : 0;
|
||||||
|
try {
|
||||||
|
// Instruction pointer
|
||||||
|
const Instruction* instr = &script.instructions[0];
|
||||||
|
|
||||||
|
// Loop until we are done
|
||||||
|
while (true) {
|
||||||
|
assert(instr < &*script.instructions.end());
|
||||||
|
// debug
|
||||||
|
// cout << script.dumpInstr(instr - &script.instructions[0], *instr) << endl;
|
||||||
|
// Evaluate the current instruction
|
||||||
|
Instruction i = *instr++;
|
||||||
|
switch (i.instr) {
|
||||||
|
case I_NOP: break;
|
||||||
|
// Push a constant
|
||||||
|
case I_PUSH_CONST: {
|
||||||
|
stack.push_back(script.constants[i.data]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Pop top value
|
||||||
|
case I_POP: {
|
||||||
|
stack.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Jump
|
||||||
|
case I_JUMP: {
|
||||||
|
instr = &script.instructions[i.data];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Conditional jump
|
||||||
|
case I_JUMP_IF_NOT: {
|
||||||
|
int condition = *stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
if (!condition) {
|
||||||
|
instr = &script.instructions[i.data];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a variable
|
||||||
|
case I_GET_VAR: {
|
||||||
|
ScriptValueP value = variables[i.data].value;
|
||||||
|
if (!value) throw ScriptError(_("Variable not set: ") + variableToString(i.data));
|
||||||
|
stack.push_back(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Set a variable
|
||||||
|
case I_SET_VAR: {
|
||||||
|
setVariable(i.data, stack.back());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an object member
|
||||||
|
case I_MEMBER_C: {
|
||||||
|
stack.back() = stack.back()->getMember(*script.constants[i.data]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Loop over a container, push next value or jump
|
||||||
|
case I_LOOP: {
|
||||||
|
ScriptValueP& it = stack[stack.size() - 2]; // second element of stack
|
||||||
|
assert(dynamic_pointer_cast<ScriptIterator>(it)); // top of stack must be an iterator
|
||||||
|
ScriptValueP val = static_pointer_cast<ScriptIterator>(it)->next();
|
||||||
|
if (val) {
|
||||||
|
stack.push_back(val);
|
||||||
|
} else {
|
||||||
|
stack.erase(stack.end() - 2); // remove iterator
|
||||||
|
instr = &script.instructions[i.data];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function call
|
||||||
|
case I_CALL: {
|
||||||
|
// new scope
|
||||||
|
size_t scope = openScope();
|
||||||
|
// prepare arguments
|
||||||
|
for (unsigned int j = 0 ; j < i.data ; ++j) {
|
||||||
|
setVariable(instr[i.data - j - 1].data, stack.back());
|
||||||
|
stack.pop_back();
|
||||||
|
}
|
||||||
|
instr += i.data; // skip arguments
|
||||||
|
// get function and call
|
||||||
|
stack.back() = stack.back()->eval(*this);
|
||||||
|
// restore scope
|
||||||
|
closeScope(scope);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Function return
|
||||||
|
case I_RET: {
|
||||||
|
// restore shadowed variables
|
||||||
|
if (useScope) closeScope(scope);
|
||||||
|
// return top of stack
|
||||||
|
ScriptValueP result = stack.back();
|
||||||
|
stack.pop_back();
|
||||||
|
assert(stack.size() == stack_size); // we end up with the same stack
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple instruction: unary
|
||||||
|
case I_UNARY: {
|
||||||
|
instrUnary(i.instr1, stack.back());
|
||||||
|
// cout << "\t\t-> " << (String)*stack.back() << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Simple instruction: binary
|
||||||
|
case I_BINARY: {
|
||||||
|
ScriptValueP b = stack.back(); stack.pop_back();
|
||||||
|
ScriptValueP& a = stack.back();
|
||||||
|
instrBinary(i.instr2, a, b);
|
||||||
|
// cout << "\t\t-> " << (String)*stack.back() << endl;
|
||||||
|
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();
|
||||||
|
instrTernary(i.instr3, a, b, c);
|
||||||
|
// cout << "\t\t-> " << (String)*stack.back() << endl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (...) {
|
||||||
|
// cleanup after an exception
|
||||||
|
if (scope) closeScope(scope); // restore scope
|
||||||
|
stack.resize(stack_size); // restore stack
|
||||||
|
throw; // rethrow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Context::setVariable(const String& name, const ScriptValueP& value) {
|
||||||
|
setVariable(stringToVariable(name), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Context::setVariable(int name, const ScriptValueP& value) {
|
||||||
|
Variable& var = variables[name];
|
||||||
|
if (var.value && var.level < level) {
|
||||||
|
// keep shadow copy
|
||||||
|
Binding bind = {name, var};
|
||||||
|
shadowed.push_back(bind);
|
||||||
|
}
|
||||||
|
var.level = level;
|
||||||
|
var.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptValueP Context::getVariable(const String& name) {
|
||||||
|
ScriptValueP value = variables[stringToVariable(name)].value;
|
||||||
|
if (!value) throw ScriptError(_("Variable not set: ") + name);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
ScriptValueP Context::getVariableOrNil(const String& name) {
|
||||||
|
return variables[stringToVariable(name)].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
size_t Context::openScope() {
|
||||||
|
level += 1;
|
||||||
|
return shadowed.size();
|
||||||
|
}
|
||||||
|
void Context::closeScope(size_t scope) {
|
||||||
|
assert(level > 0);
|
||||||
|
assert(scope <= shadowed.size());
|
||||||
|
level -= 1;
|
||||||
|
// restore shadowed variables
|
||||||
|
while (shadowed.size() > scope) {
|
||||||
|
variables[shadowed.back().variable] = shadowed.back().value;
|
||||||
|
shadowed.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Simple instructions : unary
|
||||||
|
|
||||||
|
void instrUnary (UnaryInstructionType i, ScriptValueP& a) {
|
||||||
|
switch (i) {
|
||||||
|
case I_ITERATOR_C:
|
||||||
|
a = a->makeIterator();
|
||||||
|
break;
|
||||||
|
case I_NEGATE:
|
||||||
|
a = toScript(-(int)*a);
|
||||||
|
break;
|
||||||
|
case I_NOT:
|
||||||
|
a = toScript(!(int)*a);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Simple instructions : binary
|
||||||
|
|
||||||
|
// operator on ints
|
||||||
|
#define OPERATOR_I(OP) \
|
||||||
|
a = toScript((int)*a OP (int)*b); \
|
||||||
|
break
|
||||||
|
|
||||||
|
// operator on doubles or ints
|
||||||
|
#define OPERATOR_DI(OP) \
|
||||||
|
if (at == SCRIPT_DOUBLE || bt == SCRIPT_DOUBLE) { \
|
||||||
|
a = toScript((double)*a OP (double)*b); \
|
||||||
|
} else { \
|
||||||
|
a = toScript((int)*a OP (int)*b); \
|
||||||
|
} \
|
||||||
|
break
|
||||||
|
|
||||||
|
// operator on strings or doubles or ints
|
||||||
|
#define OPERATOR_SDI(OP) \
|
||||||
|
if (at == SCRIPT_STRING || bt == SCRIPT_STRING) { \
|
||||||
|
a = toScript((String)*a OP (String)*b); \
|
||||||
|
} else if (at == SCRIPT_DOUBLE || bt == SCRIPT_DOUBLE) { \
|
||||||
|
a = toScript((double)*a OP (double)*b); \
|
||||||
|
} else { \
|
||||||
|
a = toScript((int)*a OP (int)*b); \
|
||||||
|
} \
|
||||||
|
break
|
||||||
|
|
||||||
|
void instrBinary (BinaryInstructionType i, ScriptValueP& a, const ScriptValueP& b) {
|
||||||
|
ScriptType at = a->type(), bt = b->type();
|
||||||
|
switch (i) {
|
||||||
|
case I_MEMBER:
|
||||||
|
a = a->getMember(*b);
|
||||||
|
break;
|
||||||
|
case I_ITERATOR_R:
|
||||||
|
a = rangeIterator(*a, *b);
|
||||||
|
break;
|
||||||
|
case I_ADD: // add is quite overloaded
|
||||||
|
if (at == SCRIPT_NIL) {
|
||||||
|
a = b;
|
||||||
|
} else if (bt == SCRIPT_NIL) {
|
||||||
|
// a = a;
|
||||||
|
//} else if (a->likesFunction() && b->likesFunction()) {
|
||||||
|
// a = compose(a, b);
|
||||||
|
} else if (at == SCRIPT_STRING || bt == SCRIPT_STRING) {
|
||||||
|
a = toScript((String)*a + (String)*b);
|
||||||
|
} else if (at == SCRIPT_DOUBLE || bt == SCRIPT_DOUBLE) {
|
||||||
|
a = toScript((double)*a + (double)*b);
|
||||||
|
} else {
|
||||||
|
a = toScript((int)*a + (int)*b);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I_SUB: OPERATOR_DI(-);
|
||||||
|
case I_MUL: OPERATOR_DI(*);
|
||||||
|
case I_DIV: OPERATOR_DI(/);
|
||||||
|
case I_MOD: // fmod
|
||||||
|
case I_AND: OPERATOR_I(&&);
|
||||||
|
case I_OR: OPERATOR_I(||);
|
||||||
|
case I_EQ: OPERATOR_SDI(==);
|
||||||
|
case I_NEQ: OPERATOR_SDI(!=);
|
||||||
|
case I_LT: OPERATOR_DI(<);
|
||||||
|
case I_GT: OPERATOR_DI(>);
|
||||||
|
case I_LE: OPERATOR_DI(<=);
|
||||||
|
case I_GE: OPERATOR_DI(>=);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Simple instructions : ternary
|
||||||
|
|
||||||
|
void instrTernary(TernaryInstructionType i, ScriptValueP& a, const ScriptValueP& b, const ScriptValueP& c) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_SCRIPT_CONTEXT
|
||||||
|
#define HEADER_SCRIPT_CONTEXT
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/script.hpp>
|
||||||
|
|
||||||
|
class Dependency;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : VectorIntMap
|
||||||
|
|
||||||
|
/// A map like data structure that stores the elements in a vector.
|
||||||
|
/** K should be an integer type, the keys should be dense. */
|
||||||
|
template <typename K, typename V>
|
||||||
|
class VectorIntMap {
|
||||||
|
public:
|
||||||
|
inline V& operator [] (K key) {
|
||||||
|
if (values.size() <= key) {
|
||||||
|
values.resize(key + 1);
|
||||||
|
}
|
||||||
|
return values[key];
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
vector<V> values;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Context
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Get the value of a variable, throws if it not set
|
||||||
|
ScriptValueP getVariable(const String& name);
|
||||||
|
/// Get the value of a variable, returns nil if it is not set
|
||||||
|
ScriptValueP getVariableOrNil(const String& name);
|
||||||
|
|
||||||
|
public:// public for FOR_EACH
|
||||||
|
/// Record of a variable
|
||||||
|
struct Variable {
|
||||||
|
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 {
|
||||||
|
int variable; ///< Name of the overwritten variable.
|
||||||
|
Variable value; ///< Old value of that variable.
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
/// Variables, indexed by integer naem (using stringToVariable)
|
||||||
|
VectorIntMap<unsigned int, Variable> variables;
|
||||||
|
/// Shadowed variable bindings
|
||||||
|
vector<Binding> shadowed;
|
||||||
|
/// Number of scopes opened
|
||||||
|
unsigned int level;
|
||||||
|
/// Stack of values
|
||||||
|
vector<ScriptValueP> stack;
|
||||||
|
|
||||||
|
// utility types for dependency analysis
|
||||||
|
struct Jump;
|
||||||
|
struct JumpOrder;
|
||||||
|
|
||||||
|
/// Set a variable to a new value (in the current scope)
|
||||||
|
void setVariable(int name, const ScriptValueP& value);
|
||||||
|
/// 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);
|
||||||
|
/// 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/context.hpp>
|
||||||
|
#include <util/error.hpp>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
DECLARE_TYPEOF_COLLECTION(ScriptValueP);
|
||||||
|
DECLARE_TYPEOF_COLLECTION(Context::Binding);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Dummy values
|
||||||
|
|
||||||
|
// A dummy type used during dependency analysis,
|
||||||
|
// 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() { return ScriptValueP(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP dependencyDummy(new DependencyDummy);
|
||||||
|
|
||||||
|
ScriptValueP unified(const ScriptValueP& a, const ScriptValueP& b);
|
||||||
|
|
||||||
|
// A script value that is a 'union' of two values.
|
||||||
|
/* During actual execution the value could be either a *or* b,
|
||||||
|
* So it has the dependency characteristics of both.
|
||||||
|
*/
|
||||||
|
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() const {
|
||||||
|
return unified(a->makeIterator(), b->makeIterator());
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
ScriptValueP a, b;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Unify two values from different execution paths
|
||||||
|
void unify(ScriptValueP& a, const ScriptValueP& b) {
|
||||||
|
if (a != b) a = new_intrusive2<DependencyUnion>(a,b);
|
||||||
|
}
|
||||||
|
// Unify two values from different execution paths
|
||||||
|
ScriptValueP unified(const ScriptValueP& a, const ScriptValueP& b) {
|
||||||
|
if (a == b) return a;
|
||||||
|
else return new_intrusive2<DependencyUnion>(a,b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : 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
|
||||||
|
};
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : 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
|
||||||
|
// Jumps are tricky:
|
||||||
|
// - I_JUMP: Just follow them, but see below
|
||||||
|
// - I_JUMP_IF_NOT: We don't know the value of the condition, 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'. After the if statement
|
||||||
|
// the net stack effect is +1, the top element will then be a DependencyUnion object.
|
||||||
|
// To detect the joining of the branches we look for I_JUMPs, the non jumping branch will have
|
||||||
|
// a I_JUMP at the end, when we encounter it we start evaluating the other if branch.
|
||||||
|
// - 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.
|
||||||
|
// Variable assignments are performed as normall.
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
assert(instr < &*script.instructions.end());
|
||||||
|
// 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) {
|
||||||
|
unify(variables[v.variable].value, v.value.value);
|
||||||
|
}
|
||||||
|
delete j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
// Pop top value (as normal)
|
||||||
|
case I_POP: {
|
||||||
|
stack.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Jump
|
||||||
|
case I_JUMP: {
|
||||||
|
if (&script.instructions[i.data] >= instr) {
|
||||||
|
// forward jump
|
||||||
|
// create jump record
|
||||||
|
Jump* jump = new Jump;
|
||||||
|
jump->target = &script.instructions[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: {
|
||||||
|
stack.pop_back(); // condition
|
||||||
|
// create jump record
|
||||||
|
Jump* jump = new Jump;
|
||||||
|
jump->target = &script.instructions[i.data];
|
||||||
|
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
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an object member (almost as normal)
|
||||||
|
case I_MEMBER_C: {
|
||||||
|
String name = *script.constants[i.data];
|
||||||
|
stack.back()->signalDependent(*this, dep, name); // dependency on member
|
||||||
|
stack.back() = stack.back()->getMember(name);
|
||||||
|
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
|
||||||
|
assert(dynamic_pointer_cast<ScriptIterator>(it)); // top of stack must be an iterator
|
||||||
|
ScriptValueP val = static_pointer_cast<ScriptIterator>(it)->next();
|
||||||
|
if (val) {
|
||||||
|
it = dependencyDummy; // invalidate iterator, so we loop only once
|
||||||
|
stack.push_back(val);
|
||||||
|
} else {
|
||||||
|
stack.erase(stack.end() - 2); // remove iterator
|
||||||
|
instr = &script.instructions[i.data];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function call (as normal)
|
||||||
|
case I_CALL: {
|
||||||
|
// new scope
|
||||||
|
size_t scope = openScope();
|
||||||
|
// prepare arguments
|
||||||
|
for (unsigned int j = 0 ; j < i.data ; ++j) {
|
||||||
|
setVariable(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);
|
||||||
|
// restore scope
|
||||||
|
closeScope(scope);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Function return (as normal)
|
||||||
|
case I_RET: {
|
||||||
|
closeScope(scope);
|
||||||
|
// return top of stack
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a variable (almost as normal)
|
||||||
|
case I_GET_VAR: {
|
||||||
|
ScriptValueP value = variables[i.data].value;
|
||||||
|
if (!value) value = scriptNil; // no errors here
|
||||||
|
stack.push_back(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Set a variable (as normal)
|
||||||
|
case I_SET_VAR: {
|
||||||
|
setVariable(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(); // as normal
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
a = dependencyDummy;
|
||||||
|
}
|
||||||
|
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: {
|
||||||
|
String name = *b;
|
||||||
|
a->signalDependent(*this, dep, name); // dependency on member
|
||||||
|
a = a->getMember(name);
|
||||||
|
break;
|
||||||
|
} case I_ADD:
|
||||||
|
unify(a, b); // may be function composition
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
a = dependencyDummy;
|
||||||
|
}
|
||||||
|
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 = dependencyDummy;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Context::resetBindings(size_t scope) {
|
||||||
|
// same as closeScope()
|
||||||
|
while (shadowed.size() > scope) {
|
||||||
|
variables[shadowed.back().variable] = shadowed.back().value;
|
||||||
|
shadowed.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/script.hpp>
|
||||||
|
#include <script/parser.hpp>
|
||||||
|
#include <util/error.hpp>
|
||||||
|
#include <stack>
|
||||||
|
#include <boost/lexical_cast.hpp> //%%
|
||||||
|
|
||||||
|
DECLARE_TYPEOF_COLLECTION(int);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Tokenizing : class
|
||||||
|
|
||||||
|
enum TokenType
|
||||||
|
{ TOK_NAME // abc
|
||||||
|
, TOK_INT // 123
|
||||||
|
, TOK_DOUBLE // 123.0
|
||||||
|
, TOK_STRING // "asdf"
|
||||||
|
, TOK_OPER // + - * / . ;
|
||||||
|
, TOK_LPAREN // ( { [
|
||||||
|
, TOK_RPAREN // ) } ]
|
||||||
|
, TOK_NEWLINE // newline
|
||||||
|
, TOK_EOF // end of input
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Tokens produced by the TokenIterator
|
||||||
|
struct Token {
|
||||||
|
TokenType type;
|
||||||
|
String value;
|
||||||
|
|
||||||
|
inline operator == (TokenType t) const { return type == t; }
|
||||||
|
inline operator != (TokenType t) const { return type != t; }
|
||||||
|
inline operator == (const String& s) const { return type != TOK_STRING && value == s; }
|
||||||
|
inline operator != (const String& s) const { return type == TOK_STRING || value != s; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Iterator over a string, one token at a time
|
||||||
|
class TokenIterator {
|
||||||
|
public:
|
||||||
|
TokenIterator(const String& str);
|
||||||
|
|
||||||
|
/// Peek at the next token, doesn't move to the one after that
|
||||||
|
/** Can peek further forward by using higher values of offset.
|
||||||
|
* offset=0 returns the last token that was read, or newline if putBack() was used.
|
||||||
|
*/
|
||||||
|
const Token& peek(size_t offset = 1);
|
||||||
|
/// Retrieve the next token
|
||||||
|
const Token& read();
|
||||||
|
/// Put back a token
|
||||||
|
/** Only one token can be correctly put back, the put back token will read as a newline.
|
||||||
|
*/
|
||||||
|
void putBack();
|
||||||
|
|
||||||
|
private:
|
||||||
|
String input;
|
||||||
|
size_t pos;
|
||||||
|
vector<Token> buffer; // buffer of unread tokens, front() = current
|
||||||
|
stack<bool> openBraces; // braces we entered, true if the brace was from a smart string escape
|
||||||
|
/// Read the next token, and add it to the buffer
|
||||||
|
void addToken();
|
||||||
|
/// Read the next token which is a string (after the opening ")
|
||||||
|
void addStringToken();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Characters
|
||||||
|
|
||||||
|
// TODO: isxx -> isXX!
|
||||||
|
|
||||||
|
bool isAlpha_(Char c) { return isAlpha(c) || c==_('_'); }
|
||||||
|
bool isAlnum_(Char c) { return isAlnum(c) || c==_('_'); }
|
||||||
|
bool isOper (Char c) { return c==_('+') || c==_('-') || c==_('*') || c==_('/') || c==_('!') || c==_('.') ||
|
||||||
|
c==_(':') || c==_('=') || c==_('<') || c==_('>') || c==_(';') || c==_(','); }
|
||||||
|
bool isLparen(Char c) { return c==_('(') || c==_('[') || c==_('{'); }
|
||||||
|
bool isRparen(Char c) { return c==_(')') || c==_(']') || c==_('}'); }
|
||||||
|
bool isDigitOrDot(Char c) { return isDigit(c) || c==_('.'); }
|
||||||
|
bool isLongOper(const String& s) { return s==_(":=") || s==_("==") || s==_("!=") || s==_("<=") || s==_(">="); }
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Tokenizing
|
||||||
|
|
||||||
|
TokenIterator::TokenIterator(const String& str)
|
||||||
|
: input(str)
|
||||||
|
, pos(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
const Token& TokenIterator::peek(size_t offset) {
|
||||||
|
// read the next token until we have enough
|
||||||
|
while (buffer.size() <= offset) {
|
||||||
|
addToken();
|
||||||
|
}
|
||||||
|
return buffer[offset];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Token& TokenIterator::read() {
|
||||||
|
if (!buffer.empty()) buffer.erase(buffer.begin());
|
||||||
|
return peek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TokenIterator::putBack() {
|
||||||
|
Token t = {TOK_NEWLINE, "\n"};
|
||||||
|
buffer.insert(buffer.begin(), t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TokenIterator::addToken() {
|
||||||
|
if (pos >= input.size()) {
|
||||||
|
// EOF
|
||||||
|
Token t = {TOK_EOF, "end of input"};
|
||||||
|
buffer.push_back(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// read a character from the input
|
||||||
|
Char c = input[pos++]; //% input.GetChar(pos++);
|
||||||
|
if (c == _('\n')) {
|
||||||
|
Token t = { TOK_NEWLINE, "newline" };
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if (isSpace(c)) {
|
||||||
|
// ignore
|
||||||
|
} else if (isAlpha(c)) {
|
||||||
|
// name
|
||||||
|
size_t start = pos - 1;
|
||||||
|
while (pos < input.size() && isalnum(input[pos])) ++pos; //%% isAlnum_(input.getChar(pos))) pos++;
|
||||||
|
Token t = {TOK_NAME, cannocialNameForm(input.substr(start, pos-start)) }; // convert name to cannocial form
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if (isDigit(c)) {
|
||||||
|
// number
|
||||||
|
size_t start = pos - 1;
|
||||||
|
while (pos < input.size() && isDigitOrDot(input[pos])) ++pos;
|
||||||
|
String num = input.substr(start, pos-start);
|
||||||
|
Token t = {
|
||||||
|
num.find_first_of('.') == String::npos ? TOK_INT : TOK_DOUBLE,
|
||||||
|
num
|
||||||
|
};
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if (isOper(c)) {
|
||||||
|
// operator
|
||||||
|
Token t = { TOK_OPER };
|
||||||
|
if (pos < input.size() && isLongOper(input.substr(pos - 1, 2))) {
|
||||||
|
// long operator
|
||||||
|
t.value = input.substr(pos - 1, 2);
|
||||||
|
pos += 1;
|
||||||
|
} else {
|
||||||
|
t.value = input.substr(pos - 1, 1);
|
||||||
|
}
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if (c==_('"')) {
|
||||||
|
// string
|
||||||
|
addStringToken();
|
||||||
|
} else if (c == _('}') && !openBraces.empty() && openBraces.top()) {
|
||||||
|
// closing smart string, resume to string parsing
|
||||||
|
// "a{e}b" --> "a" "{ e }" "b"
|
||||||
|
openBraces.pop();
|
||||||
|
Token t2 = {TOK_RPAREN, _("}\"")};
|
||||||
|
buffer.push_back(t2);
|
||||||
|
addStringToken();
|
||||||
|
} else if (isLparen(c)) {
|
||||||
|
// paranthesis/brace
|
||||||
|
openBraces.push(false);
|
||||||
|
Token t = { TOK_LPAREN, String(1,c) };
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if (isRparen(c)) {
|
||||||
|
// paranthesis/brace
|
||||||
|
if (!openBraces.empty()) openBraces.pop();
|
||||||
|
Token t = { TOK_RPAREN, String(1,c) };
|
||||||
|
buffer.push_back(t);
|
||||||
|
} else if(c==_('#')) {
|
||||||
|
// comment untill end of line
|
||||||
|
while (pos < input.size() && input[pos] != _('\n')) ++pos;
|
||||||
|
} else {
|
||||||
|
throw ScriptParseError(_("Unknown character in script: '") + String(1,c) + _("'"));
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TokenIterator::addStringToken() {
|
||||||
|
Token t = {TOK_STRING};
|
||||||
|
while (true) {
|
||||||
|
if (pos >= input.size()) throw ScriptParseError(_("Unexpected end of input in string constant"));
|
||||||
|
Char c = input[pos++]; //% input.GetChar(pos++);
|
||||||
|
// parse the string constant
|
||||||
|
if (c == _('"')) {
|
||||||
|
// end of string
|
||||||
|
buffer.push_back(t);
|
||||||
|
return;
|
||||||
|
} else if (c == _('\\')) {
|
||||||
|
// escape
|
||||||
|
if (pos >= input.size()) throw ScriptParseError(_("Unexpected end of input in string constant"));
|
||||||
|
c = input[pos++];
|
||||||
|
if (c == _('n')) t.value += _('\n');
|
||||||
|
if (c == _('<')) t.value += _('\1'); // escape for <
|
||||||
|
else t.value += c; // \ or { or "
|
||||||
|
} else if (c == _('{')) {
|
||||||
|
// smart string
|
||||||
|
// "a{e}b" --> "a" "{ e }" "b"
|
||||||
|
buffer.push_back(t);
|
||||||
|
openBraces.push(true);
|
||||||
|
Token t2 = {TOK_LPAREN, _("\"{")};
|
||||||
|
buffer.push_back(t2);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
t.value += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Parsing
|
||||||
|
|
||||||
|
/// Precedence levels for parsing, higher = tighter
|
||||||
|
enum Precedence
|
||||||
|
{ PREC_ALL
|
||||||
|
, PREC_NEWLINE // newline ;
|
||||||
|
, PREC_SEQ // ;
|
||||||
|
, PREC_SET // :=
|
||||||
|
, PREC_AND // and or
|
||||||
|
, PREC_CMP // == != < > <= >=
|
||||||
|
, PREC_ADD // + -
|
||||||
|
, PREC_MUL // * / mod
|
||||||
|
, PREC_UNARY // - not (unary operators)
|
||||||
|
, PREC_FUN // [] () . (function call, member)
|
||||||
|
, PREC_STRING // +{ }+ (smart string operators)
|
||||||
|
, PREC_NONE
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Parse an expression
|
||||||
|
/** @param input Read tokens from the input
|
||||||
|
* @param scrip Add resulting instructions to the script
|
||||||
|
* @param minPrec Minimum precedence level for operators
|
||||||
|
* NOTE: The net stack effect of an expression should be +1
|
||||||
|
*/
|
||||||
|
void parseExpr(TokenIterator& input, Script& script, Precedence minPrec);
|
||||||
|
|
||||||
|
/// Parse an expression, possibly with operators applied. Optionally adds an instruction at the end.
|
||||||
|
/** @param input Read tokens from the input
|
||||||
|
* @param scrip Add resulting instructions to the script
|
||||||
|
* @param minPrec Minimum precedence level for operators
|
||||||
|
* @param closeWith Add this instruction at the end
|
||||||
|
* @param closeWithData Data for the instruction at the end
|
||||||
|
* NOTE: The net stack effect of an expression should be +1
|
||||||
|
*/
|
||||||
|
void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith = I_NOP, int closeWithData = 0);
|
||||||
|
|
||||||
|
ScriptP parse(const String& s) {
|
||||||
|
TokenIterator input(s);
|
||||||
|
ScriptP script(new Script);
|
||||||
|
parseOper(input, *script, PREC_ALL, I_RET);
|
||||||
|
if (input.peek() != TOK_EOF) {
|
||||||
|
throw ScriptParseError(_("end of input"), input.peek().value);
|
||||||
|
} else {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expect a token, throws if it is not found
|
||||||
|
void expectToken(TokenIterator& input, const Char* expect) {
|
||||||
|
Token token = input.read();
|
||||||
|
while (token == TOK_NEWLINE) token = input.read(); // skip newlines
|
||||||
|
if (token != expect) {
|
||||||
|
throw ScriptParseError(expect, token.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseExpr(TokenIterator& input, Script& script, Precedence minPrec) {
|
||||||
|
// usually loop only once, unless we encounter newlines
|
||||||
|
while (true) {
|
||||||
|
const Token& token = input.read();
|
||||||
|
if (token == _("(")) {
|
||||||
|
// Parentheses = grouping for precedence of expressions
|
||||||
|
parseOper(input, script, PREC_ALL);
|
||||||
|
expectToken(input, _(")"));
|
||||||
|
} else if (token == _("{")) {
|
||||||
|
// {} = function block. Parse a new Script
|
||||||
|
intrusive_ptr<Script> subScript(new Script);
|
||||||
|
parseOper(input, *subScript, PREC_ALL, I_RET);
|
||||||
|
expectToken(input, _("}"));
|
||||||
|
script.addInstruction(I_PUSH_CONST, subScript);
|
||||||
|
} else if (minPrec <= PREC_UNARY && token == _("-")) {
|
||||||
|
parseOper(input, script, PREC_UNARY, I_UNARY, I_NEGATE); // unary negation
|
||||||
|
} else if (token == TOK_NAME) {
|
||||||
|
if (minPrec <= PREC_UNARY && token == _("not")) {
|
||||||
|
parseOper(input, script, PREC_UNARY, I_UNARY, I_NOT); // unary not
|
||||||
|
} else if (token == _("true")) {
|
||||||
|
script.addInstruction(I_PUSH_CONST, scriptTrue); // boolean constant : true
|
||||||
|
} else if (token == _("false")) {
|
||||||
|
script.addInstruction(I_PUSH_CONST, scriptFalse); // boolean constant : false
|
||||||
|
} else if (token == _("if")) {
|
||||||
|
// if AAA then BBB else CCC
|
||||||
|
unsigned int jmpElse, jmpEnd;
|
||||||
|
parseOper(input, script, PREC_SET); // AAA
|
||||||
|
jmpElse = script.getLabel(); // jmp_else:
|
||||||
|
script.addInstruction(I_JUMP_IF_NOT, 0xFFFF); // jnz lbl_else
|
||||||
|
expectToken(input, _("then")); // then
|
||||||
|
parseOper(input, script, PREC_SET); // BBB
|
||||||
|
jmpEnd = script.getLabel(); // jmp_end:
|
||||||
|
script.addInstruction(I_JUMP, 0xFFFF); // jump lbl_end
|
||||||
|
script.comeFrom(jmpElse); // lbl_else:
|
||||||
|
if (input.read() == _("else")) { // else
|
||||||
|
parseOper(input, script, PREC_SET); // CCC
|
||||||
|
} else {
|
||||||
|
script.addInstruction(I_PUSH_CONST, scriptNil);
|
||||||
|
}
|
||||||
|
script.comeFrom(jmpEnd); // lbl_end:
|
||||||
|
} else if (token == _("for")) {
|
||||||
|
unsigned int lblStart;
|
||||||
|
// the loop body should have a net stack effect of 0, but the entire expression of +1
|
||||||
|
// solution: add all results from the body, start with nil
|
||||||
|
if (input.peek() == _("each")) {
|
||||||
|
// for each AAA in BBB do CCC
|
||||||
|
input.read(); // each
|
||||||
|
Token name = input.read(); // AAA
|
||||||
|
if (name != TOK_NAME) throw ScriptParseError(_("name"), name.value);
|
||||||
|
expectToken(input, _("in")); // in
|
||||||
|
parseOper(input, script, PREC_SET); // BBB
|
||||||
|
script.addInstruction(I_UNARY, I_ITERATOR_C); // iterator_collection
|
||||||
|
script.addInstruction(I_PUSH_CONST, scriptNil); // push nil
|
||||||
|
lblStart = script.getLabel(); // lbl_start:
|
||||||
|
script.addInstruction(I_LOOP, 0xFFFF); // loop
|
||||||
|
expectToken(input, _("do")); // do
|
||||||
|
script.addInstruction(I_SET_VAR,
|
||||||
|
stringToVariable(name.value)); // set name
|
||||||
|
script.addInstruction(I_POP); // pop
|
||||||
|
parseOper(input, script, PREC_SET, I_BINARY, I_ADD);// CCC; add
|
||||||
|
script.addInstruction(I_JUMP, lblStart); // jump lbl_start
|
||||||
|
script.comeFrom(lblStart); // lbl_end:
|
||||||
|
} else {
|
||||||
|
// for AAA from BBB to CCC do DDD
|
||||||
|
Token name = input.read(); // AAA
|
||||||
|
expectToken(input, _("from")); // from
|
||||||
|
parseOper(input, script, PREC_SET); // BBB
|
||||||
|
expectToken(input, _("to")); // to
|
||||||
|
parseOper(input, script, PREC_SET); // CCC
|
||||||
|
script.addInstruction(I_BINARY, I_ITERATOR_R); // iterator_range
|
||||||
|
script.addInstruction(I_PUSH_CONST, scriptNil); // push nil
|
||||||
|
lblStart = script.getLabel(); // lbl_start:
|
||||||
|
script.addInstruction(I_LOOP, 0xFFFF); // loop
|
||||||
|
expectToken(input, _("do")); // do
|
||||||
|
script.addInstruction(I_SET_VAR,
|
||||||
|
stringToVariable(name.value)); // set name
|
||||||
|
script.addInstruction(I_POP); // pop
|
||||||
|
parseOper(input, script, PREC_SET, I_BINARY, I_ADD);// DDD; add
|
||||||
|
script.addInstruction(I_JUMP, lblStart); // jump lbl_start
|
||||||
|
script.comeFrom(lblStart); // lbl_end:
|
||||||
|
}
|
||||||
|
} else if (token == _("rgb")) {
|
||||||
|
// rgb(r, g, b)
|
||||||
|
expectToken(input, _("("));
|
||||||
|
parseOper(input, script, PREC_ALL); // r
|
||||||
|
expectToken(input, _(","));
|
||||||
|
parseOper(input, script, PREC_ALL); // g
|
||||||
|
expectToken(input, _(","));
|
||||||
|
parseOper(input, script, PREC_ALL); // b
|
||||||
|
expectToken(input, _(")"));
|
||||||
|
script.addInstruction(I_TERNARY, I_RGB);
|
||||||
|
} else {
|
||||||
|
// variable
|
||||||
|
unsigned int var = stringToVariable(token.value);
|
||||||
|
script.addInstruction(I_GET_VAR, var);
|
||||||
|
}
|
||||||
|
} else if (token == TOK_INT) {
|
||||||
|
long l;
|
||||||
|
l = lexical_cast<long>(token.value);
|
||||||
|
//token.value.toLong(l);
|
||||||
|
script.addInstruction(I_PUSH_CONST, toScript(l));
|
||||||
|
} else if (token == TOK_DOUBLE) {
|
||||||
|
double d;
|
||||||
|
d = lexical_cast<double>(token.value);
|
||||||
|
//token.value.toDouble(d);
|
||||||
|
script.addInstruction(I_PUSH_CONST, toScript(d));
|
||||||
|
} else if (token == TOK_STRING) {
|
||||||
|
script.addInstruction(I_PUSH_CONST, toScript(token.value));
|
||||||
|
} else if (token == TOK_NEWLINE) {
|
||||||
|
continue; // ignore
|
||||||
|
} else {
|
||||||
|
throw ScriptParseError(_("Unexpected token '") + token.value + _("'"));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseOper(TokenIterator& input, Script& script, Precedence minPrec, InstructionType closeWith, int closeWithData) {
|
||||||
|
parseExpr(input, script, minPrec); // first argument
|
||||||
|
bool newlines = false; // did we skip any newlines?
|
||||||
|
// read any operators after an expression
|
||||||
|
// EBNF: expr = expr | expr oper expr
|
||||||
|
// without left recursion: expr = expr (oper expr)*
|
||||||
|
while (true) {
|
||||||
|
const Token& token = input.read();
|
||||||
|
bool newlines2 = newlines;
|
||||||
|
newlines = false;
|
||||||
|
if (token == TOK_OPER || token == TOK_NAME) {
|
||||||
|
if (minPrec <= PREC_SEQ && token==_(";")) {
|
||||||
|
Token next = input.peek(1);
|
||||||
|
if (next == TOK_RPAREN || next == TOK_EOF) {
|
||||||
|
// allow ; at end of expression without errors
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
script.addInstruction(I_POP); // discard result of first expression
|
||||||
|
parseOper(input, script, PREC_SET);
|
||||||
|
} else if (minPrec <= PREC_SET && token==_(":=")) {
|
||||||
|
// We made a mistake, the part before the := should be a variable name,
|
||||||
|
// not an expression. Remove that instruction.
|
||||||
|
Instruction instr = script.getInstructions().back();
|
||||||
|
if (instr.instr != I_GET_VAR) {
|
||||||
|
throw ScriptParseError("Can only assign to variables");
|
||||||
|
} else {
|
||||||
|
script.getInstructions().pop_back();
|
||||||
|
parseOper(input, script, PREC_SET, I_SET_VAR, instr.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (minPrec <= PREC_AND && token==_("and")) parseOper(input, script, PREC_CMP, I_BINARY, I_AND);
|
||||||
|
else if (minPrec <= PREC_AND && token==_("or" )) parseOper(input, script, PREC_CMP, I_BINARY, I_OR);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_("=")) parseOper(input, script, PREC_ADD, I_BINARY, I_EQ);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_("==")) parseOper(input, script, PREC_ADD, I_BINARY, I_EQ);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_("!=")) parseOper(input, script, PREC_ADD, I_BINARY, I_NEQ);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_("<")) parseOper(input, script, PREC_ADD, I_BINARY, I_LT);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_(">")) parseOper(input, script, PREC_ADD, I_BINARY, I_GT);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_("<=")) parseOper(input, script, PREC_ADD, I_BINARY, I_LE);
|
||||||
|
else if (minPrec <= PREC_CMP && token==_(">=")) parseOper(input, script, PREC_ADD, I_BINARY, I_GE);
|
||||||
|
else if (minPrec <= PREC_ADD && token==_("+")) parseOper(input, script, PREC_MUL, I_BINARY, I_ADD);
|
||||||
|
else if (minPrec <= PREC_ADD && token==_("-")) parseOper(input, script, PREC_MUL, I_BINARY, I_SUB);
|
||||||
|
else if (minPrec <= PREC_MUL && token==_("*")) parseOper(input, script, PREC_UNARY, I_BINARY, I_MUL);
|
||||||
|
else if (minPrec <= PREC_MUL && token==_("/")) parseOper(input, script, PREC_UNARY, I_BINARY, I_DIV);
|
||||||
|
else if (minPrec <= PREC_MUL && token==_("mod")) parseOper(input, script, PREC_UNARY, I_BINARY, I_MOD);
|
||||||
|
else if (minPrec <= PREC_FUN && token==_(".")) { // get member by name
|
||||||
|
const Token& token = input.read();
|
||||||
|
if (token == TOK_NAME || token == TOK_INT || token == TOK_DOUBLE || token == TOK_STRING) {
|
||||||
|
script.addInstruction(I_MEMBER_C, token.value);
|
||||||
|
} else {
|
||||||
|
throw ScriptParseError(_("name"), input.peek().value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.putBack();
|
||||||
|
newlines = newlines2; // remember newlines
|
||||||
|
break; // unknown operator
|
||||||
|
}
|
||||||
|
} else if (token==TOK_LPAREN) {
|
||||||
|
if (minPrec <= PREC_FUN && token==_("(")) {
|
||||||
|
// function call, read arguments
|
||||||
|
vector<int> arguments;
|
||||||
|
Token t = input.peek();
|
||||||
|
while (t != _(")")) {
|
||||||
|
if (input.peek(2) == _(":")) {
|
||||||
|
// name: ...
|
||||||
|
arguments.push_back(stringToVariable(t.value));
|
||||||
|
input.read(); // skip the name
|
||||||
|
input.read(); // and the :
|
||||||
|
parseOper(input, script, PREC_SEQ);
|
||||||
|
} else {
|
||||||
|
// implicit "input" argument
|
||||||
|
arguments.push_back(stringToVariable(_("input")));
|
||||||
|
parseOper(input, script, PREC_SEQ);
|
||||||
|
}
|
||||||
|
t = input.peek();
|
||||||
|
if (t == _(",")) {
|
||||||
|
// Comma separating the arguments
|
||||||
|
input.read();
|
||||||
|
t = input.peek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
input.read(); // skip the )
|
||||||
|
// generate instruction
|
||||||
|
script.addInstruction(I_CALL, (unsigned int)arguments.size());
|
||||||
|
FOR_EACH(arg,arguments) {
|
||||||
|
script.addInstruction(I_NOP, arg);
|
||||||
|
}
|
||||||
|
} else if (minPrec <= PREC_FUN && token==_("[")) { // get member by expr
|
||||||
|
parseOper(input, script, PREC_ALL, I_BINARY, I_MEMBER);
|
||||||
|
expectToken(input, _("]"));
|
||||||
|
} else if (minPrec <= PREC_STRING && token==_("\"{")) {
|
||||||
|
// for smart strings: "x" {{ e }} "y"
|
||||||
|
parseOper(input, script, PREC_ALL, I_BINARY, I_ADD); // e
|
||||||
|
expectToken(input, _("}\""));
|
||||||
|
parseOper(input, script, PREC_NONE, I_BINARY, I_ADD); // y
|
||||||
|
} else {
|
||||||
|
input.putBack();
|
||||||
|
newlines = newlines2; // remember newlines
|
||||||
|
break; // unknown LPAREN, has to be {
|
||||||
|
}
|
||||||
|
} else if (token == TOK_NEWLINE) {
|
||||||
|
const Token& next = input.peek(1);
|
||||||
|
if (minPrec <= PREC_NEWLINE && (next == TOK_NAME || next == TOK_LPAREN)) {
|
||||||
|
// function as ;
|
||||||
|
script.addInstruction(I_POP);
|
||||||
|
parseOper(input, script, PREC_SET);
|
||||||
|
} else {
|
||||||
|
// skip newlines
|
||||||
|
newlines = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
input.putBack();
|
||||||
|
newlines = newlines2; // remember newlines
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newlines) {
|
||||||
|
// we accidentally ate a newline, restore it
|
||||||
|
input.putBack();
|
||||||
|
}
|
||||||
|
// add closing instruction
|
||||||
|
if (closeWith != I_NOP) {
|
||||||
|
script.addInstruction(closeWith, closeWithData);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_SCRIPT_PARSER
|
||||||
|
#define HEADER_SCRIPT_PARSER
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
#include <script/script.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Parser
|
||||||
|
|
||||||
|
// Parse a String to a Script
|
||||||
|
ScriptP parse(const String& s);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/script.hpp>
|
||||||
|
#include <script/context.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Variables
|
||||||
|
|
||||||
|
typedef map<string, unsigned int> Variables;
|
||||||
|
Variables variables;
|
||||||
|
DECLARE_TYPEOF(Variables);
|
||||||
|
|
||||||
|
/// Return a unique name for a variable to allow for faster loopups
|
||||||
|
unsigned int stringToVariable(const String& s) {
|
||||||
|
map<string, unsigned int>::iterator it = variables.find(s);
|
||||||
|
if (it == variables.end()) {
|
||||||
|
unsigned int v = (unsigned int)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 variableToString(unsigned int v) {
|
||||||
|
FOR_EACH(vi, variables) {
|
||||||
|
if (vi.second == v) return vi.first;
|
||||||
|
}
|
||||||
|
throw "Variable not found: " + lexical_cast<String>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Script
|
||||||
|
|
||||||
|
ScriptType Script::type() const {
|
||||||
|
return SCRIPT_SCRIPT_FUN;
|
||||||
|
}
|
||||||
|
String Script::typeName() const {
|
||||||
|
return "function";
|
||||||
|
}
|
||||||
|
ScriptValueP Script::eval(Context& ctx) const {
|
||||||
|
return ctx.eval(*this);
|
||||||
|
}
|
||||||
|
ScriptValueP Script::dependencies(Context& ctx, const Dependency& dep) const {
|
||||||
|
return ctx.dependencies(dep, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Script::addInstruction(InstructionType t) {
|
||||||
|
//if (t == I_MEMBER_V && !instructions.empty() && instructions.back().instr == I_PUSH_CONST) {
|
||||||
|
// // optimize: push x ; member_v --> member x
|
||||||
|
// instructions.back().instr = I_MEMBER;
|
||||||
|
//} else {
|
||||||
|
Instruction i = {t, 0};
|
||||||
|
instructions.push_back(i);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
void Script::addInstruction(InstructionType t, unsigned int d) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
void Script::addInstruction(InstructionType t, const String& s) {
|
||||||
|
constants.push_back(toScript(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_LOOP);
|
||||||
|
instructions.at(pos).data = (unsigned int)instructions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int Script::getLabel() const {
|
||||||
|
return (unsigned int)instructions.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
DECLARE_TYPEOF_COLLECTION(Instruction);
|
||||||
|
|
||||||
|
String Script::dumpScript() const {
|
||||||
|
String ret;
|
||||||
|
int pos = 0;
|
||||||
|
FOR_EACH_CONST(i, instructions) {
|
||||||
|
ret += dumpInstr(pos++, i) + "\n";
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Script::dumpInstr(unsigned int pos, Instruction i) const {
|
||||||
|
String ret = lexical_cast<String>(pos) + ":\t";
|
||||||
|
// instruction
|
||||||
|
switch (i.instr) {
|
||||||
|
case I_NOP: ret += "nop"; break;
|
||||||
|
case I_PUSH_CONST: ret += "push"; break;
|
||||||
|
case I_POP: ret += "pop"; break;
|
||||||
|
case I_JUMP: ret += "jump"; break;
|
||||||
|
case I_JUMP_IF_NOT: ret += "jnz"; 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_CALL: ret += "call"; break;
|
||||||
|
case I_RET: ret += "ret"; 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 += "*"; break;
|
||||||
|
case I_AND: ret += "and"; break;
|
||||||
|
case I_OR: ret += "or"; 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;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case I_TERNARY: ret += "ternary\t";
|
||||||
|
switch (i.instr3) {
|
||||||
|
case I_RGB: ret += "rgb"; break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// arg
|
||||||
|
switch (i.instr) {
|
||||||
|
case I_PUSH_CONST: case I_MEMBER_C: // const
|
||||||
|
ret += "\t" + (String)*constants[i.data];
|
||||||
|
ret += "\t(" + constants[i.data]->typeName();
|
||||||
|
ret += ", #" + lexical_cast<String>(i.data) + ")";
|
||||||
|
break;
|
||||||
|
case I_JUMP: case I_JUMP_IF_NOT: case I_LOOP: case I_CALL: // int
|
||||||
|
ret += "\t" + lexical_cast<String>(i.data);
|
||||||
|
break;
|
||||||
|
case I_GET_VAR: case I_SET_VAR: case I_NOP: // variable
|
||||||
|
ret += "\t" + variableToString(i.data) + "\t$" + lexical_cast<String>(i.data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_SCRIPT_SCRIPT
|
||||||
|
#define HEADER_SCRIPT_SCRIPT
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
#include <script/value.hpp>
|
||||||
|
|
||||||
|
DECLARE_POINTER_TYPE(Script);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Instructions
|
||||||
|
|
||||||
|
/// A type of instruction to be performed in a script
|
||||||
|
/** The reason for the negative values is that the compiler (at least MSVC) considers the bitfield signed
|
||||||
|
*/
|
||||||
|
enum InstructionType
|
||||||
|
// Basic
|
||||||
|
{ I_NOP = 0 ///< no operation, used as placeholder for extra data values
|
||||||
|
, I_PUSH_CONST = 1 ///< arg = value : push a constant onto the stack
|
||||||
|
, I_POP = 2 ///< pop the top value from the stack (used for ; operator)
|
||||||
|
, I_JUMP = 3 ///< arg = int : move the instruction pointer to the given position
|
||||||
|
, I_JUMP_IF_NOT = 4 ///< arg = int : move the instruction pointer if the top of the stack is false
|
||||||
|
// Variables
|
||||||
|
, I_GET_VAR = 5 ///< arg = int : find a variable, push its value onto the stack, it is an error if the variable is not found
|
||||||
|
, I_SET_VAR = 6 ///< arg = int : assign the top value from the stack to a variable (doesn't pop)
|
||||||
|
// Objects
|
||||||
|
, I_MEMBER_C = 7 ///< arg = name : finds a member of the top of the stack replaces the top of the stack with the member
|
||||||
|
, I_LOOP = -8 ///< arg = int : 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!
|
||||||
|
// Functions
|
||||||
|
, I_CALL = -7 ///< arg = int, int+ : 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_RET = -6 ///< return from the current function
|
||||||
|
// Simple instructions
|
||||||
|
, I_UNARY = -5 ///< pop 1 value, apply a function, push the result
|
||||||
|
, I_BINARY = -4 ///< pop 2 values, apply a function, push the result
|
||||||
|
, I_TERNARY = -3 ///< pop 3 values, apply a function, push the result
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
};
|
||||||
|
|
||||||
|
/// 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
|
||||||
|
// Arithmatic
|
||||||
|
, I_ADD ///< add
|
||||||
|
, I_SUB ///< subtract
|
||||||
|
, I_MUL ///< multiply
|
||||||
|
, I_DIV ///< divide
|
||||||
|
, I_MOD ///< modulus
|
||||||
|
// Logical
|
||||||
|
, I_AND ///< logical and
|
||||||
|
, I_OR ///< logical or
|
||||||
|
// Comparison
|
||||||
|
, I_EQ ///< operator ==
|
||||||
|
, I_NEQ ///< operator !=
|
||||||
|
, I_LT ///< operator <
|
||||||
|
, I_GT ///< operator >
|
||||||
|
, I_LE ///< operator <=
|
||||||
|
, I_GE ///< operator >=
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Types of ternary instructions (taking three arguments from the stack)
|
||||||
|
enum TernaryInstructionType
|
||||||
|
{ I_RGB ///< pop r,g,b, push a color value
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An instruction in a script, consists of the opcode and data
|
||||||
|
/** If the opcode is one of I_UNARY,I_BINARY,I_TERNARY,
|
||||||
|
* Then the instr? member gives the actual instruction to perform
|
||||||
|
*/
|
||||||
|
struct Instruction {
|
||||||
|
InstructionType instr : 4;
|
||||||
|
union {
|
||||||
|
unsigned int data : 28;
|
||||||
|
UnaryInstructionType instr1 : 28;
|
||||||
|
BinaryInstructionType instr2 : 28;
|
||||||
|
TernaryInstructionType instr3 : 28;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Variables
|
||||||
|
|
||||||
|
/// Return a unique name for a variable to allow for faster loopups
|
||||||
|
unsigned int stringToVariable(const String& s);
|
||||||
|
|
||||||
|
/// Get the name of a vaiable
|
||||||
|
/** Warning: this function is slow, it should only be used for error messages and such.
|
||||||
|
*/
|
||||||
|
String variableToString(unsigned int v);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Script
|
||||||
|
|
||||||
|
/// A script that can be executed
|
||||||
|
/** Represent a function that returns a single value.
|
||||||
|
* The script is itself a ScriptValue
|
||||||
|
*/
|
||||||
|
class Script : public ScriptValue {
|
||||||
|
public:
|
||||||
|
/// Set the script to be executed
|
||||||
|
//void setScript(const String& script);
|
||||||
|
|
||||||
|
virtual ScriptType type() const;
|
||||||
|
virtual String typeName() const;
|
||||||
|
virtual ScriptValueP eval(Context& ctx) const;
|
||||||
|
virtual ScriptValueP dependencies(Context& ctx, const Dependency&) const;
|
||||||
|
|
||||||
|
/// Add an instruction with no data
|
||||||
|
void 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).
|
||||||
|
* The position must be a label just before the instruction to be updated.
|
||||||
|
*/
|
||||||
|
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; }
|
||||||
|
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
friend class Context;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <script/value.hpp>
|
||||||
|
#include <util/error.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp> //%%
|
||||||
|
#include <boost/pool/singleton_pool.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : ScriptValue
|
||||||
|
// Base cases
|
||||||
|
|
||||||
|
ScriptValue::operator String() const { return "[[" + typeName() + "]]"; }
|
||||||
|
ScriptValue::operator int() const { throw ScriptError("Can't convert from "+typeName()+" to integer number"); }
|
||||||
|
ScriptValue::operator double() const { throw ScriptError("Can't convert from "+typeName()+" to real number" ); }
|
||||||
|
ScriptValue::operator Color() const { throw ScriptError("Can't convert from "+typeName()+" to color" ); }
|
||||||
|
ScriptValueP ScriptValue::eval(Context&) const
|
||||||
|
{ throw ScriptError("Can't convert from "+typeName()+" to function" ); }
|
||||||
|
ScriptValueP ScriptValue::getMember(const String& name) const
|
||||||
|
{ throw (typeName() + " has no member '" + name + "'"); }
|
||||||
|
ScriptValueP ScriptValue::next() { throw InternalError("Can't convert from "+typeName()+" to iterator"); }
|
||||||
|
ScriptValueP ScriptValue::makeIterator() const { throw ScriptError("Can't convert from "+typeName()+" to collection"); }
|
||||||
|
|
||||||
|
void ScriptValue::signalDependent(Context&, const Dependency&, const String& name) {}
|
||||||
|
ScriptValueP ScriptValue::dependencies( Context&, const Dependency&) const { return scriptNil; }
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Iterators
|
||||||
|
|
||||||
|
ScriptType ScriptIterator::type() const { return SCRIPT_OBJECT; }
|
||||||
|
String ScriptIterator::typeName() const { return "iterator"; }
|
||||||
|
|
||||||
|
// 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), end(end) {}
|
||||||
|
virtual ScriptValueP next() {
|
||||||
|
if (pos <= end) {
|
||||||
|
return toScript(pos++);
|
||||||
|
} else {
|
||||||
|
return ScriptValueP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int pos, end;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP rangeIterator(int start, int end) {
|
||||||
|
return new_intrusive2<ScriptRangeIterator>(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Integers
|
||||||
|
|
||||||
|
// Integer values
|
||||||
|
class ScriptInt : public ScriptValue {
|
||||||
|
public:
|
||||||
|
ScriptInt(int v) : value(v) {}
|
||||||
|
virtual ScriptType type() const { return SCRIPT_INT; }
|
||||||
|
virtual String typeName() const { return "integer number"; }
|
||||||
|
virtual operator String() const { return lexical_cast<String>(value); }
|
||||||
|
virtual operator double() const { return value; }
|
||||||
|
virtual operator int() const { return value; }
|
||||||
|
protected:
|
||||||
|
virtual void destroy() {
|
||||||
|
boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::free(this);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
int value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP toScript(int v) {
|
||||||
|
// return new_intrusive1<ScriptInt>(v);
|
||||||
|
return ScriptValueP(
|
||||||
|
new(boost::singleton_pool<ScriptValue, sizeof(ScriptInt)>::malloc())
|
||||||
|
ScriptInt(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// use integers to represent true/false
|
||||||
|
ScriptValueP scriptTrue = toScript((int)true);
|
||||||
|
ScriptValueP scriptFalse = toScript((int)false);
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Doubles
|
||||||
|
|
||||||
|
// Double values
|
||||||
|
class ScriptDouble : public ScriptValue {
|
||||||
|
public:
|
||||||
|
ScriptDouble(double v) : value(v) {}
|
||||||
|
virtual ScriptType type() const { return SCRIPT_DOUBLE; }
|
||||||
|
virtual String typeName() const { return "real number"; }
|
||||||
|
virtual operator String() const { return lexical_cast<String>(value); }
|
||||||
|
virtual operator double() const { return value; }
|
||||||
|
virtual operator int() const { return (int)value; }
|
||||||
|
private:
|
||||||
|
double value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP toScript(double v) {
|
||||||
|
return new_intrusive1<ScriptDouble>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : String type
|
||||||
|
|
||||||
|
// 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 "string"; }
|
||||||
|
virtual operator String() const { return value; }
|
||||||
|
virtual operator double() const { return lexical_cast<double>(value); }
|
||||||
|
virtual operator int() const { return lexical_cast<int>(value); }
|
||||||
|
private:
|
||||||
|
String value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP toScript(const String& v) {
|
||||||
|
return new_intrusive1<ScriptString>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Color
|
||||||
|
|
||||||
|
// Color values
|
||||||
|
class ScriptColor : public ScriptValue {
|
||||||
|
public:
|
||||||
|
ScriptColor(const Color& v) : value(v) {}
|
||||||
|
virtual ScriptType type() const { return SCRIPT_COLOR; }
|
||||||
|
virtual String typeName() const { return "color"; }
|
||||||
|
private:
|
||||||
|
Color value;
|
||||||
|
};
|
||||||
|
|
||||||
|
ScriptValueP toScript(const Color& v) {
|
||||||
|
return new_intrusive1<ScriptColor>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Nil type
|
||||||
|
|
||||||
|
// the nil object
|
||||||
|
class ScriptNil : public ScriptValue {
|
||||||
|
public:
|
||||||
|
virtual ScriptType type() const { return SCRIPT_NIL; }
|
||||||
|
virtual String typeName() const { return "nil"; }
|
||||||
|
virtual operator String() const { return ""; }
|
||||||
|
virtual operator double() const { return 0.0; }
|
||||||
|
virtual operator int() const { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The preallocated nil value
|
||||||
|
ScriptValueP scriptNil(new ScriptNil);
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_SCRIPT_VALUE
|
||||||
|
#define HEADER_SCRIPT_VALUE
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
#include <boost/intrusive_ptr.hpp>
|
||||||
|
#include <boost/lexical_cast.hpp> //%%
|
||||||
|
|
||||||
|
//#ifndef WINVER
|
||||||
|
//#define WINVER 0x0501
|
||||||
|
//#endif
|
||||||
|
//#include <winresrc.h>
|
||||||
|
//#include <windef.h>
|
||||||
|
//#include <winbase.h> // Interlocked*crement
|
||||||
|
#include <windows.h>
|
||||||
|
#define TokenType TokenType_
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
LONG __cdecl _InterlockedIncrement(LONG volatile *Addend);
|
||||||
|
LONG __cdecl _InterlockedDecrement(LONG volatile *Addend);
|
||||||
|
}
|
||||||
|
#pragma intrinsic (_InterlockedIncrement)
|
||||||
|
#define InterlockedIncrement _InterlockedIncrement
|
||||||
|
#pragma intrinsic (_InterlockedDecrement)
|
||||||
|
#define InterlockedDecrement _InterlockedDecrement
|
||||||
|
|
||||||
|
//long __declspec(dllimport) __stdcall InterlockedDecrement(long volatile* Addend);
|
||||||
|
//long __declspec(dllimport) __stdcall InterlockedIncrement(long volatile* Addend);
|
||||||
|
//#pragma intrinsic(_InterlockedIncrement)
|
||||||
|
//#pragma intrinsic( _InterlockedDecrement )
|
||||||
|
|
||||||
|
class Context;
|
||||||
|
class Dependency;
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : ScriptValue
|
||||||
|
|
||||||
|
//DECLARE_POINTER_TYPE(ScriptValue);
|
||||||
|
class ScriptValue;
|
||||||
|
typedef boost::intrusive_ptr<ScriptValue> ScriptValueP;
|
||||||
|
|
||||||
|
enum ScriptType
|
||||||
|
{ SCRIPT_NIL
|
||||||
|
, SCRIPT_INT
|
||||||
|
, SCRIPT_DOUBLE
|
||||||
|
, SCRIPT_STRING
|
||||||
|
, SCRIPT_COLOR
|
||||||
|
, SCRIPT_BUILDIN_FUN
|
||||||
|
, SCRIPT_SCRIPT_FUN
|
||||||
|
, SCRIPT_OBJECT
|
||||||
|
, SCRIPT_DUMMY
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A value that can be handled by the scripting engine.
|
||||||
|
/// Actual values are derived types
|
||||||
|
class ScriptValue {
|
||||||
|
public:
|
||||||
|
inline ScriptValue() : refCount(0) {}
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// 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 color
|
||||||
|
virtual operator Color() const;
|
||||||
|
|
||||||
|
/// Get a member variable from this value
|
||||||
|
virtual ScriptValueP getMember(const String& name) const;
|
||||||
|
|
||||||
|
/// Evaluate this value (if it is a function)
|
||||||
|
virtual ScriptValueP eval(Context&) const;
|
||||||
|
|
||||||
|
/// Return an iterator for the current collection, an iterator is a value that has next()
|
||||||
|
virtual ScriptValueP makeIterator() const;
|
||||||
|
/// Return the next item for this iterator, or ScriptValueP() if there is no such item
|
||||||
|
virtual ScriptValueP next();
|
||||||
|
|
||||||
|
/// Signal that a script depends on a member of this value
|
||||||
|
virtual void signalDependent(Context&, const Dependency&, const String& name);
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Delete this object
|
||||||
|
virtual void destroy() {
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
volatile LONG refCount;
|
||||||
|
friend void intrusive_ptr_add_ref(ScriptValue*);
|
||||||
|
friend void intrusive_ptr_release(ScriptValue*);
|
||||||
|
};
|
||||||
|
|
||||||
|
inline void intrusive_ptr_add_ref(ScriptValue* p) {
|
||||||
|
//p->refCount += 1;
|
||||||
|
InterlockedIncrement(&p->refCount);
|
||||||
|
}
|
||||||
|
inline void intrusive_ptr_release(ScriptValue* p) {
|
||||||
|
if (InterlockedDecrement(&p->refCount) == 0) {
|
||||||
|
//if (--p->refCount == 0) {
|
||||||
|
p->destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern ScriptValueP scriptNil; ///< The preallocated nil value
|
||||||
|
extern ScriptValueP scriptTrue; ///< The preallocated true value
|
||||||
|
extern ScriptValueP scriptFalse; ///< The preallocated false value
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Iterators
|
||||||
|
|
||||||
|
// Iterator over a collection
|
||||||
|
struct ScriptIterator : public ScriptValue {
|
||||||
|
virtual ScriptType type() const;// { return SCRIPT_OBJECT; }
|
||||||
|
virtual String typeName() const;// { return "iterator"; }
|
||||||
|
|
||||||
|
/// Return the next item for this iterator, or ScriptValueP() if there is no such item
|
||||||
|
virtual ScriptValueP next() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// make an iterator over a range
|
||||||
|
ScriptValueP rangeIterator(int start, int end);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Collections
|
||||||
|
|
||||||
|
// Iterator over a collection
|
||||||
|
template <typename Collection>
|
||||||
|
class ScriptCollectionIterator : public ScriptIterator {
|
||||||
|
public:
|
||||||
|
ScriptCollectionIterator(const Collection* col) : pos(0), col(col) {}
|
||||||
|
virtual ScriptValueP next() {
|
||||||
|
if (pos < col->size()) {
|
||||||
|
return toScript(col->at(pos++));
|
||||||
|
} else {
|
||||||
|
return ScriptValueP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
size_t pos;
|
||||||
|
const Collection* col;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Script value containing a collection
|
||||||
|
template <typename Collection>
|
||||||
|
class ScriptCollection : public ScriptValue {
|
||||||
|
public:
|
||||||
|
inline ScriptCollection(const Collection* v) : value(v) {}
|
||||||
|
virtual ScriptType type() const { return SCRIPT_OBJECT; }
|
||||||
|
virtual String typeName() const { return "collection"; }
|
||||||
|
virtual ScriptValueP getMember(const String& name) const {
|
||||||
|
int index = lexical_cast<int>(name);
|
||||||
|
return toScript(value->at(index));
|
||||||
|
}
|
||||||
|
virtual ScriptValueP makeIterator() const {
|
||||||
|
return new_intrusive1<ScriptCollectionIterator<Collection> >(value);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
/// Store a pointer to a collection, collections are only ever used for structures owned outside the script
|
||||||
|
const Collection* value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Objects
|
||||||
|
|
||||||
|
/// Script value containing an object (pointer)
|
||||||
|
template <typename T>
|
||||||
|
class ScriptObject : public ScriptValue {
|
||||||
|
public:
|
||||||
|
inline ScriptObject(const shared_ptr<T>& v) : value(v) {}
|
||||||
|
virtual ScriptType type() const { return SCRIPT_OBJECT; }
|
||||||
|
virtual String typeName() const { return "object"; }
|
||||||
|
virtual ScriptValueP getMember(const String& name) const {
|
||||||
|
GetMember gm(name);
|
||||||
|
gm.handle(*value);
|
||||||
|
if (gm.result()) return gm.result();
|
||||||
|
else throw ScriptError(_("Object has no member '") + name + _("'"));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
shared_ptr<T> value; ///< The object
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Creating
|
||||||
|
|
||||||
|
/// Convert a value to a script value
|
||||||
|
ScriptValueP toScript(int v);
|
||||||
|
ScriptValueP toScript(double v);
|
||||||
|
ScriptValueP toScript(const String& v);
|
||||||
|
ScriptValueP toScript(const Color& v);
|
||||||
|
inline ScriptValueP toScript(bool v) { return v ? scriptTrue : scriptFalse; }
|
||||||
|
template <typename T>
|
||||||
|
inline ScriptValueP toScript(const vector<T>* v) { return new_intrusive1<ScriptCollection<vector<T> > >(v); }
|
||||||
|
template <typename T>
|
||||||
|
inline ScriptValueP toScript(const shared_ptr<T>& v) { return new_intrusive1<ScriptObject<T> >(v); }
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Buildin functions
|
||||||
|
|
||||||
|
/// Macro to declare a new script function
|
||||||
|
/** Usage:
|
||||||
|
* @code
|
||||||
|
* SCRIPT_FUNCTION(my_function) {
|
||||||
|
* // function code goes here
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
* This declares a value 'script_my_function' which can be added as a variable to a context
|
||||||
|
* using:
|
||||||
|
* @code
|
||||||
|
* extern ScriptValueP script_my_function;
|
||||||
|
* context.setVariable("my_function", script_my_function);
|
||||||
|
* @endcode
|
||||||
|
*/
|
||||||
|
#define SCRIPT_FUNCTION(name) SCRIPT_FUNCTION_AUX(name,virtual)
|
||||||
|
|
||||||
|
/// Macro to declare a new script function with custom dependency handling
|
||||||
|
#define SCRIPT_FUNCTION_DEP(name) SCRIPT_FUNCTION_AUX(name,virtual) //TODO
|
||||||
|
|
||||||
|
// helper for SCRIPT_FUNCTION and SCRIPT_FUNCTION_DEP
|
||||||
|
#define SCRIPT_FUNCTION_AUX(name,dep) \
|
||||||
|
class ScriptBuildin_##name : public ScriptValue { \
|
||||||
|
dep \
|
||||||
|
/* virtual */ ScriptType type() const \
|
||||||
|
{ return SCRIPT_BUILDIN_FUN; } \
|
||||||
|
virtual String typeName() const \
|
||||||
|
{ return _("build in function '") _(#name) _("'"); } \
|
||||||
|
virtual ScriptValueP eval(Context&) const; \
|
||||||
|
}; \
|
||||||
|
ScriptValueP script_##name(new ScriptBuildin_##name); \
|
||||||
|
ScriptValueP ScriptBuildin_##name::eval(Context& ctx) const
|
||||||
|
|
||||||
|
/// Retrieve a parameter to a SCRIPT_FUNCTION with the given name and type
|
||||||
|
/** Usage:
|
||||||
|
* @code
|
||||||
|
* SCRIPT_FUNCTION(my_function) {
|
||||||
|
* SCRIPT_PARAM(String, my_string_param);
|
||||||
|
* }
|
||||||
|
* @endcode
|
||||||
|
* Throws an error if the parameter is not found.
|
||||||
|
*/
|
||||||
|
#define SCRIPT_PARAM(Type, name) \
|
||||||
|
Type name = *ctx.getVariable(_(#name))
|
||||||
|
|
||||||
|
/// Return a value from a SCRIPT_FUNCTION
|
||||||
|
#define SCRIPT_RETURN(value) return toScript(value)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/io/get_member.hpp>
|
||||||
|
#include <script/value.hpp>
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : GetMember
|
||||||
|
|
||||||
|
GetMember::GetMember(const String& name)
|
||||||
|
: targetName(name)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void GetMember::store(const String& v) { value = toScript(v); }
|
||||||
|
void GetMember::store(const int v) { value = toScript(v); }
|
||||||
|
void GetMember::store(const unsigned int v) { value = toScript((int)v); }
|
||||||
|
void GetMember::store(const double v) { value = toScript(v); }
|
||||||
|
void GetMember::store(const bool v) { value = toScript(v); }
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
//| Description: Magic Set Editor - Program to make Magic (tm) cards |
|
||||||
|
//| Copyright: (C) 2001 - 2006 Twan van Laarhoven |
|
||||||
|
//| License: GNU General Public License 2 or later (see file COPYING) |
|
||||||
|
//+----------------------------------------------------------------------------+
|
||||||
|
|
||||||
|
#ifndef HEADER_UTIL_IO_GET_MEMBER
|
||||||
|
#define HEADER_UTIL_IO_GET_MEMBER
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Includes
|
||||||
|
|
||||||
|
#include <util/prec.hpp>
|
||||||
|
|
||||||
|
class ScriptValue;
|
||||||
|
typedef boost::intrusive_ptr<ScriptValue> ScriptValueP;
|
||||||
|
inline void intrusive_ptr_add_ref(ScriptValue* p);
|
||||||
|
inline void intrusive_ptr_release(ScriptValue* p);
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : GetMember
|
||||||
|
|
||||||
|
/// Find a member with a specific name using reflection
|
||||||
|
/** The member is wrapped in a ScriptValue */
|
||||||
|
class GetMember {
|
||||||
|
public:
|
||||||
|
/// Construct a member getter that looks for the given name
|
||||||
|
GetMember(const String& name);
|
||||||
|
|
||||||
|
/// Tell the reflection code we are not reading
|
||||||
|
inline bool reading() const { return false; }
|
||||||
|
|
||||||
|
/// The result, or scriptNil if the member was not found
|
||||||
|
inline ScriptValueP result() { return value; }
|
||||||
|
|
||||||
|
// --------------------------------------------------- : Handling objects
|
||||||
|
|
||||||
|
/// Handle an object: we are done if the name matches
|
||||||
|
template <typename T>
|
||||||
|
void handle(const Char* name, const T& object) {
|
||||||
|
if (!value && name == targetName) store(object);
|
||||||
|
}
|
||||||
|
template <typename T>
|
||||||
|
void handle(const T&);
|
||||||
|
|
||||||
|
/// Store something in the return value
|
||||||
|
void store(const String& v);
|
||||||
|
void store(const int v);
|
||||||
|
void store(const unsigned int v);
|
||||||
|
void store(const double v);
|
||||||
|
void store(const bool v);
|
||||||
|
/// Store a vector in the return value
|
||||||
|
template <typename T> void store(const vector<T>& vector) {
|
||||||
|
value = toScript(&vector);
|
||||||
|
}
|
||||||
|
/// Store a shared_ptr in the return value
|
||||||
|
template <typename T> void store(const shared_ptr<T>& pointer) {
|
||||||
|
value = toScript(pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const String& targetName; ///< The name we are looking for
|
||||||
|
ScriptValueP value; ///< The value we found (if any)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Reflection
|
||||||
|
|
||||||
|
/// Implement reflection as used by GetMember
|
||||||
|
#define REFLECT_OBJECT_GET_MEMBER(Cls) \
|
||||||
|
template<> void GetMember::handle<Cls>(const Cls& object) { \
|
||||||
|
const_cast<Cls&>(object).reflect(*this); \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : Reflection for enumerations
|
||||||
|
|
||||||
|
/// Implement enum reflection as used by Writer
|
||||||
|
#define REFLECT_ENUM_WRITER(Enum) \
|
||||||
|
template<> void Writer::handle<Enum>(const Enum& enum_) { \
|
||||||
|
EnumGetMember gm(*this); \
|
||||||
|
reflect_ ## Enum(const_cast<Enum&>(enum_), gm); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 'Tag' to be used when reflecting enumerations for GetMember
|
||||||
|
class EnumGetMember {
|
||||||
|
public:
|
||||||
|
inline EnumGetMember(GetMember& getMember)
|
||||||
|
: getMember(getMember) {}
|
||||||
|
|
||||||
|
/// Handle a possible value for the enum, if the name matches the name in the input
|
||||||
|
template <typename Enum>
|
||||||
|
inline void handle(const Char* name, Enum value, Enum enum_) {
|
||||||
|
if (enum_ == value) {
|
||||||
|
writer.store(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GetMember& getMember; ///< The writer to write output to
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------- : EOF
|
||||||
|
#endif
|
||||||
Reference in New Issue
Block a user