Skip to content

Commit

Permalink
Scripting|libcore: Added the name scope keyword (->)
Browse files Browse the repository at this point in the history
This is nothing like C's -> operator but instead a special keyword
that lets NameExpression know where to start looking for an identifier.
It allows accessing shadowed members from super records:

self.Base->__init__()
  • Loading branch information
skyjake committed Jun 13, 2014
1 parent 9ae4a30 commit 6e08be5
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 38 deletions.
7 changes: 6 additions & 1 deletion doomsday/libcore/include/de/scriptsys/nameexpression.h
Expand Up @@ -45,9 +45,14 @@ class NameExpression : public Expression
/// The identifier does not specify an existing variable. @ingroup errors
DENG2_ERROR(NotFoundError);

/// Special scope that can be specified in the constructor to tell the
/// expression to start looking in the context's local namespace.
static String const LOCAL_SCOPE;

public:
NameExpression();
NameExpression(String const &identifier, Flags const &flags = ByValue);
NameExpression(String const &identifier, Flags const &flags = ByValue,
String const &scopeIdentifier = "");

/// Returns the identifier in the name expression.
String const &identifier() const;
Expand Down
1 change: 1 addition & 0 deletions doomsday/libcore/include/de/scriptsys/scriptlex.h
Expand Up @@ -70,6 +70,7 @@ class DENG2_PUBLIC ScriptLex : public Lex
static String const IMPORT;
static String const EXPORT;
static String const RECORD;
static String const SCOPE;
static String const DEL;
static String const PASS;
static String const CONTINUE;
Expand Down
128 changes: 93 additions & 35 deletions doomsday/libcore/src/scriptsys/nameexpression.cpp
Expand Up @@ -24,44 +24,87 @@
#include "de/RefValue"
#include "de/ArrayValue"
#include "de/RecordValue"
#include "de/ScopeStatement"
#include "de/Writer"
#include "de/Reader"
#include "de/App"
#include "de/Module"

namespace de {

String const NameExpression::LOCAL_SCOPE = "-";

DENG2_PIMPL_NOREF(NameExpression)
{
String identifier;

Instance(String const &id = "") : identifier(id) {}

Variable *findIdentifier(Record const &where, Record *&foundIn, bool lookInClass = true) const
String scopeIdentifier;

Instance(String const &id = "",
String const &scopeId = "")
: identifier(id)
, scopeIdentifier(scopeId)
{}

Variable *findInRecord(String const & name,
Record const & where,
Record *& foundIn,
bool lookInClass = true) const
{
if(where.hasMember(identifier))
if(where.hasMember(name))
{
// The name exists in this namespace. Even though the lookup was done as
// const, the caller expects non-const return values.
foundIn = const_cast<Record *>(&where);
return const_cast<Variable *>(&where[identifier]);
return const_cast<Variable *>(&where[name]);
}
if(lookInClass && where.hasMember("__super__"))
if(lookInClass && where.hasMember(ScopeStatement::SUPER_NAME))
{
// The namespace is derived from another record. Let's look into each
// super-record in turn.
ArrayValue const &supers = where.geta("__super__");
ArrayValue const &supers = where.geta(ScopeStatement::SUPER_NAME);
for(dsize i = 0; i < supers.size(); ++i)
{
if(Variable *found = findIdentifier(supers.at(i).as<RecordValue>().dereference(),
foundIn))
if(Variable *found = findInRecord(
name, supers.at(i).as<RecordValue>().dereference(), foundIn))
{
return found;
}
}
}
return 0;
}

Variable *findInNamespaces(String const & name,
Evaluator::Namespaces const &spaces,
bool localOnly,
Record *& foundInNamespace,
Record ** higherNamespace = 0)
{
DENG2_FOR_EACH_CONST(Evaluator::Namespaces, i, spaces)
{
Record &ns = **i;
if(Variable *variable =
findInRecord(name, ns, foundInNamespace,
// allow looking in class if local not required:
!localOnly))
{
// The name exists in this namespace.
// Also note the higher namespace (for export).
Evaluator::Namespaces::const_iterator next = i;
if(++next != spaces.end())
{
if(higherNamespace) *higherNamespace = *next;
}
return variable;
}
if(localOnly)
{
// Not allowed to look in outer scopes.
break;
}
}
return 0;
}
};

} // namespace de
Expand All @@ -71,8 +114,9 @@ namespace de {
NameExpression::NameExpression() : d(new Instance)
{}

NameExpression::NameExpression(String const &identifier, Flags const &flags)
: d(new Instance(identifier))
NameExpression::NameExpression(String const &identifier, Flags const &flags,
String const &scopeIdentifier)
: d(new Instance(identifier, scopeIdentifier))
{
setFlags(flags);
}
Expand All @@ -87,36 +131,45 @@ 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", d->identifier << flags());

// Collect the namespaces to search.
Evaluator::Namespaces spaces;
evaluator.namespaces(spaces);


Record *foundInNamespace = 0;
Record *higherNamespace = 0;
Variable *variable = 0;
DENG2_FOR_EACH(Evaluator::Namespaces, i, spaces)

if(d->scopeIdentifier.isEmpty() || d->scopeIdentifier == LOCAL_SCOPE)
{
Record &ns = **i;
if((variable = d->findIdentifier(ns, foundInNamespace,
// allow looking in class if local not required
!flags().testFlag(LocalOnly))) != 0)
if(d->scopeIdentifier != LOCAL_SCOPE)
{
// The name exists in this namespace.
//variable = &ns[d->identifier];
//foundInNamespace = &ns;

// Also note the higher namespace (for export).
Evaluator::Namespaces::iterator next = i;
if(++next != spaces.end()) higherNamespace = *next;
break;
// This is the usual case: scope defined by the left side of the member
// operator, or if that is not specified, the context's namespace stack.
evaluator.namespaces(spaces);
}
if(flags().testFlag(LocalOnly))
else
{
// Not allowed to look in outer scopes.
break;
// Start with the context's local namespace.
evaluator.process().namespaces(spaces);
}
variable = d->findInNamespaces(d->identifier, spaces, flags().testFlag(LocalOnly),
foundInNamespace, &higherNamespace);
}
else
{
// An explicit scope has been defined; try to find it first. Look in the current
// context of the process, ignoring any narrower scopes that may apply here.
evaluator.process().namespaces(spaces);
Variable *scope = d->findInNamespaces(d->scopeIdentifier, spaces, false, foundInNamespace);
if(!scope)
{
throw NotFoundError("NameExpression::evaluate",
"Scope '" + d->scopeIdentifier + "' not found");
}
// Locate the identifier from this scope, disregarding the regular
// namespace context.
variable = d->findInRecord(d->identifier, scope->valueAsRecord(),
foundInNamespace);
}

if(flags().testFlag(ThrowawayIfInScope) && variable)
Expand All @@ -133,11 +186,11 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
}

// Create a new subrecord in the namespace? ("record xyz")
if(flags().testFlag(NewSubrecord))
if(flags().testFlag(NewSubrecord) ||
(flags().testFlag(NewSubrecordIfNotInScope) && !variable))
{
// Replaces existing member with this identifier.
Record &record = spaces.front()->addRecord(d->identifier);

return new RecordValue(record);
}

Expand Down Expand Up @@ -231,7 +284,7 @@ void NameExpression::operator >> (Writer &to) const

Expression::operator >> (to);

to << d->identifier;
to << d->identifier << d->scopeIdentifier;
}

void NameExpression::operator << (Reader &from)
Expand All @@ -248,6 +301,11 @@ void NameExpression::operator << (Reader &from)
Expression::operator << (from);

from >> d->identifier;

if(from.version() >= DENG2_PROTOCOL_1_15_0_NameExpression_with_scope_identifier)
{
from >> d->scopeIdentifier;
}
}

} // namespace de
16 changes: 14 additions & 2 deletions doomsday/libcore/src/scriptsys/scriptlex.cpp
Expand Up @@ -40,6 +40,7 @@ String const ScriptLex::TRY("try");
String const ScriptLex::IMPORT("import");
String const ScriptLex::EXPORT("export");
String const ScriptLex::RECORD("record");
String const ScriptLex::SCOPE("->");
String const ScriptLex::DEL("del");
String const ScriptLex::PASS("pass");
String const ScriptLex::CONTINUE("continue");
Expand Down Expand Up @@ -176,6 +177,16 @@ duint ScriptLex::getStatement(TokenBuffer &output)
continue;
}

// The scope keyword.
if(c == '-' && peek() == '>')
{
output.setType(Token::KEYWORD);
output.appendChar(get());
output.endToken();
counter++;
continue;
}

if(isOperator(c))
{
output.setType(Token::OPERATOR);
Expand Down Expand Up @@ -374,6 +385,7 @@ static QChar const *keywordStr[] =
ScriptLex::PRINT,
ScriptLex::RECORD,
ScriptLex::RETURN,
ScriptLex::SCOPE,
ScriptLex::THROW,
ScriptLex::TRY,
ScriptLex::WHILE,
Expand Down Expand Up @@ -409,8 +421,8 @@ StringList ScriptLex::keywords()
String ScriptLex::unescapeStringToken(Token const &token)
{
DENG2_ASSERT(token.type() == Token::LITERAL_STRING_APOSTROPHE ||
token.type() == Token::LITERAL_STRING_QUOTED ||
token.type() == Token::LITERAL_STRING_LONG);
token.type() == Token::LITERAL_STRING_QUOTED ||
token.type() == Token::LITERAL_STRING_LONG);

String result;
QTextStream os(&result);
Expand Down

0 comments on commit 6e08be5

Please sign in to comment.