Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions src/GDShrapt.Reader.Tests/Parsing/MethodParsingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,76 @@ public void ParseMethod_VariadicFunction_NotSupported()
}
}
}

[TestMethod]
public void ParseMethod_WithLocalConst_InferredType()
{
var reader = new GDScriptReader();

var code = "func test():\n\tconst MAX := 10\n\treturn MAX";

var declaration = reader.ParseFileContent(code);
Assert.IsNotNull(declaration);

var method = declaration.Methods.First();
Assert.AreEqual(2, method.Statements.Count);

var statement = method.Statements[0] as GDVariableDeclarationStatement;
Assert.IsNotNull(statement);
Assert.IsTrue(statement.IsConstant);
Assert.IsNotNull(statement.ConstKeyword);
Assert.IsNull(statement.VarKeyword);
Assert.AreEqual("MAX", statement.Identifier?.ToString());
Assert.IsNotNull(statement.Initializer);

AssertHelper.CompareCodeStrings(code, declaration.ToString());
AssertHelper.NoInvalidTokens(declaration);
}

[TestMethod]
public void ParseMethod_WithLocalConst_ExplicitType()
{
var reader = new GDScriptReader();

var code = "func test():\n\tconst LABEL: String = \"hello\"";

var declaration = reader.ParseFileContent(code);
Assert.IsNotNull(declaration);

var method = declaration.Methods.First();
Assert.AreEqual(1, method.Statements.Count);

var statement = method.Statements[0] as GDVariableDeclarationStatement;
Assert.IsNotNull(statement);
Assert.IsTrue(statement.IsConstant);
Assert.AreEqual("LABEL", statement.Identifier?.ToString());
Assert.IsNotNull(statement.Type);
Assert.AreEqual("String", statement.Type.ToString()?.Trim());
Assert.IsNotNull(statement.Initializer);

AssertHelper.CompareCodeStrings(code, declaration.ToString());
AssertHelper.NoInvalidTokens(declaration);
}

[TestMethod]
public void ParseMethod_WithLocalVar_IsNotConstant()
{
var reader = new GDScriptReader();

var code = "func test():\n\tvar x := 10";

var declaration = reader.ParseFileContent(code);
var method = declaration.Methods.First();

var statement = method.Statements[0] as GDVariableDeclarationStatement;
Assert.IsNotNull(statement);
Assert.IsFalse(statement.IsConstant);
Assert.IsNull(statement.ConstKeyword);
Assert.IsNotNull(statement.VarKeyword);
Assert.AreEqual("x", statement.Identifier?.ToString());

AssertHelper.CompareCodeStrings(code, declaration.ToString());
AssertHelper.NoInvalidTokens(declaration);
}
}
}
2 changes: 1 addition & 1 deletion src/GDShrapt.Reader/Resolvers/GDKeywordPrefixMatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ internal static class GDKeywordPrefixMatcher
{ "var", "const", "func", "class", "signal", "enum", "static", "extends", "class_name", "tool", "pass" };

internal static readonly string[] StatementLevelKeywords =
{ "if", "for", "while", "match", "var", "return", "break", "continue", "pass", "elif", "else", "await", "assert" };
{ "if", "for", "while", "match", "var", "const", "return", "break", "continue", "pass", "elif", "else", "await", "assert" };

/// <summary>
/// Returns the shortest keyword that starts with the given fragment.
Expand Down
7 changes: 7 additions & 0 deletions src/GDShrapt.Reader/Resolvers/GDStatementsResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,13 @@ private GDStatement CompleteAsStatement(GDReadingState state, string sequence)
statement = s;
break;
}
case "const":
{
var s = new GDVariableDeclarationStatement(CurrentResolvedIntendationInSpaces);
s.Add(new GDConstKeyword());
statement = s;
break;
}
default:
{
if (state.Settings.DetectKeywordFragments && sequence != null)
Expand Down
65 changes: 52 additions & 13 deletions src/GDShrapt.Reader/Statements/GDVariableDeclarationStatement.cs
Original file line number Diff line number Diff line change
@@ -1,53 +1,64 @@
using System.Collections.Generic;
using System.Collections.Generic;

namespace GDShrapt.Reader
{
public sealed class GDVariableDeclarationStatement : GDStatement,
public sealed class GDVariableDeclarationStatement : GDStatement,
ITokenOrSkipReceiver<GDConstKeyword>,
ITokenOrSkipReceiver<GDVarKeyword>,
ITokenOrSkipReceiver<GDIdentifier>,
ITokenOrSkipReceiver<GDColon>,
ITokenOrSkipReceiver<GDTypeNode>,
ITokenOrSkipReceiver<GDAssign>,
ITokenOrSkipReceiver<GDExpression>
{
public GDVarKeyword VarKeyword
public GDConstKeyword ConstKeyword
{
get => _form.Token0;
set => _form.Token0 = value;
}

public GDIdentifier Identifier
public GDVarKeyword VarKeyword
{
get => _form.Token1;
set => _form.Token1 = value;
}

public GDColon Colon
public GDIdentifier Identifier
{
get => _form.Token2;
set => _form.Token2 = value;
}

public GDTypeNode Type
public GDColon Colon
{
get => _form.Token3;
set => _form.Token3 = value;
}

public GDAssign Assign
public GDTypeNode Type
{
get => _form.Token4;
set => _form.Token4 = value;
}

public GDExpression Initializer
public GDAssign Assign
{
get => _form.Token5;
set => _form.Token5 = value;
}

public GDExpression Initializer
{
get => _form.Token6;
set => _form.Token6 = value;
}

/// <summary>True if this is a const declaration rather than var.</summary>
public bool IsConstant => ConstKeyword != null;

public enum State
{
Const,
Var,
Identifier,
Colon,
Expand All @@ -57,18 +68,18 @@ public enum State
Completed
}

readonly GDTokensForm<State, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression> _form;
readonly GDTokensForm<State, GDConstKeyword, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression> _form;
public override GDTokensForm Form => _form;
public GDTokensForm<State, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression> TypedForm => _form;
public GDTokensForm<State, GDConstKeyword, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression> TypedForm => _form;
internal GDVariableDeclarationStatement(int lineIntendation)
: base(lineIntendation)
{
_form = new GDTokensForm<State, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression>(this);
_form = new GDTokensForm<State, GDConstKeyword, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression>(this);
}

public GDVariableDeclarationStatement()
{
_form = new GDTokensForm<State, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression>(this);
_form = new GDTokensForm<State, GDConstKeyword, GDVarKeyword, GDIdentifier, GDColon, GDTypeNode, GDAssign, GDExpression>(this);
}

internal override void HandleChar(char c, GDReadingState state)
Expand All @@ -78,6 +89,11 @@ internal override void HandleChar(char c, GDReadingState state)

switch (_form.State)
{
case State.Const:
// For const declarations, the const keyword is already added.
// Skip to Var state (which will skip to Identifier).
this.ResolveKeyword<GDConstKeyword>(c, state);
break;
case State.Var:
this.ResolveKeyword<GDVarKeyword>(c, state);
break;
Expand Down Expand Up @@ -127,6 +143,29 @@ internal override void Left(IGDVisitor visitor)
visitor.Left(this);
}

void ITokenReceiver<GDConstKeyword>.HandleReceivedToken(GDConstKeyword token)
{
if (_form.IsOrLowerState(State.Const))
{
_form.State = State.Identifier; // skip Var state — const replaces var
ConstKeyword = token;
return;
}

throw new GDInvalidStateException();
}

void ITokenSkipReceiver<GDConstKeyword>.HandleReceivedTokenSkip()
{
if (_form.IsOrLowerState(State.Const))
{
_form.State = State.Var; // no const keyword, check for var
return;
}

throw new GDInvalidStateException();
}

void ITokenReceiver<GDVarKeyword>.HandleReceivedToken(GDVarKeyword token)
{
if (_form.IsOrLowerState(State.Var))
Expand Down Expand Up @@ -265,4 +304,4 @@ void ITokenSkipReceiver<GDExpression>.HandleReceivedTokenSkip()
throw new GDInvalidStateException();
}
}
}
}