Skip to content

Commit

Permalink
[WIP] Eager completion of variables
Browse files Browse the repository at this point in the history
  • Loading branch information
4ian committed Nov 28, 2023
1 parent ba95f66 commit bb23493
Show file tree
Hide file tree
Showing 6 changed files with 380 additions and 73 deletions.
45 changes: 36 additions & 9 deletions Core/GDCore/Events/Parsers/ExpressionParser2.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,11 @@ class GD_CORE_API ExpressionParser2 {

std::unique_ptr<VariableNode> Variable(const gd::String &name, gd::ExpressionParserLocation nameLocation) {
auto variable = gd::make_unique<VariableNode>(name);
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->child->parent = variable.get();

if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
variable->child = VariableAccessorOrVariableBracketAccessor();
variable->child->parent = variable.get();
}

variable->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
Expand All @@ -302,8 +305,12 @@ class GD_CORE_API ExpressionParser2 {
"bracket for each opening bracket."));
}
SkipIfChar(IsClosingSquareBracket);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();

SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
}
child->location =
ExpressionParserLocation(childStartPosition, GetCurrentPosition());

Expand All @@ -315,8 +322,15 @@ class GD_CORE_API ExpressionParser2 {
auto identifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
if (identifierAndLocation.name.empty()) {
child->diagnostic = RaiseSyntaxError(_("A name should be entered after the dot."));
}

SkipAllWhitespaces();
if (CheckIfChar(IsOpeningSquareBracket) || CheckIfChar(IsDot)) {
child->child = VariableAccessorOrVariableBracketAccessor();
child->child->parent = child.get();
}
child->nameLocation = identifierAndLocation.location;
child->dotLocation = dotLocation;
child->location =
Expand All @@ -325,7 +339,11 @@ class GD_CORE_API ExpressionParser2 {
return std::move(child);
}

return std::move(gd::make_unique<VariableAccessorOrVariableBracketAccessorNode>());
// Should never happen, unless a node called this function without checking if the current character
// was a dot or an opening bracket - this means there is an error in the grammar.
auto unrecognisedNode = gd::make_unique<VariableAccessorOrVariableBracketAccessorNode>();
unrecognisedNode->diagnostic = RaiseSyntaxError(_("A dot or bracket was expected here."));
return std::move(unrecognisedNode);
}

std::unique_ptr<FunctionCallNode> FreeFunction(
Expand Down Expand Up @@ -361,18 +379,24 @@ class GD_CORE_API ExpressionParser2 {
const auto &childIdentifierNameLocation =
childIdentifierAndLocation.location;

std::unique_ptr<gd::ExpressionParserError> emptyNameError = childIdentifierName.empty() ?
RaiseSyntaxError(_("A name should be entered after the dot.")) : nullptr;

SkipAllWhitespaces();

if (IsNamespaceSeparator()) {
ExpressionParserLocation namespaceSeparatorLocation =
SkipNamespaceSeparator();
SkipAllWhitespaces();
return BehaviorFunction(parentIdentifier,
auto behaviorFunction = BehaviorFunction(parentIdentifier,
childIdentifierName,
parentIdentifierLocation,
parentIdentifierDotLocation,
childIdentifierNameLocation,
namespaceSeparatorLocation);

if (emptyNameError) behaviorFunction->diagnostic = std::move(emptyNameError);
return std::move(behaviorFunction);
} else if (CheckIfChar(IsOpeningParenthesis)) {
ExpressionParserLocation openingParenthesisLocation = SkipChar();

Expand All @@ -381,7 +405,7 @@ class GD_CORE_API ExpressionParser2 {
childIdentifierName);
auto parametersNode = Parameters(function.get(), parentIdentifier);
function->parameters = std::move(parametersNode.parameters),
function->diagnostic = std::move(parametersNode.diagnostic);
function->diagnostic = emptyNameError ? std::move(emptyNameError) : std::move(parametersNode.diagnostic);

function->location = ExpressionParserLocation(
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
Expand All @@ -394,6 +418,8 @@ class GD_CORE_API ExpressionParser2 {
return std::move(function);
} else if (CheckIfChar(IsDot) || CheckIfChar(IsOpeningSquareBracket)) {
auto variable = gd::make_unique<VariableNode>(parentIdentifier);
variable->diagnostic = std::move(emptyNameError);

auto child =
gd::make_unique<VariableAccessorNode>(childIdentifierName);
child->child = VariableAccessorOrVariableBracketAccessor();
Expand All @@ -419,6 +445,7 @@ class GD_CORE_API ExpressionParser2 {
node->identifierNameLocation = parentIdentifierLocation;
node->identifierNameDotLocation = parentIdentifierDotLocation;
node->childIdentifierNameLocation = childIdentifierNameLocation;
node->diagnostic = std::move(emptyNameError);
return std::move(node);
}

Expand Down
118 changes: 89 additions & 29 deletions Core/GDCore/IDE/Events/ExpressionCompletionFinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2Node.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
Expand Down Expand Up @@ -487,6 +488,7 @@ class GD_CORE_API ExpressionCompletionFinder
auto type = gd::ExpressionTypeFinder::GetType(
platform, projectScopedContainers, rootType, node);

bool eagerlyCompleteIfExactMatch = node.child == nullptr;
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
if (type == "globalvar" || type == "scenevar") {
const auto* variablesContainer =
Expand All @@ -497,27 +499,31 @@ class GD_CORE_API ExpressionCompletionFinder
.GetBottomMostVariablesContainer();
if (variablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variablesContainer, node.name, node.nameLocation);
*variablesContainer, node.name, node.nameLocation, eagerlyCompleteIfExactMatch);
}
} else if (type == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform, objectsContainersList, rootObjectName, node);

AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList, objectName, node.name, node.nameLocation);
objectsContainersList, objectName, node.name, node.nameLocation, eagerlyCompleteIfExactMatch);
}
} else {
AddCompletionsForObjectsAndVariablesMatchingSearch(
node.name, type, node.nameLocation);
node.name, type, node.nameLocation, eagerlyCompleteIfExactMatch);
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
VariableOrVariablesContainer variableOrVariablesContainer =
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);

AddCompletionsForChildrenVariablesOf(variableOrVariablesContainer,
node.nameLocation);
// If no child, we're at the end of a variable (like `GrandChild` in `Something.Child.GrandChild`)
// so we can complete eagerly children if we can.
gd::String eagerlyCompleteForVariableName = node.child == nullptr ? node.name : "";
AddCompletionsForChildrenVariablesOf(variableAndItsParent,
node.nameLocation,
eagerlyCompleteForVariableName);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {}
Expand Down Expand Up @@ -545,14 +551,17 @@ class GD_CORE_API ExpressionCompletionFinder
if (variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation);
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the scene or project:
bool eagerlyCompleteIfPossible = !node.identifierNameDotLocation.IsValid();
AddCompletionsForVariablesMatchingSearch(
*variablesContainer,
node.identifierName,
node.identifierNameLocation);
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else if (type == "objectvar") {
Expand All @@ -569,23 +578,27 @@ class GD_CORE_API ExpressionCompletionFinder
variablesContainer->Has(node.identifierName)) {
AddCompletionsForChildrenVariablesOf(
&variablesContainer->Get(node.identifierName),
node.childIdentifierNameLocation);
node.childIdentifierNameLocation,
node.childIdentifierName);
}
} else {
// Complete a root variable of the object:
bool eagerlyCompleteIfPossible = !node.identifierNameDotLocation.IsValid();
AddCompletionsForObjectOrGroupVariablesMatchingSearch(
objectsContainersList,
objectName,
node.identifierName,
node.identifierNameLocation);
node.identifierNameLocation,
eagerlyCompleteIfPossible);
}
}
} else {
// Object function, behavior name, variable, object variable.
if (IsCaretOn(node.identifierNameLocation)) {
// Is this the proper position?
bool eagerlyCompleteIfPossible = !node.identifierNameDotLocation.IsValid();
AddCompletionsForAllIdentifiersMatchingSearch(
node.identifierName, type, node.identifierNameLocation);
node.identifierName, type, node.identifierNameLocation,
eagerlyCompleteIfPossible);
if (!node.identifierNameDotLocation.IsValid()) {
completions.push_back(
ExpressionCompletionDescription::ForExpressionWithPrefix(
Expand All @@ -608,7 +621,8 @@ class GD_CORE_API ExpressionCompletionFinder
objectsContainersList,
objectName,
node.childIdentifierName,
node.childIdentifierNameLocation);
node.childIdentifierNameLocation, true);

completions.push_back(
ExpressionCompletionDescription::ForBehaviorWithPrefix(
node.childIdentifierName,
Expand All @@ -625,13 +639,14 @@ class GD_CORE_API ExpressionCompletionFinder
},
[&]() {
// This is a variable.
VariableOrVariablesContainer variableOrVariablesContainer =
VariableAndItsParent variableAndItsParent =
gd::ExpressionVariableParentFinder::GetLastParentOfNode(
platform, projectScopedContainers, node);

AddCompletionsForChildrenVariablesOf(
variableOrVariablesContainer,
node.childIdentifierNameLocation);
variableAndItsParent,
node.childIdentifierNameLocation,
node.childIdentifierName);
},
[&]() {
// Ignore properties here.
Expand Down Expand Up @@ -794,19 +809,21 @@ class GD_CORE_API ExpressionCompletionFinder
}

void AddCompletionsForChildrenVariablesOf(
VariableOrVariablesContainer variableOrVariablesContainer,
const ExpressionParserLocation& location) {
if (variableOrVariablesContainer.variable) {
return AddCompletionsForChildrenVariablesOf(
variableOrVariablesContainer.variable, location);
} else if (variableOrVariablesContainer.variablesContainer) {
return AddCompletionsForVariablesMatchingSearch(
*variableOrVariablesContainer.variablesContainer, "", location);
VariableAndItsParent variableAndItsParent,
const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (variableAndItsParent.parentVariable) {
AddCompletionsForChildrenVariablesOf(
variableAndItsParent.parentVariable, location, eagerlyCompleteForVariableName);
} else if (variableAndItsParent.parentVariablesContainer) {
AddCompletionsForVariablesMatchingSearch(
*variableAndItsParent.parentVariablesContainer, "", location);
}
}

void AddCompletionsForChildrenVariablesOf(
const gd::Variable* variable, const ExpressionParserLocation& location) {
const gd::Variable* variable, const ExpressionParserLocation& location,
gd::String eagerlyCompleteForVariableName = "") {
if (!variable) return;

if (variable->GetType() == gd::Variable::Structure) {
Expand All @@ -819,17 +836,41 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(name);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);

if (name == eagerlyCompleteForVariableName) {
AddEagerCompletionForVariableChildren(childVariable, name, location);
}
}
} else {
// TODO: we could do a "comment only completion" to indicate that nothing
// can/should be completed?
}
}

void AddEagerCompletionForVariableChildren(
const gd::Variable& variable,
const gd::String& variableName,
const ExpressionParserLocation& location) {
if (variable.GetType() == gd::Variable::Structure) {
gd::String prefix = variableName + ".";
for (const auto& name : variable.GetAllChildrenNames()) {
const auto& childVariable = variable.GetChild(name);
ExpressionCompletionDescription description(
ExpressionCompletionDescription::Variable,
location.GetStartPosition(),
location.GetEndPosition());
description.SetCompletion(prefix+name);
description.SetVariableType(childVariable.GetType());
completions.push_back(description);
}
}
}

void AddCompletionsForVariablesMatchingSearch(
const gd::VariablesContainer& variablesContainer,
const gd::String& search,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
variablesContainer.ForEachVariableMatchingSearch(
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
Expand All @@ -840,14 +881,19 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);

if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(variable, variableName, location);
}
});
}

void AddCompletionsForObjectOrGroupVariablesMatchingSearch(
const gd::ObjectsContainersList& objectsContainersList,
const gd::String& objectOrGroupName,
const gd::String& search,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
objectsContainersList.ForEachObjectOrGroupVariableMatchingSearch(
objectOrGroupName,
search,
Expand All @@ -859,6 +905,10 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);

if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(variable, variableName, location);
}
});
}

Expand All @@ -885,7 +935,8 @@ class GD_CORE_API ExpressionCompletionFinder
void AddCompletionsForObjectsAndVariablesMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
Expand All @@ -907,6 +958,10 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);

if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
// Ignore properties here.
Expand All @@ -927,7 +982,8 @@ class GD_CORE_API ExpressionCompletionFinder
void AddCompletionsForAllIdentifiersMatchingSearch(
const gd::String& search,
const gd::String& type,
const ExpressionParserLocation& location) {
const ExpressionParserLocation& location,
bool eagerlyCompleteIfExactMatch = false) {
projectScopedContainers.ForEachIdentifierMatchingSearch(
search,
[&](const gd::String& objectName,
Expand All @@ -949,6 +1005,10 @@ class GD_CORE_API ExpressionCompletionFinder
description.SetCompletion(variableName);
description.SetVariableType(variable.GetType());
completions.push_back(description);

if (eagerlyCompleteIfExactMatch && variableName == search) {
AddEagerCompletionForVariableChildren(variable, variableName, location);
}
},
[&](const gd::NamedPropertyDescriptor& property) {
ExpressionCompletionDescription description(
Expand Down
Loading

0 comments on commit bb23493

Please sign in to comment.