Commit 5a96fa68 authored by twanvl's avatar twanvl

Added scripting support; not yet integrated with the rest of the app.

parent 07d69616
//+----------------------------------------------------------------------------+
//| 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) {
}
//+----------------------------------------------------------------------------+
//| 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
//+----------------------------------------------------------------------------+
//| 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();
}
}
//+----------------------------------------------------------------------------+
//| 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);
}
}
//+----------------------------------------------------------------------------+
//| 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
//+----------------------------------------------------------------------------+
//| 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;
}
//+----------------------------------------------------------------------------+
//| 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
//+----------------------------------------------------------------------------+
//| 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
//+----------------------------------------------------------------------------+
//| 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
//+----------------------------------------------------------------------------+
//| 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); }
//+----------------------------------------------------------------------------+
//| 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment