From bbc2dbf332f7884361742ddfb5200e2c0e7ff3f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= Date: Wed, 11 Jun 2014 14:02:23 +0300 Subject: [PATCH] libcore|Scripting: Core functionality for object-oriented classes This commit implements the core plumbing of an object-oriented class system for Doomsday Script. Added Value::memberScope(): when the value is used with the member operator (left value), returns the Record where the members should be looked up from. NameExpression now checks for the existence of the __isa__ member that specifies all the superclasses of an instance. Identifiers can be located from superclasses if they are not actual instance members. Built-in types like DictionaryValue can return their built-in class directly via memberScope(), so that any operations done on them are looked up from the class. Evaluator now keeps track of the scope in which evaluation occurs in terms of a Value, so that the appropriate instance can be determined when doing a member function call. It can then call memberScope() as appropriate to determine the namespace of the members. It can also then give out the scope when a result is popped, so that a function call can take this value and give it to "self" or specify it via Context::instanceScope() for native calls. ScriptSystem defines built-in classes in the native "Script" module, e.g., Script.Dictionary with keys() and values() member functions. Refactored dictkeys() and dictvalues() to ask DictionaryValue to compose the returned arrays. Applied pimpl in Evaluator and Context. --- .../libcore/include/de/data/dictionaryvalue.h | 14 + .../libcore/include/de/data/recordvalue.h | 1 + doomsday/libcore/include/de/data/refvalue.h | 3 +- doomsday/libcore/include/de/data/value.h | 21 +- .../include/de/scriptsys/arrayexpression.h | 2 +- .../include/de/scriptsys/builtinexpression.h | 2 +- .../libcore/include/de/scriptsys/context.h | 97 ++----- .../de/scriptsys/dictionaryexpression.h | 2 +- .../libcore/include/de/scriptsys/evaluator.h | 44 +-- .../libcore/include/de/scriptsys/expression.h | 2 +- .../include/de/scriptsys/functionvalue.h | 2 +- .../include/de/scriptsys/nameexpression.h | 5 +- .../include/de/scriptsys/operatorexpression.h | 2 +- .../libcore/include/de/scriptsys/process.h | 5 +- .../include/de/scriptsys/scriptsystem.h | 2 + doomsday/libcore/src/data/dictionaryvalue.cpp | 24 ++ doomsday/libcore/src/data/recordvalue.cpp | 6 + doomsday/libcore/src/data/refvalue.cpp | 9 +- doomsday/libcore/src/data/value.cpp | 8 +- .../libcore/src/scriptsys/arrayexpression.cpp | 4 +- .../src/scriptsys/builtinexpression.cpp | 21 +- doomsday/libcore/src/scriptsys/context.cpp | 232 ++++++++++++---- .../src/scriptsys/dictionaryexpression.cpp | 4 +- doomsday/libcore/src/scriptsys/evaluator.cpp | 259 ++++++++++++------ doomsday/libcore/src/scriptsys/expression.cpp | 4 +- .../libcore/src/scriptsys/functionvalue.cpp | 4 +- .../libcore/src/scriptsys/nameexpression.cpp | 85 ++++-- .../src/scriptsys/operatorexpression.cpp | 27 +- doomsday/libcore/src/scriptsys/process.cpp | 10 +- .../libcore/src/scriptsys/scriptsystem.cpp | 36 ++- 30 files changed, 610 insertions(+), 327 deletions(-) diff --git a/doomsday/libcore/include/de/data/dictionaryvalue.h b/doomsday/libcore/include/de/data/dictionaryvalue.h index 1ae6682f5c..2fa1bd9d69 100644 --- a/doomsday/libcore/include/de/data/dictionaryvalue.h +++ b/doomsday/libcore/include/de/data/dictionaryvalue.h @@ -26,6 +26,8 @@ namespace de { +class ArrayValue; + /** * Subclass of Value that contains an array of values, indexed by any value. * @@ -85,9 +87,21 @@ class DENG2_PUBLIC DictionaryValue : public Value void remove(Elements::iterator const &pos); + enum ContentSelection { Keys, Values }; + + /** + * Creates an array with the keys or the values of the dictionary. + * + * @param selection Keys or values. + * + * @return Caller gets ownership. + */ + ArrayValue *contentsAsArray(ContentSelection selection) const; + // Implementations of pure virtual methods. Value *duplicate() const; Text asText() const; + Record *memberScope() const; dsize size() const; Value const &element(Value const &index) const; Value &element(Value const &index); diff --git a/doomsday/libcore/include/de/data/recordvalue.h b/doomsday/libcore/include/de/data/recordvalue.h index 2a3089944c..f0e9b93be5 100644 --- a/doomsday/libcore/include/de/data/recordvalue.h +++ b/doomsday/libcore/include/de/data/recordvalue.h @@ -106,6 +106,7 @@ class DENG2_PUBLIC RecordValue : public Value, DENG2_OBSERVES(Record, Deletion) Value *duplicate() const; Value *duplicateAsReference() const; Text asText() const; + Record *memberScope() const; dsize size() const; void setElement(Value const &index, Value *elementValue); Value *duplicateElement(Value const &value) const; diff --git a/doomsday/libcore/include/de/data/refvalue.h b/doomsday/libcore/include/de/data/refvalue.h index 1654dfce1b..7716eeebd3 100644 --- a/doomsday/libcore/include/de/data/refvalue.h +++ b/doomsday/libcore/include/de/data/refvalue.h @@ -62,6 +62,7 @@ class DENG2_PUBLIC RefValue : public Value, Value *duplicate() const; Number asNumber() const; Text asText() const; + Record *memberScope() const; dsize size() const; Value const &element(Value const &index) const; Value &element(Value const &index); @@ -79,7 +80,7 @@ class DENG2_PUBLIC RefValue : public Value, void multiply(Value const &value); void modulo(Value const &divisor); void assign(Value *value); - void call(Process &process, Value const &arguments) const; + void call(Process &process, Value const &arguments, Value *instanceScope) const; // Implements ISerializable. void operator >> (Writer &to) const; diff --git a/doomsday/libcore/include/de/data/value.h b/doomsday/libcore/include/de/data/value.h index 9995ec95f4..9658075ae5 100644 --- a/doomsday/libcore/include/de/data/value.h +++ b/doomsday/libcore/include/de/data/value.h @@ -27,6 +27,7 @@ namespace de { class Process; +class Record; class NumberValue; /** @@ -138,6 +139,15 @@ class DENG2_PUBLIC Value : public String::IPatternArg, public ISerializable return dynamic_cast(this); } + /** + * Returns the scope for any members of this value. When evaluating a member in + * reference to this value, this will return the primary scope using which the + * members can be found. + * + * For built-in types, this may return one of the Script.* records. + */ + virtual Record *memberScope() const; + /** * Determine the size of the value. The meaning of this * depends on the type of the value. @@ -279,12 +289,13 @@ class DENG2_PUBLIC Value : public String::IPatternArg, public ISerializable /** * Applies the call operator on the value. * - * @param process Process where the call is made. - * @param arguments Arguments of the call. - * - * @return Result of the call operator, which can be anything. + * @param process Process where the call is made. + * @param arguments Arguments of the call. + * @param instanceScope Optional scope that becomes the value of the "self" + * variable in the called function's local namespace. + * Ownership taken. */ - virtual void call(Process &process, Value const &arguments) const; + virtual void call(Process &process, Value const &arguments, Value *instanceScope = 0) const; public: /** diff --git a/doomsday/libcore/include/de/scriptsys/arrayexpression.h b/doomsday/libcore/include/de/scriptsys/arrayexpression.h index 84dcef0780..222229bb5c 100644 --- a/doomsday/libcore/include/de/scriptsys/arrayexpression.h +++ b/doomsday/libcore/include/de/scriptsys/arrayexpression.h @@ -52,7 +52,7 @@ class ArrayExpression : public Expression */ void add(Expression *arg); - void push(Evaluator &evaluator, Record *names = 0) const; + void push(Evaluator &evaluator, Value *scope = 0) const; /** * Returns one of the expressions in the array. diff --git a/doomsday/libcore/include/de/scriptsys/builtinexpression.h b/doomsday/libcore/include/de/scriptsys/builtinexpression.h index 0d5075f0db..e5721f62f9 100644 --- a/doomsday/libcore/include/de/scriptsys/builtinexpression.h +++ b/doomsday/libcore/include/de/scriptsys/builtinexpression.h @@ -65,7 +65,7 @@ class DENG2_PUBLIC BuiltInExpression : public Expression ~BuiltInExpression(); - void push(Evaluator &evaluator, Record *names = 0) const; + void push(Evaluator &evaluator, Value *scope = 0) const; Value *evaluate(Evaluator &evaluator) const; diff --git a/doomsday/libcore/include/de/scriptsys/context.h b/doomsday/libcore/include/de/scriptsys/context.h index 47b470b491..24a2816fb1 100644 --- a/doomsday/libcore/include/de/scriptsys/context.h +++ b/doomsday/libcore/include/de/scriptsys/context.h @@ -1,7 +1,7 @@ /* * The Doomsday Engine Project -- libcore * - * Copyright © 2004-2013 Jaakko Keränen + * Copyright © 2004-2014 Jaakko Keränen * * @par License * LGPL: http://www.gnu.org/licenses/lgpl.html @@ -39,6 +39,9 @@ class DENG2_PUBLIC Context /// Attempting a jump when there is no suitable target (continue or break). @ingroup errors DENG2_ERROR(JumpError); + /// There is no instance scope defined for the context. @ingroup errors + DENG2_ERROR(UndefinedScopeError); + enum Type { BaseProcess, GlobalNamespace, @@ -55,13 +58,11 @@ class DENG2_PUBLIC Context */ Context(Type type, Process *owner, Record *globals = 0); - virtual ~Context(); - /// Determines the type of the execution context. - Type type() { return _type; } + Type type(); /// Returns the process that owns this context. - Process &process() { return *_owner; } + Process &process(); /// Returns the namespace of the context. Record &names(); @@ -135,84 +136,28 @@ class DENG2_PUBLIC Context void setIterationValue(Value *value); /** - * Returns the throwaway variable. This can be used for dumping - * values that are not needed. For instance, the weak assignment operator - * will use this when the identifier already exists. + * Sets the instance scope of the context. This is equivalent to the value + * of the "self" variable, however only used for native function calls. + * + * @param scope Value that specifies the instance whose scope the context + * is being evaluated in. Ownership taken. */ - Variable &throwaway() { return _throwaway; } + void setInstanceScope(Value *scope); -private: /** - * Information about the control flow is stored within a stack of - * ControlFlow instances. + * Returns the current instance scope. A scope must exist if this is called. */ - class ControlFlow { - public: - /** - * Constructor. - * - * @param current Current statement being executed. - * @param f Statement where normal flow continues. - * @param c Statement where to jump on "continue". - * @c NULL if continuing is not allowed. - * @param b Statement where to jump to and flow from on "break". - * @c NULL if breaking is not allowed. - */ - ControlFlow(Statement const *current, - Statement const *f = 0, - Statement const *c = 0, - Statement const *b = 0) - : flow(f), jumpContinue(c), jumpBreak(b), iteration(0), _current(current) {} - - /// Returns the currently executed statement. - Statement const *current() const { return _current; } - - /// Sets the currently executed statement. When the statement - /// changes, the phase is reset back to zero. - void setCurrent(Statement const *s) { _current = s; } - - public: - Statement const *flow; - Statement const *jumpContinue; - Statement const *jumpBreak; - Value *iteration; - - private: - Statement const *_current; - }; - -private: - /// Returns the topmost control flow information. - ControlFlow &flow() { return _controlFlow.back(); } - - /// Pops the topmost control flow instance off of the stack. The - /// iteration value is deleted, if it has been defined. - void popFlow(); + Value &instanceScope() const; - /// Sets the currently executed statement. - void setCurrent(Statement const *statement); + /** + * Returns the throwaway variable. This can be used for dumping + * values that are not needed. For instance, the weak assignment operator + * will use this when the identifier already exists. + */ + Variable &throwaway(); private: - /// Type of the execution context. - Type _type; - - /// The process that owns this context. - Process *_owner; - - /// Control flow stack. - typedef std::vector FlowStack; - FlowStack _controlFlow; - - /// Expression evaluator. - Evaluator _evaluator; - - /// Determines whether the namespace is owned by the context. - bool _ownsNamespace; - - /// The local namespace of this context. - Record *_names; - - Variable _throwaway; + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libcore/include/de/scriptsys/dictionaryexpression.h b/doomsday/libcore/include/de/scriptsys/dictionaryexpression.h index fa453e2c45..44689e8257 100644 --- a/doomsday/libcore/include/de/scriptsys/dictionaryexpression.h +++ b/doomsday/libcore/include/de/scriptsys/dictionaryexpression.h @@ -48,7 +48,7 @@ class DictionaryExpression : public Expression */ void add(Expression *key, Expression *value); - void push(Evaluator &evaluator, Record *names = 0) const; + void push(Evaluator &evaluator, Value *scope = 0) const; /** * Collects the result keys and values of the arguments and puts them diff --git a/doomsday/libcore/include/de/scriptsys/evaluator.h b/doomsday/libcore/include/de/scriptsys/evaluator.h index 96b8885ea2..60ee65acb5 100644 --- a/doomsday/libcore/include/de/scriptsys/evaluator.h +++ b/doomsday/libcore/include/de/scriptsys/evaluator.h @@ -1,7 +1,7 @@ /* * The Doomsday Engine Project -- libcore * - * Copyright © 2004-2013 Jaakko Keränen + * Copyright © 2004-2014 Jaakko Keränen * * @par License * LGPL: http://www.gnu.org/licenses/lgpl.html @@ -49,9 +49,8 @@ class DENG2_PUBLIC Evaluator public: Evaluator(Context &owner); - ~Evaluator(); - Context &context() { return _context; } + Context &context(); /** * Returns the process that owns this evaluator. @@ -114,9 +113,10 @@ class DENG2_PUBLIC Evaluator * Insert the given expression to the top of the expression stack. * * @param expression Expression to push on the stack. - * @param names Namespace scope for this expression only. + * @param scope Scope for this expression only (using memberScope()). + * Evaluator takes ownership of this value. */ - void push(Expression const *expression, Record *names = 0); + void push(Expression const *expression, Value *scope = 0); /** * Push a value onto the result stack. @@ -129,10 +129,15 @@ class DENG2_PUBLIC Evaluator /** * Pop a value off of the result stack. * + * @param evaluationScope If not @c NULL, the scope used for evaluating the + * result is passed here to the caller. If there was + * a scope, ownership of the scope value is given to + * the caller. + * * @return Value resulting from expression evaluation. Caller * gets ownership of the returned object. */ - Value *popResult(); + Value *popResult(Value **evaluationScope = 0); /** * Pop a value off of the result stack, making sure it has a specific type. @@ -163,32 +168,7 @@ class DENG2_PUBLIC Evaluator Value &result(); private: - void clearNames(); - void clearResults(); - void clearStack(); - - /// The context that owns this evaluator. - Context &_context; - - struct ScopedExpression { - Expression const *expression; - Record *names; - ScopedExpression(Expression const *e = 0, Record *n = 0) : expression(e), names(n) {} - }; - typedef std::vector Expressions; - typedef std::vector Results; - - /// The expression that is currently being evaluated. - Expression const *_current; - - /// Namespace for the current expression. - Record *_names; - - Expressions _stack; - Results _results; - - /// Returned when there is no result to give. - NoneValue _noResult; + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libcore/include/de/scriptsys/expression.h b/doomsday/libcore/include/de/scriptsys/expression.h index e5c382e00a..d4b691a2f7 100644 --- a/doomsday/libcore/include/de/scriptsys/expression.h +++ b/doomsday/libcore/include/de/scriptsys/expression.h @@ -88,7 +88,7 @@ class DENG2_PUBLIC Expression : public ISerializable public: virtual ~Expression(); - virtual void push(Evaluator &evaluator, Record *names = 0) const; + virtual void push(Evaluator &evaluator, Value *scope = 0) const; virtual Value *evaluate(Evaluator &evaluator) const = 0; diff --git a/doomsday/libcore/include/de/scriptsys/functionvalue.h b/doomsday/libcore/include/de/scriptsys/functionvalue.h index 481cc32942..2c6c991be5 100644 --- a/doomsday/libcore/include/de/scriptsys/functionvalue.h +++ b/doomsday/libcore/include/de/scriptsys/functionvalue.h @@ -45,7 +45,7 @@ class FunctionValue : public Value bool isTrue() const; bool isFalse() const; dint compare(Value const &value) const; - void call(Process &process, Value const &arguments) const; + void call(Process &process, Value const &arguments, Value *instanceScope) const; // Implements ISerializable. void operator >> (Writer &to) const; diff --git a/doomsday/libcore/include/de/scriptsys/nameexpression.h b/doomsday/libcore/include/de/scriptsys/nameexpression.h index 317d73cb2a..b10d38582e 100644 --- a/doomsday/libcore/include/de/scriptsys/nameexpression.h +++ b/doomsday/libcore/include/de/scriptsys/nameexpression.h @@ -48,10 +48,9 @@ class NameExpression : public Expression public: NameExpression(); NameExpression(String const &identifier, Flags const &flags = ByValue); - ~NameExpression(); /// Returns the identifier in the name expression. - String const &identifier() const { return _identifier; } + String const &identifier() const; Value *evaluate(Evaluator &evaluator) const; @@ -60,7 +59,7 @@ class NameExpression : public Expression void operator << (Reader &from); private: - String _identifier; + DENG2_PRIVATE(d) }; } // namespace de diff --git a/doomsday/libcore/include/de/scriptsys/operatorexpression.h b/doomsday/libcore/include/de/scriptsys/operatorexpression.h index 611f4e32e6..c88ffd64db 100644 --- a/doomsday/libcore/include/de/scriptsys/operatorexpression.h +++ b/doomsday/libcore/include/de/scriptsys/operatorexpression.h @@ -76,7 +76,7 @@ class OperatorExpression : public Expression ~OperatorExpression(); - void push(Evaluator &evaluator, Record *names = 0) const; + void push(Evaluator &evaluator, Value *scope = 0) const; Value *evaluate(Evaluator &evaluator) const; diff --git a/doomsday/libcore/include/de/scriptsys/process.h b/doomsday/libcore/include/de/scriptsys/process.h index 072f0c8c47..689a1e743b 100644 --- a/doomsday/libcore/include/de/scriptsys/process.h +++ b/doomsday/libcore/include/de/scriptsys/process.h @@ -172,8 +172,11 @@ class DENG2_PUBLIC Process * must be a DictionaryValue containing values for the * named arguments of the call. The rest of the array * are the unnamed arguments. + * @parm instanceScope Optional scope that becomes the value of the "self" + * variable. Ownership given to Process. */ - void call(Function const &function, ArrayValue const &arguments); + void call(Function const &function, ArrayValue const &arguments, + Value *instanceScope = 0); /** * Collects the namespaces currently visible. This includes the process's diff --git a/doomsday/libcore/include/de/scriptsys/scriptsystem.h b/doomsday/libcore/include/de/scriptsys/scriptsystem.h index 67701502a8..1b103d5e40 100644 --- a/doomsday/libcore/include/de/scriptsys/scriptsystem.h +++ b/doomsday/libcore/include/de/scriptsys/scriptsystem.h @@ -92,6 +92,8 @@ class DENG2_PUBLIC ScriptSystem : public System */ File const &findModuleSource(String const &name, String const &localPath = ""); + static Record &builtInClass(String const &name); + void timeChanged(Clock const &); private: diff --git a/doomsday/libcore/src/data/dictionaryvalue.cpp b/doomsday/libcore/src/data/dictionaryvalue.cpp index e3074c71f7..ffd0c7133a 100644 --- a/doomsday/libcore/src/data/dictionaryvalue.cpp +++ b/doomsday/libcore/src/data/dictionaryvalue.cpp @@ -20,6 +20,7 @@ #include "de/DictionaryValue" #include "de/ArrayValue" #include "de/RecordValue" +#include "de/ScriptSystem" #include "de/Writer" #include "de/Reader" @@ -91,6 +92,24 @@ void DictionaryValue::remove(Elements::iterator const &pos) _elements.erase(pos); } +ArrayValue *DictionaryValue::contentsAsArray(ContentSelection selection) const +{ + QScopedPointer array(new ArrayValue); + + DENG2_FOR_EACH_CONST(Elements, i, elements()) + { + if(selection == Keys) + { + array->add(i->first.value->duplicateAsReference()); + } + else + { + array->add(i->second->duplicateAsReference()); + } + } + return array.take(); +} + Value *DictionaryValue::duplicate() const { return new DictionaryValue(*this); @@ -120,6 +139,11 @@ Value::Text DictionaryValue::asText() const return result; } +Record *DictionaryValue::memberScope() const +{ + return &ScriptSystem::builtInClass("Dictionary"); +} + dsize DictionaryValue::size() const { return _elements.size(); diff --git a/doomsday/libcore/src/data/recordvalue.cpp b/doomsday/libcore/src/data/recordvalue.cpp index 0a3d1c9d78..d4e621c4b8 100644 --- a/doomsday/libcore/src/data/recordvalue.cpp +++ b/doomsday/libcore/src/data/recordvalue.cpp @@ -164,6 +164,12 @@ Value::Text RecordValue::asText() const return dereference().asText(); } +Record *RecordValue::memberScope() const +{ + verify(); + return d->record; +} + dsize RecordValue::size() const { return dereference().members().size(); diff --git a/doomsday/libcore/src/data/refvalue.cpp b/doomsday/libcore/src/data/refvalue.cpp index 1ac57b68c3..79e7f6a10e 100644 --- a/doomsday/libcore/src/data/refvalue.cpp +++ b/doomsday/libcore/src/data/refvalue.cpp @@ -75,6 +75,11 @@ Value::Text RefValue::asText() const return dereference().asText(); } +Record *RefValue::memberScope() const +{ + return dereference().memberScope(); +} + dsize RefValue::size() const { return dereference().size(); @@ -161,9 +166,9 @@ void RefValue::assign(Value *value) _variable->set(value); } -void RefValue::call(Process &process, Value const &arguments) const +void RefValue::call(Process &process, Value const &arguments, Value *instanceScope) const { - dereference().call(process, arguments); + dereference().call(process, arguments, instanceScope); } void RefValue::operator >> (Writer &to) const diff --git a/doomsday/libcore/src/data/value.cpp b/doomsday/libcore/src/data/value.cpp index 55e0fa352f..8b25928320 100644 --- a/doomsday/libcore/src/data/value.cpp +++ b/doomsday/libcore/src/data/value.cpp @@ -62,6 +62,12 @@ int Value::asInt() const return round(asNumber()); } +Record *Value::memberScope() const +{ + // By default, there are no members are thus no scope for them. + return 0; +} + dsize Value::size() const { /// @throw IllegalError Size is meaningless. @@ -168,7 +174,7 @@ void Value::assign(Value *value) throw IllegalError("Value::assign", "Cannot assign to value"); } -void Value::call(Process &/*process*/, Value const &/*arguments*/) const +void Value::call(Process &, Value const &, Value *) const { /// @throw IllegalError Value cannot be called. throw IllegalError("Value::call", "Value cannot be called"); diff --git a/doomsday/libcore/src/scriptsys/arrayexpression.cpp b/doomsday/libcore/src/scriptsys/arrayexpression.cpp index d116f3be3a..41c8a9db76 100644 --- a/doomsday/libcore/src/scriptsys/arrayexpression.cpp +++ b/doomsday/libcore/src/scriptsys/arrayexpression.cpp @@ -46,9 +46,9 @@ void ArrayExpression::add(Expression *arg) _arguments.push_back(arg); } -void ArrayExpression::push(Evaluator &evaluator, Record *names) const +void ArrayExpression::push(Evaluator &evaluator, Value *scope) const { - Expression::push(evaluator, names); + Expression::push(evaluator, scope); // The arguments in reverse order (so they are evaluated in // natural order, i.e., the same order they are in the source). diff --git a/doomsday/libcore/src/scriptsys/builtinexpression.cpp b/doomsday/libcore/src/scriptsys/builtinexpression.cpp index 08f01c7dd0..530ea15edf 100644 --- a/doomsday/libcore/src/scriptsys/builtinexpression.cpp +++ b/doomsday/libcore/src/scriptsys/builtinexpression.cpp @@ -47,9 +47,9 @@ BuiltInExpression::~BuiltInExpression() delete _arg; } -void BuiltInExpression::push(Evaluator &evaluator, Record *) const +void BuiltInExpression::push(Evaluator &evaluator, Value *scope) const { - Expression::push(evaluator); + Expression::push(evaluator, scope); _arg->push(evaluator); } @@ -85,20 +85,9 @@ Value *BuiltInExpression::evaluate(Evaluator &evaluator) const throw WrongArgumentsError("BuiltInExpression::evaluate", "Argument must be a dictionary"); } - ArrayValue *array = new ArrayValue(); - for(DictionaryValue::Elements::const_iterator i = dict->elements().begin(); - i != dict->elements().end(); ++i) - { - if(_type == DICTIONARY_KEYS) - { - array->add(i->first.value->duplicateAsReference()); - } - else - { - array->add(i->second->duplicateAsReference()); - } - } - return array; + + return dict->contentsAsArray(_type == DICTIONARY_KEYS ? DictionaryValue::Keys + : DictionaryValue::Values); } case RECORD_MEMBERS: diff --git a/doomsday/libcore/src/scriptsys/context.cpp b/doomsday/libcore/src/scriptsys/context.cpp index 9a1c33ed9f..42e2ed8d70 100644 --- a/doomsday/libcore/src/scriptsys/context.cpp +++ b/doomsday/libcore/src/scriptsys/context.cpp @@ -1,7 +1,7 @@ /* * The Doomsday Engine Project -- libcore * - * Copyright © 2004-2013 Jaakko Keränen + * Copyright © 2004-2014 Jaakko Keränen * * @par License * LGPL: http://www.gnu.org/licenses/lgpl.html @@ -21,43 +21,156 @@ #include "de/Statement" #include "de/Process" -using namespace de; +namespace de { -Context::Context(Type type, Process *owner, Record *globals) - : _type(type), _owner(owner), _evaluator(*this), _ownsNamespace(false), _names(globals) +DENG2_PIMPL(Context) { - if(!_names) + /** + * Information about the control flow is stored within a stack of + * ControlFlow instances. + */ + class ControlFlow { + public: + /** + * Constructor. + * + * @param current Current statement being executed. + * @param f Statement where normal flow continues. + * @param c Statement where to jump on "continue". + * @c NULL if continuing is not allowed. + * @param b Statement where to jump to and flow from on "break". + * @c NULL if breaking is not allowed. + */ + ControlFlow(Statement const *current, + Statement const *f = 0, + Statement const *c = 0, + Statement const *b = 0) + : flow(f), jumpContinue(c), jumpBreak(b), iteration(0), _current(current) {} + + /// Returns the currently executed statement. + Statement const *current() const { return _current; } + + /// Sets the currently executed statement. When the statement + /// changes, the phase is reset back to zero. + void setCurrent(Statement const *s) { _current = s; } + + public: + Statement const *flow; + Statement const *jumpContinue; + Statement const *jumpBreak; + Value *iteration; + + private: + Statement const *_current; + }; + + /// Type of the execution context. + Type type; + + /// The process that owns this context. + Process *owner; + + /// Control flow stack. + typedef std::vector FlowStack; + FlowStack controlFlow; + + /// Expression evaluator. + Evaluator evaluator; + + /// Determines whether the namespace is owned by the context. + bool ownsNamespace; + + /// The local namespace of this context. + Record *names; + + QScopedPointer instanceScope; + + Variable throwaway; + + Instance(Public *i, Type type, Process *owner, Record *globals) + : Base(i) + , type(type) + , owner(owner) + , evaluator(self) + , ownsNamespace(false) + , names(globals) { - // Create a private empty namespace. - DENG2_ASSERT(_type != GlobalNamespace); - _names = new Record(); - _ownsNamespace = true; + if(!names) + { + // Create a private empty namespace. + DENG2_ASSERT(type != GlobalNamespace); + names = new Record; + ownsNamespace = true; + } + } + + ~Instance() + { + if(ownsNamespace) + { + delete names; + } + self.reset(); } + + /// Returns the topmost control flow information. + ControlFlow &flow() + { + return controlFlow.back(); + } + + /// Pops the topmost control flow instance off of the stack. The + /// iteration value is deleted, if it has been defined. + void popFlow() + { + DENG2_ASSERT(!controlFlow.empty()); + delete flow().iteration; + controlFlow.pop_back(); + } + + /// Sets the currently executed statement. + void setCurrent(Statement const *statement) + { + if(controlFlow.size()) + { + evaluator.reset(); + flow().setCurrent(statement); + } + else + { + DENG2_ASSERT(statement == NULL); + } + } +}; + +Context::Context(Type type, Process *owner, Record *globals) + : d(new Instance(this, type, owner, globals)) +{} + +Context::Type Context::type() +{ + return d->type; } -Context::~Context() +Process &Context::process() { - if(_ownsNamespace) - { - delete _names; - } - reset(); + return *d->owner; } Evaluator &Context::evaluator() { - return _evaluator; + return d->evaluator; } Record &Context::names() { - return *_names; + return *d->names; } -void Context::start(Statement const *statement, Statement const *fallback, - Statement const *jumpContinue, Statement const *jumpBreak) +void Context::start(Statement const *statement, Statement const *fallback, + Statement const *jumpContinue, Statement const *jumpBreak) { - _controlFlow.push_back(ControlFlow(statement, fallback, jumpContinue, jumpBreak)); + d->controlFlow.push_back(Instance::ControlFlow(statement, fallback, jumpContinue, jumpBreak)); // When the current statement is NULL it means that the sequence of statements // has ended, so we shouldn't do that until there really is no more statements. @@ -69,11 +182,11 @@ void Context::start(Statement const *statement, Statement const *fallback, void Context::reset() { - while(!_controlFlow.empty()) + while(!d->controlFlow.empty()) { - popFlow(); + d->popFlow(); } - _evaluator.reset(); + d->evaluator.reset(); } bool Context::execute() @@ -94,27 +207,27 @@ void Context::proceed() st = current()->next(); } // Should we fall back to a point that was specified earlier? - while(!st && _controlFlow.size()) + while(!st && d->controlFlow.size()) { - st = _controlFlow.back().flow; - popFlow(); + st = d->controlFlow.back().flow; + d->popFlow(); } - setCurrent(st); + d->setCurrent(st); } void Context::jumpContinue() { Statement const *st = NULL; - while(!st && _controlFlow.size()) + while(!st && d->controlFlow.size()) { - st = _controlFlow.back().jumpContinue; - popFlow(); + st = d->controlFlow.back().jumpContinue; + d->popFlow(); } if(!st) { throw JumpError("Context::jumpContinue", "No jump targets defined for continue"); } - setCurrent(st); + d->setCurrent(st); } void Context::jumpBreak(duint count) @@ -125,14 +238,14 @@ void Context::jumpBreak(duint count) } Statement const *st = NULL; - while((!st || count > 0) && _controlFlow.size()) + while((!st || count > 0) && d->controlFlow.size()) { - st = _controlFlow.back().jumpBreak; + st = d->controlFlow.back().jumpBreak; if(st) { --count; } - popFlow(); + d->popFlow(); } if(count > 0) { @@ -142,43 +255,30 @@ void Context::jumpBreak(duint count) { throw JumpError("Context::jumpBreak", "No jump targets defined for break"); } - setCurrent(st); + d->setCurrent(st); proceed(); } Statement const *Context::current() { - if(_controlFlow.size()) + if(d->controlFlow.size()) { - return flow().current(); + return d->flow().current(); } return NULL; } -void Context::setCurrent(Statement const *statement) -{ - if(_controlFlow.size()) - { - _evaluator.reset(); - flow().setCurrent(statement); - } - else - { - DENG2_ASSERT(statement == NULL); - } -} - Value *Context::iterationValue() { - DENG2_ASSERT(_controlFlow.size()); - return _controlFlow.back().iteration; + DENG2_ASSERT(d->controlFlow.size()); + return d->controlFlow.back().iteration; } void Context::setIterationValue(Value *value) { - DENG2_ASSERT(_controlFlow.size()); + DENG2_ASSERT(d->controlFlow.size()); - ControlFlow &fl = flow(); + Instance::ControlFlow &fl = d->flow(); if(fl.iteration) { delete fl.iteration; @@ -186,9 +286,25 @@ void Context::setIterationValue(Value *value) fl.iteration = value; } -void Context::popFlow() +void Context::setInstanceScope(Value *scope) { - DENG2_ASSERT(!_controlFlow.empty()); - delete flow().iteration; - _controlFlow.pop_back(); + d->instanceScope.reset(scope); } + +Value &Context::instanceScope() const +{ + DENG2_ASSERT(!d->instanceScope.isNull()); + if(d->instanceScope.isNull()) + { + throw UndefinedScopeError("Context::instanceScope", + "Context is not executing in scope of any instance"); + } + return *d->instanceScope; +} + +Variable &Context::throwaway() +{ + return d->throwaway; +} + +} // namespace de diff --git a/doomsday/libcore/src/scriptsys/dictionaryexpression.cpp b/doomsday/libcore/src/scriptsys/dictionaryexpression.cpp index ec9d589118..f0ca717eba 100644 --- a/doomsday/libcore/src/scriptsys/dictionaryexpression.cpp +++ b/doomsday/libcore/src/scriptsys/dictionaryexpression.cpp @@ -52,9 +52,9 @@ void DictionaryExpression::add(Expression *key, Expression *value) _arguments.push_back(ExpressionPair(key, value)); } -void DictionaryExpression::push(Evaluator &evaluator, Record *names) const +void DictionaryExpression::push(Evaluator &evaluator, Value *scope) const { - Expression::push(evaluator, names); + Expression::push(evaluator, scope); // The arguments in reverse order (so they are evaluated in // natural order, i.e., the same order they are in the source). diff --git a/doomsday/libcore/src/scriptsys/evaluator.cpp b/doomsday/libcore/src/scriptsys/evaluator.cpp index 6037660537..acaee49ca0 100644 --- a/doomsday/libcore/src/scriptsys/evaluator.cpp +++ b/doomsday/libcore/src/scriptsys/evaluator.cpp @@ -1,7 +1,7 @@ /* * The Doomsday Engine Project -- libcore * - * Copyright © 2004-2013 Jaakko Keränen + * Copyright © 2004-2014 Jaakko Keränen * * @par License * LGPL: http://www.gnu.org/licenses/lgpl.html @@ -23,77 +23,190 @@ #include "de/Context" #include "de/Process" -using namespace de; +#include -Evaluator::Evaluator(Context &owner) : _context(owner), _current(0), _names(0) +namespace de { + +DENG2_PIMPL(Evaluator) +{ + /// The context that owns this evaluator. + Context &context; + + struct ScopedExpression { + Expression const *expression; + Value *scope; // owned + + ScopedExpression(Expression const *e = 0, Value *s = 0) : expression(e), scope(s) {} + Record *names() const { + if(!scope) return 0; + return scope->memberScope(); + } + }; + struct ScopedResult { + Value *result; + Value *scope; // owned + + ScopedResult(Value *v, Value *s = 0) : result(v), scope(s) {} + }; + + typedef QList Expressions; + typedef QList Results; + + /// The expression that is currently being evaluated. + Expression const *current; + + /// Namespace for the current expression. + Record *names; + + Expressions stack; + Results results; + + /// Returned when there is no result to give. + NoneValue noResult; + + Instance(Public *i, Context &owner) + : Base(i) + , context(owner) + , current(0) + , names(0) + {} + + ~Instance() + { + clearNames(); + clearResults(); + } + + void clearNames() + { + if(names) + { + names = 0; + } + } + + void clearResults() + { + foreach(ScopedResult const &i, results) + { + delete i.result; + } + results.clear(); + } + + void clearStack() + { + while(!stack.empty()) + { + ScopedExpression top = stack.takeLast(); + clearNames(); + names = top.names(); + delete top.scope; + } + } + + void pushResult(Value *value, Value *scope = 0 /*take*/) + { + // NULLs are not pushed onto the results stack as they indicate that + // no result was given. + if(value) + { + qDebug() << "Evaluator: Pushing result" << value->asText() << "in scope" + << (scope? scope->asText() : "null"); + results << ScopedResult(value, scope); + } + } + + Value &result() + { + if(results.isEmpty()) + { + return noResult; + } + return *results.first().result; + } + + Value &evaluate(Expression const *expression) + { + DENG2_ASSERT(names == NULL); + DENG2_ASSERT(stack.empty()); + + qDebug() << "Evaluator: Starting evaluation of" << expression; + + // Begin a new evaluation operation. + current = expression; + expression->push(self); + + // Clear the result stack. + clearResults(); + + while(!stack.empty()) + { + // Continue by processing the next step in the evaluation. + ScopedExpression top = stack.takeLast(); + clearNames(); + names = top.names(); + qDebug() << "Evaluator: Evaluating latest scoped expression" << top.expression + << "in" << (top.scope? names->asText() : "null scope"); + pushResult(top.expression->evaluate(self), top.scope); + } + + // During function call evaluation the process's context changes. We should + // now be back at the level we started from. + DENG2_ASSERT(&self.process().context() == &context); + + // Exactly one value should remain in the result stack: the result of the + // evaluated expression. + DENG2_ASSERT(self.hasResult()); + + clearNames(); + current = NULL; + return result(); + } +}; + +} // namespace de + +namespace de { + +Evaluator::Evaluator(Context &owner) : d(new Instance(this, owner)) {} -Evaluator::~Evaluator() +Context &Evaluator::context() { - clearNames(); - clearResults(); + return d->context; } Process &Evaluator::process() { - return _context.process(); + return d->context.process(); } Process const &Evaluator::process() const { - return _context.process(); + return d->context.process(); } void Evaluator::reset() { - _current = NULL; + d->current = NULL; - clearStack(); - clearNames(); + d->clearStack(); + d->clearNames(); } Value &Evaluator::evaluate(Expression const *expression) { - DENG2_ASSERT(_names == NULL); - DENG2_ASSERT(_stack.empty()); - - // Begin a new evaluation operation. - _current = expression; - expression->push(*this); - - // Clear the result stack. - clearResults(); - - while(!_stack.empty()) - { - // Continue by processing the next step in the evaluation. - ScopedExpression top = _stack.back(); - _stack.pop_back(); - clearNames(); - _names = top.names; - pushResult(top.expression->evaluate(*this)); - } - - // During function call evaluation the process's context changes. We should - // now be back at the level we started from. - DENG2_ASSERT(&process().context() == &_context); - - // Exactly one value should remain in the result stack: the result of the - // evaluated expression. - DENG2_ASSERT(hasResult()); - - clearNames(); - _current = NULL; - return result(); + return d->evaluate(expression); } void Evaluator::namespaces(Namespaces &spaces) const { - if(_names) + if(d->names) { // A specific namespace has been defined. spaces.clear(); - spaces.push_back(_names); + spaces.push_back(d->names); } else { @@ -113,66 +226,42 @@ Record *Evaluator::localNamespace() const bool Evaluator::hasResult() const { - return _results.size() == 1; + return d->results.size() == 1; } Value &Evaluator::result() { - if(_results.empty()) - { - return _noResult; - } - return *_results.front(); + return d->result(); } -void Evaluator::push(Expression const *expression, Record *names) +void Evaluator::push(Expression const *expression, Value *scope) { - _stack.push_back(ScopedExpression(expression, names)); + d->stack.push_back(Instance::ScopedExpression(expression, scope)); } void Evaluator::pushResult(Value *value) { - // NULLs are not pushed onto the results stack as they indicate that - // no result was given. - if(value) - { - _results.push_back(value); - } + d->pushResult(value); } -Value *Evaluator::popResult() +Value *Evaluator::popResult(Value **evaluationScope) { - DENG2_ASSERT(_results.size() > 0); + DENG2_ASSERT(d->results.size() > 0); - Value *result = _results.back(); - _results.pop_back(); - return result; -} + Instance::ScopedResult result = d->results.takeLast(); + qDebug() << "Evaluator: Popping result" << result.result->asText() + << "in scope" << (result.scope? result.scope->asText() : "null"); -void Evaluator::clearNames() -{ - if(_names) + if(evaluationScope) { - _names = 0; + *evaluationScope = result.scope; } -} - -void Evaluator::clearResults() -{ - for(Results::iterator i = _results.begin(); i != _results.end(); ++i) + else { - delete *i; + delete result.scope; // Was owned by us and the caller didn't want it. } - _results.clear(); -} -void Evaluator::clearStack() -{ - while(!_stack.empty()) - { - ScopedExpression top = _stack.back(); - _stack.pop_back(); - clearNames(); - _names = top.names; - } + return result.result; } + +} // namespace de diff --git a/doomsday/libcore/src/scriptsys/expression.cpp b/doomsday/libcore/src/scriptsys/expression.cpp index 824694f6f4..faa78db818 100644 --- a/doomsday/libcore/src/scriptsys/expression.cpp +++ b/doomsday/libcore/src/scriptsys/expression.cpp @@ -33,9 +33,9 @@ using namespace de; Expression::~Expression() {} -void Expression::push(Evaluator &evaluator, Record *names) const +void Expression::push(Evaluator &evaluator, Value *scope) const { - evaluator.push(this, names); + evaluator.push(this, scope); } Expression *Expression::constructFrom(Reader &reader) diff --git a/doomsday/libcore/src/scriptsys/functionvalue.cpp b/doomsday/libcore/src/scriptsys/functionvalue.cpp index 297438e0a7..6d866be4fb 100644 --- a/doomsday/libcore/src/scriptsys/functionvalue.cpp +++ b/doomsday/libcore/src/scriptsys/functionvalue.cpp @@ -77,7 +77,7 @@ dint FunctionValue::compare(Value const &value) const return -1; } -void FunctionValue::call(Process &process, Value const &arguments) const +void FunctionValue::call(Process &process, Value const &arguments, Value *instanceScope) const { ArrayValue const *array = dynamic_cast(&arguments); if(!array) @@ -85,7 +85,7 @@ void FunctionValue::call(Process &process, Value const &arguments) const /// @throw IllegalError The call arguments must be an array value. throw IllegalError("FunctionValue::call", "Arguments is not an array"); } - process.call(*_func, *array); + process.call(*_func, *array, instanceScope); } void FunctionValue::operator >> (Writer &to) const diff --git a/doomsday/libcore/src/scriptsys/nameexpression.cpp b/doomsday/libcore/src/scriptsys/nameexpression.cpp index 87dd630021..8b407cc401 100644 --- a/doomsday/libcore/src/scriptsys/nameexpression.cpp +++ b/doomsday/libcore/src/scriptsys/nameexpression.cpp @@ -22,31 +22,71 @@ #include "de/Process" #include "de/TextValue" #include "de/RefValue" +#include "de/ArrayValue" #include "de/RecordValue" #include "de/Writer" #include "de/Reader" #include "de/App" #include "de/Module" -using namespace de; +namespace de { -NameExpression::NameExpression() +DENG2_PIMPL_NOREF(NameExpression) +{ + String identifier; + + Instance(String const &id = "") : identifier(id) {} + + Variable *findIdentifier(Record const &where, Record *&foundIn, bool lookInClass = true) const + { + if(where.hasMember(identifier)) + { + // The name exists in this namespace. Even though the lookup was done as + // const, the caller expects non-const return values. + foundIn = const_cast(&where); + return const_cast(&where[identifier]); + } + if(lookInClass && where.hasMember("__isa__")) + { + // The namespace is derived from another record. Let's look into each + // super-record in turn. + ArrayValue const &supers = where.geta("__isa__"); + for(dsize i = 0; i < supers.size(); ++i) + { + if(Variable *found = findIdentifier(supers.at(i).as().dereference(), + foundIn)) + { + return found; + } + } + } + return 0; + } +}; + +} // namespace de + +namespace de { + +NameExpression::NameExpression() : d(new Instance) {} NameExpression::NameExpression(String const &identifier, Flags const &flags) - : _identifier(identifier) + : d(new Instance(identifier)) { setFlags(flags); } -NameExpression::~NameExpression() -{} +String const &NameExpression::identifier() const +{ + return d->identifier; +} Value *NameExpression::evaluate(Evaluator &evaluator) const { //LOG_AS("NameExpression::evaluate"); //std::cout << "NameExpression::evaluator: " << _flags.to_string() << "\n"; - LOGDEV_SCR_XVERBOSE_DEBUGONLY("evaluating name:\"%s\" flags:%x", _identifier << flags()); + LOGDEV_SCR_XVERBOSE_DEBUGONLY("evaluating name:\"%s\" flags:%x", d->identifier << flags()); // Collect the namespaces to search. Evaluator::Namespaces spaces; @@ -59,11 +99,13 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const DENG2_FOR_EACH(Evaluator::Namespaces, i, spaces) { Record &ns = **i; - if(ns.hasMember(_identifier)) + if((variable = d->findIdentifier(ns, foundInNamespace, + // allow looking in class if local not required + !flags().testFlag(LocalOnly))) != 0) { // The name exists in this namespace. - variable = &ns[_identifier]; - foundInNamespace = &ns; + //variable = &ns[d->identifier]; + //foundInNamespace = &ns; // Also note the higher namespace (for export). Evaluator::Namespaces::iterator next = i; @@ -72,6 +114,7 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const } if(flags().testFlag(LocalOnly)) { + // Not allowed to look in outer scopes. break; } } @@ -86,14 +129,14 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const if(flags().testFlag(NotInScope) && variable) { throw AlreadyExistsError("NameExpression::evaluate", - "Identifier '" + _identifier + "' already exists"); + "Identifier '" + d->identifier + "' already exists"); } // Create a new subrecord in the namespace? ("record xyz") if(flags().testFlag(NewSubrecord)) { // Replaces existing member with this identifier. - Record &record = spaces.front()->addRecord(_identifier); + Record &record = spaces.front()->addRecord(d->identifier); return new RecordValue(record); } @@ -102,7 +145,7 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const // Occurs when assigning into new variables. if(!variable && flags().testFlag(NewVariable)) { - variable = new Variable(_identifier); + variable = new Variable(d->identifier); // Add it to the local namespace. spaces.front()->add(variable); @@ -124,14 +167,14 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const if(!variable) { throw NotFoundError("NameExpression::evaluate", - "Cannot export nonexistent identifier '" + _identifier + "'"); + "Cannot export nonexistent identifier '" + d->identifier + "'"); } if(!higherNamespace) { throw NotFoundError("NameExpression::evaluate", - "No higher namespace for exporting '" + _identifier + "' into"); + "No higher namespace for exporting '" + d->identifier + "' into"); } - if(higherNamespace != foundInNamespace) + if(higherNamespace != foundInNamespace && foundInNamespace) { foundInNamespace->remove(*variable); higherNamespace->add(variable); @@ -141,11 +184,11 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const // Should we import a namespace? if(flags() & Import) { - Record *record = &App::scriptSystem().importModule(_identifier, + Record *record = &App::scriptSystem().importModule(d->identifier, evaluator.process().globals()["__file__"].value().asText()); // Overwrite any existing member with this identifier. - spaces.front()->add(variable = new Variable(_identifier)); + spaces.front()->add(variable = new Variable(d->identifier)); if(flags().testFlag(ByValue)) { @@ -178,7 +221,7 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const } } - throw NotFoundError("NameExpression::evaluate", "Identifier '" + _identifier + + throw NotFoundError("NameExpression::evaluate", "Identifier '" + d->identifier + "' does not exist"); } @@ -188,7 +231,7 @@ void NameExpression::operator >> (Writer &to) const Expression::operator >> (to); - to << _identifier; + to << d->identifier; } void NameExpression::operator << (Reader &from) @@ -204,5 +247,7 @@ void NameExpression::operator << (Reader &from) Expression::operator << (from); - from >> _identifier; + from >> d->identifier; } + +} // namespace de diff --git a/doomsday/libcore/src/scriptsys/operatorexpression.cpp b/doomsday/libcore/src/scriptsys/operatorexpression.cpp index 51a1927990..02d303d7cb 100644 --- a/doomsday/libcore/src/scriptsys/operatorexpression.cpp +++ b/doomsday/libcore/src/scriptsys/operatorexpression.cpp @@ -61,7 +61,7 @@ OperatorExpression::~OperatorExpression() delete _rightOperand; } -void OperatorExpression::push(Evaluator &evaluator, Record *names) const +void OperatorExpression::push(Evaluator &evaluator, Value *scope) const { Expression::push(evaluator); @@ -69,15 +69,15 @@ void OperatorExpression::push(Evaluator &evaluator, Record *names) const { // The MEMBER operator works a bit differently. Just push the left side // now. We'll push the other side when we've found out what is the - // scope defined by the result of the left side (which must be a RecordValue). - _leftOperand->push(evaluator, names); + // scope defined by the result of the left side. + _leftOperand->push(evaluator, scope); } else { _rightOperand->push(evaluator); if(_leftOperand) { - _leftOperand->push(evaluator, names); + _leftOperand->push(evaluator, scope); } } } @@ -100,11 +100,16 @@ void OperatorExpression::verifyAssignable(Value *value) Value *OperatorExpression::evaluate(Evaluator &evaluator) const { + qDebug() << "OperatorExpression:" << operatorToText(_op); + // Get the operands. Value *rightValue = (_op == MEMBER? 0 : evaluator.popResult()); - Value *leftValue = (_leftOperand? evaluator.popResult() : 0); + Value *leftScopePtr = 0; + Value *leftValue = (_leftOperand? evaluator.popResult(&leftScopePtr) : 0); Value *result = (leftValue? leftValue : rightValue); + QScopedPointer leftScope(leftScopePtr); // will be deleted if not needed + DENG2_ASSERT(_op == MEMBER || (!isUnary(_op) && leftValue && rightValue) || ( isUnary(_op) && rightValue)); @@ -214,7 +219,7 @@ Value *OperatorExpression::evaluate(Evaluator &evaluator) const break; case CALL: - leftValue->call(evaluator.process(), *rightValue); + leftValue->call(evaluator.process(), *rightValue, leftScope.take()); // Result comes from whatever is being called. result = 0; break; @@ -247,20 +252,20 @@ Value *OperatorExpression::evaluate(Evaluator &evaluator) const case MEMBER: { - RecordValue const *recValue = dynamic_cast(leftValue); - if(!recValue) + Record *scope = (leftValue? leftValue->memberScope() : 0); + if(!scope) { throw ScopeError("OperatorExpression::evaluate", - "Left side of " + operatorToText(_op) + " must evaluate to a record [" + + "Left side of " + operatorToText(_op) + " does not have members [" + DENG2_TYPE_NAME(*leftValue) + "]"); } // Now that we know what the scope is, push the rest of the expression // for evaluation (in this specific scope). - _rightOperand->push(evaluator, recValue->record()); + _rightOperand->push(evaluator, leftValue); // Cleanup. - delete leftValue; + //delete leftValue; DENG2_ASSERT(rightValue == NULL); // The MEMBER operator does not evaluate to any result. diff --git a/doomsday/libcore/src/scriptsys/process.cpp b/doomsday/libcore/src/scriptsys/process.cpp index 2ab2456aa1..4ed9cd8a28 100644 --- a/doomsday/libcore/src/scriptsys/process.cpp +++ b/doomsday/libcore/src/scriptsys/process.cpp @@ -295,7 +295,7 @@ void Process::setWorkingPath(String const &newWorkingPath) _workingPath = newWorkingPath; } -void Process::call(Function const &function, ArrayValue const &arguments) +void Process::call(Function const &function, ArrayValue const &arguments, Value *instanceScope) { // First map the argument values. Function::ArgumentValues argValues; @@ -304,7 +304,9 @@ void Process::call(Function const &function, ArrayValue const &arguments) if(function.isNative()) { // Do a native function call. + context().setInstanceScope(instanceScope); context().evaluator().pushResult(function.callNative(context(), argValues)); + context().setInstanceScope(0); } else { @@ -317,6 +319,12 @@ void Process::call(Function const &function, ArrayValue const &arguments) // Create a new context. _stack.push_back(new Context(Context::FunctionCall, this)); + + // If the scope is defined, create the "self" variable for it. + if(instanceScope) + { + context().names().add(new Variable("self", instanceScope /*taken*/)); + } // Create local variables for the arguments in the new context. Function::ArgumentValues::const_iterator b = argValues.begin(); diff --git a/doomsday/libcore/src/scriptsys/scriptsystem.cpp b/doomsday/libcore/src/scriptsys/scriptsystem.cpp index 78da01474e..25b919d50a 100644 --- a/doomsday/libcore/src/scriptsys/scriptsystem.cpp +++ b/doomsday/libcore/src/scriptsys/scriptsystem.cpp @@ -23,6 +23,8 @@ #include "de/Version" #include "de/ArrayValue" #include "de/NumberValue" +#include "de/RecordValue" +#include "de/DictionaryValue" #include "de/math.h" #include @@ -34,7 +36,18 @@ Value *Function_Path_FileNamePath(Context &, Function::ArgumentValues const &arg return new TextValue(args.at(0)->asText().fileNamePath()); } -DENG2_PIMPL(ScriptSystem), DENG2_OBSERVES(Record, Deletion) +Value *Function_Dictionary_Keys(Context &ctx, Function::ArgumentValues const &) +{ + return ctx.instanceScope().as().contentsAsArray(DictionaryValue::Keys); +} + +Value *Function_Dictionary_Values(Context &ctx, Function::ArgumentValues const &) +{ + return ctx.instanceScope().as().contentsAsArray(DictionaryValue::Values); +} + +DENG2_PIMPL(ScriptSystem) +, DENG2_OBSERVES(Record, Deletion) { Binder binder; @@ -42,6 +55,7 @@ DENG2_PIMPL(ScriptSystem), DENG2_OBSERVES(Record, Deletion) /// parsed from any script. typedef QMap NativeModules; NativeModules nativeModules; // not owned + Record scriptModule; // Script: built-in script classes. Record versionModule; // Version: information about the platform and build Record pathModule; // Path: path manipulation routines (based on native classes Path, NativePath, String) @@ -51,6 +65,8 @@ DENG2_PIMPL(ScriptSystem), DENG2_OBSERVES(Record, Deletion) Instance(Public *i) : Base(*i) { + initScriptModule(); + // Setup the Version module. { Version ver; @@ -89,6 +105,18 @@ DENG2_PIMPL(ScriptSystem), DENG2_OBSERVES(Record, Deletion) } } + void initScriptModule() + { + { + Record &dict = scriptModule.addRecord("Dictionary"); + binder.init(dict) + << DENG2_FUNC_NOARG(Dictionary_Keys, "keys") + << DENG2_FUNC_NOARG(Dictionary_Values, "values"); + } + + addNativeModule("Script", scriptModule); + } + void addNativeModule(String const &name, Record &module) { nativeModules.insert(name, &module); // not owned @@ -222,6 +250,12 @@ File const &ScriptSystem::findModuleSource(String const &name, String const &loc return *src; } +Record &ScriptSystem::builtInClass(String const &name) +{ + return const_cast(App::scriptSystem().nativeModule("Script") + .getAs(name).dereference()); +} + Record &ScriptSystem::importModule(String const &name, String const &importedFromPath) { LOG_AS("ScriptSystem::importModule");