Skip to content

Commit

Permalink
Added guard condition parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
elamaunt committed Jan 14, 2024
1 parent df1d24d commit ac2b1a4
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 37 deletions.
187 changes: 187 additions & 0 deletions src/GDShrapt.Reader.Tests/ParsingTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using System.Threading;
using static GDShrapt.Reader.GD;

namespace GDShrapt.Reader.Tests
{
Expand Down Expand Up @@ -537,6 +540,190 @@ public void MatchStatementTest2()
AssertHelper.NoInvalidTokens(statement);
}

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

var code = @"match x:
1, 2, 3:
print(""It's 1 - 3"")
""Sword"", ""Splash potion"", ""Fist"":
print(""Yep, you've taken damage"")";

var statement = reader.ParseStatement(code);

Assert.IsNotNull(statement);
Assert.IsInstanceOfType(statement, typeof(GDMatchStatement));

var matchStatement = (GDMatchStatement)statement;

matchStatement.Cases.Select(x => x.Conditions.ToString()).Should().BeEquivalentTo(new string[]
{
"1, 2, 3",
"\"Sword\", \"Splash potion\", \"Fist\""
});

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

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

var code = @"match x:
1:
print(""It's one!"")
2:
print(""It's one times two!"")
_:
print(""It's not 1 or 2. I don't care to be honest."")";

var statement = reader.ParseStatement(code);

Assert.IsNotNull(statement);
Assert.IsInstanceOfType(statement, typeof(GDMatchStatement));

var matchStatement = (GDMatchStatement)statement;

matchStatement.Cases.Select(x => x.Conditions.ToString()).Should().BeEquivalentTo(new string[]
{
"1",
"2",
"_"
});

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

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

var code = @"match x:
[]:
print(""Empty array"")
[1, 3, ""test"", null]:
print(""Very specific array"")
[var start, _, ""test""]:
print(""First element is "", start, "", and the last is \""test\"""")
[42, ..]:
print(""Open ended array"")";

var statement = reader.ParseStatement(code);

Assert.IsNotNull(statement);
Assert.IsInstanceOfType(statement, typeof(GDMatchStatement));

var matchStatement = (GDMatchStatement)statement;

matchStatement.Cases.Select(x => x.Conditions.ToString()).Should().BeEquivalentTo(new string[]
{
"[]",
"[1, 3, \"test\", null]",
"[var start, _, \"test\"]",
"[42, ..]"
});

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

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

var code = @"match x:
{}:
print(""Empty dict"")
{""name"": ""Dennis""}:
print(""The name is Dennis"")
{""name"": ""Dennis"", ""age"": var age}:
print(""Dennis is "", age, "" years old."")
{""name"", ""age""}:
print(""Has a name and an age, but it's not Dennis :("")
{""key"": ""godotisawesome"", ..}:
print(""I only checked for one entry and ignored the rest"")";

var statement = reader.ParseStatement(code);

Assert.IsNotNull(statement);
Assert.IsInstanceOfType(statement, typeof(GDMatchStatement));

var matchStatement = (GDMatchStatement)statement;

matchStatement.Cases.Select(x => x.Conditions.ToString()).Should().BeEquivalentTo(new string[]
{
"{}",
"{\"name\": \"Dennis\"}",
"{\"name\": \"Dennis\", \"age\": var age}",
"{\"name\", \"age\"}",
"{\"key\": \"godotisawesome\", ..}"
});

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

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

var code = @"match point:
[0, 0]:
print(""Origin"")
[_, 0]:
print(""Point on X-axis"")
[0, _]:
print(""Point on Y-axis"")
[var x, var y] when y == x:
print(""Point on line y = x"")
[var x, var y] when y == -x:
print(""Point on line y = -x"")
[var x, var y]:
print(""Point (%s, %s)"" % [x, y])";

var statement = reader.ParseStatement(code);

Assert.IsNotNull(statement);
Assert.IsInstanceOfType(statement, typeof(GDMatchStatement));

var matchStatement = (GDMatchStatement)statement;

var cases = matchStatement.Cases;

Assert.AreEqual(6, cases.Count);

var one = cases[3];

Assert.IsNotNull(one.When);
Assert.IsNotNull(one.GuardCondition);
Assert.AreEqual("y == x", one.GuardCondition.ToString());
Assert.AreEqual("[var x, var y] ", one.Conditions.ToString());

var two = cases[4];

Assert.IsNotNull(two.When);
Assert.IsNotNull(two.GuardCondition);
Assert.AreEqual("y == -x", two.GuardCondition.ToString());
Assert.AreEqual("[var x, var y] ", two.Conditions.ToString());

var three = cases[5];
Assert.AreEqual("[var x, var y]", three.Conditions.ToString());

Assert.AreEqual(1, three.Statements.Count);
Assert.AreEqual("print(\"Point (%s, %s)\" % [x, y])", three.Statements[0].ToString());

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

[TestMethod]
public void ArrayTest()
{
Expand Down
111 changes: 99 additions & 12 deletions src/GDShrapt.Reader/Declarations/GDMatchCaseDeclaration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace GDShrapt.Reader
{
public sealed class GDMatchCaseDeclaration : GDIntendedNode,
ITokenOrSkipReceiver<GDExpressionsList>,
ITokenOrSkipReceiver<GDWhenKeyword>,
ITokenOrSkipReceiver<GDExpression>,
ITokenOrSkipReceiver<GDColon>,
ITokenOrSkipReceiver<GDStatementsList>
{
Expand All @@ -13,39 +15,55 @@ public GDExpressionsList Conditions
get => _form.Token0 ?? (_form.Token0 = new GDExpressionsList());
set => _form.Token0 = value;
}

public GDColon Colon
public GDWhenKeyword When
{
get => _form.Token1;
set => _form.Token1 = value;
}

public GDExpression GuardCondition
{
get => _form.Token2;
set => _form.Token2 = value;
}
public GDColon Colon
{
get => _form.Token3;
set => _form.Token3 = value;
}
public GDExpression Expression
{
get => _form.Token4;
set => _form.Token4 = value;
}
public GDStatementsList Statements
{
get => _form.Token2 ?? (_form.Token2 = new GDStatementsList(Intendation + 1));
set => _form.Token2 = value;
get => _form.Token5 ?? (_form.Token5 = new GDStatementsList(Intendation + 1));
set => _form.Token5 = value;
}

public enum State
{
Conditions,
When,
GuardCondition,
Colon,
Expression,
Statements,
Completed
}

readonly GDTokensForm<State, GDExpressionsList, GDColon, GDStatementsList> _form;
readonly GDTokensForm<State, GDExpressionsList, GDWhenKeyword, GDExpression, GDColon, GDExpression, GDStatementsList> _form;
public override GDTokensForm Form => _form;
public GDTokensForm<State, GDExpressionsList, GDColon, GDStatementsList> TypedForm => _form;
public GDTokensForm<State, GDExpressionsList, GDWhenKeyword, GDExpression, GDColon, GDExpression, GDStatementsList> TypedForm => _form;
internal GDMatchCaseDeclaration(int lineIntendation)
: base(lineIntendation)
{
_form = new GDTokensForm<State, GDExpressionsList, GDColon, GDStatementsList>(this);
_form = new GDTokensForm<State, GDExpressionsList, GDWhenKeyword, GDExpression, GDColon, GDExpression, GDStatementsList>(this);
}

public GDMatchCaseDeclaration()
{
_form = new GDTokensForm<State, GDExpressionsList, GDColon, GDStatementsList>(this);
_form = new GDTokensForm<State, GDExpressionsList, GDWhenKeyword, GDExpression, GDColon, GDExpression, GDStatementsList>(this);
}

internal override void HandleChar(char c, GDReadingState state)
Expand All @@ -56,9 +74,16 @@ internal override void HandleChar(char c, GDReadingState state)
switch (_form.State)
{
case State.Conditions:
_form.State = State.Colon;
_form.State = State.When;
state.PushAndPass(Conditions, c);
break;
case State.When:
this.ResolveKeyword<GDWhenKeyword>(c, state);
break;
case State.Expression:
case State.GuardCondition:
this.ResolveExpression(c, state, Intendation);
break;
case State.Colon:
this.ResolveColon(c, state);
break;
Expand All @@ -76,7 +101,10 @@ internal override void HandleNewLineChar(GDReadingState state)
switch (_form.State)
{
case State.Conditions:
case State.When:
case State.GuardCondition:
case State.Colon:
case State.Expression:
case State.Statements:
_form.State = State.Completed;
state.PushAndPassNewLine(Statements);
Expand Down Expand Up @@ -137,7 +165,7 @@ void ITokenReceiver<GDExpressionsList>.HandleReceivedToken(GDExpressionsList tok
{
if (_form.IsOrLowerState(State.Conditions))
{
_form.State = State.Colon;
_form.State = State.When;
Conditions = token;
return;
}
Expand All @@ -149,7 +177,7 @@ void ITokenSkipReceiver<GDExpressionsList>.HandleReceivedTokenSkip()
{
if (_form.IsOrLowerState(State.Conditions))
{
_form.State = State.Colon;
_form.State = State.When;
return;
}

Expand Down Expand Up @@ -178,5 +206,64 @@ void ITokenSkipReceiver<GDStatementsList>.HandleReceivedTokenSkip()

throw new GDInvalidStateException();
}

void ITokenReceiver<GDWhenKeyword>.HandleReceivedToken(GDWhenKeyword token)
{
if (_form.IsOrLowerState(State.When))
{
_form.State = State.GuardCondition;
When = token;
return;
}

throw new GDInvalidStateException();
}

void ITokenSkipReceiver<GDWhenKeyword>.HandleReceivedTokenSkip()
{
if (_form.IsOrLowerState(State.When))
{
_form.State = State.Colon;
return;
}

throw new GDInvalidStateException();
}

void ITokenReceiver<GDExpression>.HandleReceivedToken(GDExpression token)
{
if (_form.IsOrLowerState(State.GuardCondition))
{
_form.State = State.Colon;
GuardCondition = token;
return;
}

if (_form.IsOrLowerState(State.Expression))
{
_form.State = State.Statements;
Expression = token;
return;
}

throw new GDInvalidStateException();
}

void ITokenSkipReceiver<GDExpression>.HandleReceivedTokenSkip()
{
if (_form.IsOrLowerState(State.GuardCondition))
{
_form.State = State.Colon;
return;
}

if (_form.IsOrLowerState(State.Expression))
{
_form.State = State.Statements;
return;
}

throw new GDInvalidStateException();
}
}
}
Loading

0 comments on commit ac2b1a4

Please sign in to comment.