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