diff --git a/doomsday/libcore/include/de/scriptsys/context.h b/doomsday/libcore/include/de/scriptsys/context.h index 24a2816fb1..5e58376c4a 100644 --- a/doomsday/libcore/include/de/scriptsys/context.h +++ b/doomsday/libcore/include/de/scriptsys/context.h @@ -45,7 +45,8 @@ class DENG2_PUBLIC Context enum Type { BaseProcess, GlobalNamespace, - FunctionCall + FunctionCall, + Namespace }; public: diff --git a/doomsday/libcore/include/de/scriptsys/expression.h b/doomsday/libcore/include/de/scriptsys/expression.h index d4b691a2f7..69d1b6a033 100644 --- a/doomsday/libcore/include/de/scriptsys/expression.h +++ b/doomsday/libcore/include/de/scriptsys/expression.h @@ -81,7 +81,10 @@ class DENG2_PUBLIC Expression : public ISerializable ReadOnly = 0x200, /// Variable will be raised into a higher namespace. - Export = 0x400 + Export = 0x400, + + /// If missing, create a new subrecord. Otherwise, reuse the existing record. + NewSubrecordIfNotInScope = 0x800 }; Q_DECLARE_FLAGS(Flags, Flag) diff --git a/doomsday/libcore/include/de/scriptsys/parser.h b/doomsday/libcore/include/de/scriptsys/parser.h index 462f712db1..b133af729b 100644 --- a/doomsday/libcore/include/de/scriptsys/parser.h +++ b/doomsday/libcore/include/de/scriptsys/parser.h @@ -33,6 +33,7 @@ namespace de { class Compound; +class Statement; class ExpressionStatement; class PrintStatement; class IfStatement; @@ -99,7 +100,7 @@ class Parser : public IParser ExpressionStatement *parseExportStatement(); - ExpressionStatement *parseDeclarationStatement(); + Statement *parseDeclarationStatement(); DeleteStatement *parseDeleteStatement(); diff --git a/doomsday/libcore/include/de/scriptsys/process.h b/doomsday/libcore/include/de/scriptsys/process.h index 85d34c9c97..b71e5eee54 100644 --- a/doomsday/libcore/include/de/scriptsys/process.h +++ b/doomsday/libcore/include/de/scriptsys/process.h @@ -199,11 +199,17 @@ class DENG2_PUBLIC Process void namespaces(Namespaces &spaces) const; /** - * Returns the global namespace of the process. This is always the - * bottommost context in the stack. + * Returns the global namespace of the process. This is always the bottommost context + * in the stack. */ Record &globals(); + /** + * Returns the local namespace of the process. This is always the topmost context in + * the stack. + */ + Record &locals(); + private: DENG2_PRIVATE(d) }; diff --git a/doomsday/libcore/include/de/scriptsys/statement.h b/doomsday/libcore/include/de/scriptsys/statement.h index 5d74577f39..df4d57d630 100644 --- a/doomsday/libcore/include/de/scriptsys/statement.h +++ b/doomsday/libcore/include/de/scriptsys/statement.h @@ -72,7 +72,8 @@ class Statement : public ISerializable PRINT, TRY, WHILE, - DELETE + DELETE, + SCOPE }; private: diff --git a/doomsday/libcore/src/data/recordvalue.cpp b/doomsday/libcore/src/data/recordvalue.cpp index 16c25216b0..ac90f6bc8a 100644 --- a/doomsday/libcore/src/data/recordvalue.cpp +++ b/doomsday/libcore/src/data/recordvalue.cpp @@ -28,6 +28,7 @@ #include "de/Variable" #include "de/Writer" #include "de/Reader" +#include "de/ScopeStatement" #include "de/math.h" namespace de { @@ -247,7 +248,7 @@ void RecordValue::call(Process &process, Value const &arguments, Value *) const ArrayValue *super = new ArrayValue; *super << new RecordValue(d->record); - instance->record()->add(new Variable("__super__", super)); + instance->record()->add(new Variable(ScopeStatement::SUPER_NAME, super)); // If there is an initializer method, call it now. if(dereference().hasMember("__init__")) diff --git a/doomsday/libcore/src/scriptsys/parser.cpp b/doomsday/libcore/src/scriptsys/parser.cpp index 340ca1983c..26dc40b4bf 100644 --- a/doomsday/libcore/src/scriptsys/parser.cpp +++ b/doomsday/libcore/src/scriptsys/parser.cpp @@ -31,6 +31,7 @@ #include "de/AssignStatement" #include "de/DeleteStatement" #include "de/FunctionStatement" +#include "de/ScopeStatement" #include "de/TryStatement" #include "de/CatchStatement" #include "de/ArrayExpression" @@ -312,17 +313,36 @@ ExpressionStatement *Parser::parseExportStatement() Expression::Export | Expression::LocalOnly)); } -ExpressionStatement *Parser::parseDeclarationStatement() +Statement *Parser::parseDeclarationStatement() { // "record" name-expr ["," name-expr]* + // "record" name-expr "(" [ name-expr ["," name-expr]* ] ")" members-compound if(_statementRange.size() < 2) { throw MissingTokenError("Parser::parseDeclarationStatement", "Expected identifier to follow " + _statementRange.firstToken().asText()); } - Expression::Flags flags = Expression::LocalOnly | Expression::NewSubrecord; - return new ExpressionStatement(parseList(_statementRange.startingFrom(1), Token::COMMA, flags)); + + // Is this a class record declaration? + dint pos = _statementRange.find(Token::PARENTHESIS_OPEN); + if(pos >= 0) + { + QScopedPointer name(parseExpression(_statementRange.between(1, pos), + Expression::NewSubrecordIfNotInScope)); + QScopedPointer names(new ScopeStatement(name.take(), + parseList(_statementRange.between(pos + 1, _statementRange.closingBracket(pos))))); + + parseConditionalCompound(names->compound(), + IgnoreExtraBeforeColon | StayAtClosingStatement); + return names.take(); + } + else + { + // Regular record declaration. + Expression::Flags flags = Expression::LocalOnly | Expression::NewSubrecord; + return new ExpressionStatement(parseList(_statementRange.startingFrom(1), Token::COMMA, flags)); + } } DeleteStatement *Parser::parseDeleteStatement() @@ -861,6 +881,14 @@ Expression *Parser::parseTokenExpression(TokenRange const &range, Expression::Fl { return ConstantExpression::Pi(); } + else if(token.equals(ScriptLex::SCOPE) && + range.size() == 2 && + range.token(1).type() == Token::IDENTIFIER) + { + // Explicit local scope. + return new NameExpression(range.token(1).str(), flags, + NameExpression::LOCAL_SCOPE); + } } switch(token.type()) @@ -870,12 +898,21 @@ Expression *Parser::parseTokenExpression(TokenRange const &range, Expression::Fl { return new NameExpression(range.token(0).str(), flags); } + else if(range.size() == 3 && + range.token(1).equals(ScriptLex::SCOPE) && + range.token(2).type() == Token::IDENTIFIER) + { + // Scoped name. This is intended for allowing access to shadowed + // identifiers from super records. + return new NameExpression(range.token(2).str(), flags, + range.token(0).str()); + } else { throw UnexpectedTokenError("Parser::parseTokenExpression", "Unexpected token " + range.token(1).asText()); } - + case Token::LITERAL_STRING_APOSTROPHE: case Token::LITERAL_STRING_QUOTED: case Token::LITERAL_STRING_LONG: diff --git a/doomsday/libcore/src/scriptsys/process.cpp b/doomsday/libcore/src/scriptsys/process.cpp index ec43d2e262..7ca393de55 100644 --- a/doomsday/libcore/src/scriptsys/process.cpp +++ b/doomsday/libcore/src/scriptsys/process.cpp @@ -236,7 +236,7 @@ void Process::execute() } // We will execute until this depth is complete. - duint startDepth = depth(); + duint startDepth = d->depth(); if(startDepth == 1) { // Mark the start time. @@ -244,15 +244,18 @@ void Process::execute() } // Execute the next command(s). - while(d->state == Running && depth() >= startDepth) + while(d->state == Running && d->depth() >= startDepth) { try { - if(!context().execute()) + dsize execDepth = d->depth(); + if(!context().execute() && d->depth() == execDepth) { + // There was no statement left to execute, and no new contexts were + // added to the stack. finish(); } - if(d->startedAt.since() > MAX_EXECUTION_TIME) + else if(d->startedAt.since() > MAX_EXECUTION_TIME) { /// @throw HangError Execution takes too long. throw HangError("Process::execute", @@ -368,6 +371,10 @@ void Process::call(Function const &function, ArrayValue const &arguments, Value { pushContext(new Context(Context::GlobalNamespace, this, function.globals())); } + + // If the function is not in the global namespace, add its own parent + // namespace to the stack, too. + // Create a new context. pushContext(new Context(Context::FunctionCall, this)); @@ -412,10 +419,19 @@ void Process::call(Function const &function, ArrayValue const &arguments, Value void Process::namespaces(Namespaces &spaces) const { spaces.clear(); + + bool gotFunction = false; DENG2_FOR_EACH_CONST_REVERSE(Instance::ContextStack, i, d->stack) { Context &context = **i; + if(context.type() == Context::FunctionCall) + { + // Only the topmost function call namespace is available: one cannot + // access the local variables of the callers. + if(gotFunction) continue; + gotFunction = true; + } spaces.push_back(&context.names()); if(context.type() == Context::GlobalNamespace) { @@ -430,4 +446,9 @@ Record &Process::globals() return d->stack[0]->names(); } +Record &Process::locals() +{ + return d->stack.back()->names(); +} + } // namespace de diff --git a/doomsday/libcore/src/scriptsys/statement.cpp b/doomsday/libcore/src/scriptsys/statement.cpp index 7fd48e9c07..7a545b7ea4 100644 --- a/doomsday/libcore/src/scriptsys/statement.cpp +++ b/doomsday/libcore/src/scriptsys/statement.cpp @@ -27,6 +27,7 @@ #include "de/IfStatement" #include "de/FlowStatement" #include "de/PrintStatement" +#include "de/ScopeStatement" #include "de/TryStatement" #include "de/WhileStatement" #include "de/Reader" @@ -86,6 +87,10 @@ Statement *Statement::constructFrom(Reader &reader) case WHILE: result.reset(new WhileStatement); break; + + case SCOPE: + result.reset(new ScopeStatement); + break; default: /// @throw DeserializationError The identifier that species the type of the