Skip to content

Commit

Permalink
Scripting: Allow scope notation to be used sequentially
Browse files Browse the repository at this point in the history
This is now legal: `a->b->c`
  • Loading branch information
skyjake committed Nov 23, 2019
1 parent f4fd4d7 commit 0b62389
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 56 deletions.
5 changes: 4 additions & 1 deletion doomsday/sdk/libcore/include/de/libcore.h
Expand Up @@ -757,7 +757,10 @@ enum ProtocolVersion {

DENG2_PROTOCOL_2_1_0 = 3,

DENG2_PROTOCOL_LATEST = DENG2_PROTOCOL_2_1_0
DENG2_PROTOCOL_2_2_0_NameExpression_identifier_sequence = 4,
DENG2_PROTOCOL_2_2_0 = 4,

DENG2_PROTOCOL_LATEST = DENG2_PROTOCOL_2_2_0
};

//@{
Expand Down
6 changes: 3 additions & 3 deletions doomsday/sdk/libcore/include/de/scriptsys/nameexpression.h
Expand Up @@ -47,12 +47,12 @@ class NameExpression : public Expression

/// 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;
static const String LOCAL_SCOPE;

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

/// Returns the identifier in the name expression.
String const &identifier() const;
Expand Down
1 change: 0 additions & 1 deletion doomsday/sdk/libcore/src/scriptsys/assignstatement.cpp
Expand Up @@ -20,7 +20,6 @@
#include "de/AssignStatement"
#include "de/Context"
#include "de/Expression"
#include "de/NameExpression"
#include "de/ArrayValue"
#include "de/RefValue"
#include "de/Writer"
Expand Down
11 changes: 5 additions & 6 deletions doomsday/sdk/libcore/src/scriptsys/catchstatement.cpp
Expand Up @@ -50,20 +50,19 @@ bool CatchStatement::isFinal() const
return flags.testFlag(FinalCompound);
}

bool CatchStatement::matches(Error const &err) const
bool CatchStatement::matches(const Error &err) const
{
if (!_args->size())
{
// Not specified, so catches all.
return true;
}

NameExpression const *name = dynamic_cast<NameExpression const *>(&_args->at(0));
DENG2_ASSERT(name != NULL);
const NameExpression &name = static_cast<const NameExpression &>(_args->at(0));

return (name->identifier() == "Error" || // Generic catch-all.
name->identifier() == err.name() || // Exact match.
String(err.name()).endsWith("_" + name->identifier())); // Sub-error match.
return (name.identifier() == "Error" || // Generic catch-all.
name.identifier() == err.name() || // Exact match.
String(err.name()).endsWith("_" + name.identifier())); // Sub-error match.
}

void CatchStatement::executeCatch(Context &context, Error const &err) const
Expand Down
1 change: 0 additions & 1 deletion doomsday/sdk/libcore/src/scriptsys/functionstatement.cpp
Expand Up @@ -20,7 +20,6 @@
#include "de/FunctionStatement"
#include "de/Function"
#include "de/Context"
#include "de/NameExpression"
#include "de/ConstantExpression"
#include "de/TextValue"
#include "de/ArrayValue"
Expand Down
112 changes: 77 additions & 35 deletions doomsday/sdk/libcore/src/scriptsys/nameexpression.cpp
Expand Up @@ -37,14 +37,14 @@ String const NameExpression::LOCAL_SCOPE = "-";

DENG2_PIMPL_NOREF(NameExpression)
{
String identifier;
String scopeIdentifier;
StringList identifierSequence;
//String scopeIdentifier;

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

Variable *findInRecord(String const & name,
Record const & where,
Expand Down Expand Up @@ -116,16 +116,23 @@ namespace de {
NameExpression::NameExpression() : d(new Impl)
{}

NameExpression::NameExpression(String const &identifier, Flags const &flags,
String const &scopeIdentifier)
: d(new Impl(identifier, scopeIdentifier))
NameExpression::NameExpression(const String &identifier, Flags flags)
: d(new Impl)
{
d->identifierSequence << "" << identifier;
setFlags(flags);
}

String const &NameExpression::identifier() const
NameExpression::NameExpression(const StringList &identifierSequence, Flags flags)
: d(new Impl)
{
return d->identifier;
d->identifierSequence = identifierSequence;
setFlags(flags);
}

const String &NameExpression::identifier() const
{
return d->identifierSequence.back();
}

Value *NameExpression::evaluate(Evaluator &evaluator) const
Expand All @@ -136,13 +143,16 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
// Collect the namespaces to search.
Evaluator::Namespaces spaces;

Record *foundInNamespace = 0;
Record *higherNamespace = 0;
Variable *variable = 0;
Record *foundInNamespace = nullptr;
Record *higherNamespace = nullptr;
Variable *variable = nullptr;

const String scopeIdentifier = d->identifierSequence.front();
String identifier = d->identifierSequence.at(1);

if (d->scopeIdentifier.isEmpty() || d->scopeIdentifier == LOCAL_SCOPE)
if (!scopeIdentifier || scopeIdentifier == LOCAL_SCOPE)
{
if (d->scopeIdentifier != LOCAL_SCOPE)
if (!scopeIdentifier)
{
// 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.
Expand All @@ -153,53 +163,65 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
// Start with the context's local namespace.
evaluator.process().namespaces(spaces);
}
variable = d->findInNamespaces(d->identifier, spaces, flags().testFlag(LocalOnly),
variable = d->findInNamespaces(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);
Variable *scope = d->findInNamespaces(scopeIdentifier, spaces, false, foundInNamespace);
if (!scope)
{
throw NotFoundError("NameExpression::evaluate",
"Scope '" + d->scopeIdentifier + "' not found");
"Scope '" + scopeIdentifier + "' not found");
}
// Locate the identifier from this scope, disregarding the regular
// namespace context.
variable = d->findInRecord(d->identifier, scope->valueAsRecord(),
variable = d->findInRecord(identifier, scope->valueAsRecord(),
foundInNamespace);
}

// Look up the rest in relation to what was already found.
for (int i = 2; i < d->identifierSequence.size(); ++i)
{
if (!variable)
{
throw NotFoundError("NameExpression::evaluate",
"Scope '" + identifier + "' not found");
}
identifier = d->identifierSequence.at(i);
variable = d->findInRecord(identifier, variable->valueAsRecord(), foundInNamespace);
}

if (flags().testFlag(ThrowawayIfInScope) && variable)
{
foundInNamespace = 0;
foundInNamespace = nullptr;
variable = &evaluator.context().throwaway();
}

// If a new variable/record is required and one is in scope, we cannot continue.
if (flags().testFlag(NotInScope) && variable)
{
throw AlreadyExistsError("NameExpression::evaluate",
"Identifier '" + d->identifier + "' already exists");
"Identifier '" + identifier + "' already exists");
}

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

// If nothing is found and we are permitted to create new variables, do so.
// Occurs when assigning into new variables.
if (!variable && flags().testFlag(NewVariable))
{
variable = new Variable(d->identifier);
variable = new Variable(identifier);

// Add it to the local namespace.
spaces.front()->add(variable);
Expand All @@ -221,12 +243,12 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
if (!variable)
{
throw NotFoundError("NameExpression::evaluate",
"Cannot export nonexistent identifier '" + d->identifier + "'");
"Cannot export nonexistent identifier '" + identifier + "'");
}
if (!higherNamespace)
{
throw NotFoundError("NameExpression::evaluate",
"No higher namespace for exporting '" + d->identifier + "' into");
"No higher namespace for exporting '" + identifier + "' into");
}
if (higherNamespace != foundInNamespace && foundInNamespace)
{
Expand All @@ -238,11 +260,11 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
// Should we import a namespace?
if (flags() & Import)
{
Record *record = &App::scriptSystem().importModule(d->identifier,
Record *record = &App::scriptSystem().importModule(identifier,
evaluator.process().globals()[Record::VAR_FILE].value().asText());

// Overwrite any existing member with this identifier.
spaces.front()->add(variable = new Variable(d->identifier));
spaces.front()->add(variable = new Variable(identifier));

if (flags().testFlag(ByValue))
{
Expand Down Expand Up @@ -275,7 +297,7 @@ Value *NameExpression::evaluate(Evaluator &evaluator) const
}
}

throw NotFoundError("NameExpression::evaluate", "Identifier '" + d->identifier +
throw NotFoundError("NameExpression::evaluate", "Identifier '" + identifier +
"' does not exist");
}

Expand All @@ -285,7 +307,11 @@ void NameExpression::operator >> (Writer &to) const

Expression::operator >> (to);

to << d->identifier << d->scopeIdentifier;
to << uint8_t(d->identifierSequence.size());
for (const auto &i : d->identifierSequence)
{
to << i;
}
}

void NameExpression::operator << (Reader &from)
Expand All @@ -301,11 +327,27 @@ void NameExpression::operator << (Reader &from)

Expression::operator << (from);

from >> d->identifier;

if (from.version() >= DENG2_PROTOCOL_1_15_0_NameExpression_with_scope_identifier)
if (from.version() < DENG2_PROTOCOL_2_2_0_NameExpression_identifier_sequence)
{
from >> d->scopeIdentifier;
String ident, scopeIdent;
from >> ident;
if (from.version() >= DENG2_PROTOCOL_1_15_0_NameExpression_with_scope_identifier)
{
from >> scopeIdent;
}
d->identifierSequence << scopeIdent;
d->identifierSequence << ident;
}
else
{
uint8_t count = 0;
from >> count;
while (count-- != 0)
{
String ident;
from >> ident;
d->identifierSequence << ident;
}
}
}

Expand Down
34 changes: 25 additions & 9 deletions doomsday/sdk/libcore/src/scriptsys/parser.cpp
Expand Up @@ -889,12 +889,12 @@ 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)
range.size() == 2 &&
range.token(1).type() == Token::IDENTIFIER)
{
// Explicit local scope.
return new NameExpression(range.token(1).str(), flags,
NameExpression::LOCAL_SCOPE);
return new NameExpression(StringList{NameExpression::LOCAL_SCOPE, range.token(1).str()},
flags);
}
}

Expand All @@ -905,14 +905,30 @@ 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)
else if (range.size() >= 3 &&
range.token(1).equals(ScriptLex::SCOPE) &&
range.token(2).type() == Token::IDENTIFIER)
{
StringList identifierSequence;
identifierSequence << range.token(0).str()
<< range.token(2).str();
for (duint i = 3; i < range.size() - 1; i += 2)
{
if (range.token(i).equals(ScriptLex::SCOPE) &&
range.token(i + 1).type() == Token::IDENTIFIER)
{
identifierSequence << range.token(i + 1).str();
}
else
{
throw UnexpectedTokenError("Parser::parseTokenExpression",
"Unexpected tokens: " + range.token(i).asText() +
" followed by " + range.token(i + 1).asText());
}
}
// 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());
return new NameExpression(identifierSequence, flags);
}
else
{
Expand Down

0 comments on commit 0b62389

Please sign in to comment.