diff --git a/src/GDShrapt.Reader.Tests/BigScriptTests.cs b/src/GDShrapt.Reader.Tests/BigScriptTests.cs index a97a5ec..00a9c48 100644 --- a/src/GDShrapt.Reader.Tests/BigScriptTests.cs +++ b/src/GDShrapt.Reader.Tests/BigScriptTests.cs @@ -61,5 +61,18 @@ public void BigScriptParsingTest4() AssertHelper.CompareCodeStrings(fileText, declaration.ToString()); AssertHelper.NoInvalidTokens(declaration); } + + [TestMethod] + public void ScriptTest1() + { + var reader = new GDScriptReader(); + + var code = "# _at_position is not used because it doesn't matter where on the panel\r\n# the item is dropped\r\nfunc _can_drop_data(_at_position: Vector2, data: Variant) -> bool:\t\r\n\tif data is InventoryItem:\r\n\t\t#This is the text that displays uupon pulling an item out.\r\n\t\t%summary.text =( str(\"atk:\" + str(data.physicalattack) +'\\n' + data.lore))\r\n\t\tif type == InventoryItem.Type.MAIN:\r\n\t\t\tif get_child_count() == 0:\r\n\t\t\t\treturn true\r\n\t\t\telse:\r\n\t\t\t\t# Swap two items\r\n\t\t\t\treturn get_child(0).type == data.type\r\n\t\telse:\r\n\t\t\treturn data.type == type\r\n\t\t\t\r\n\t\r\n\treturn false"; + + var declaration = reader.ParseFileContent(code); + + AssertHelper.CompareCodeStrings(code, declaration.ToString()); + AssertHelper.NoInvalidTokens(declaration); + } } } diff --git a/src/GDShrapt.Reader.Tests/ParsingTests.cs b/src/GDShrapt.Reader.Tests/ParsingTests.cs index e679b73..5eabd79 100644 --- a/src/GDShrapt.Reader.Tests/ParsingTests.cs +++ b/src/GDShrapt.Reader.Tests/ParsingTests.cs @@ -2303,5 +2303,35 @@ public void StatementAtributesTest2() AssertHelper.CompareCodeStrings(code, @class.ToString()); AssertHelper.NoInvalidTokens(@class); } + + [TestMethod] + public void ParseGetNodeNewSyntax() + { + var reader = new GDScriptReader(); + + var code = @"if Input.is_anything_pressed() == false: + %summary.text = ""physical attack: "" + str(Physical_attack_sum) + '\n'"; + + var @class = reader.ParseStatement(code); + + AssertHelper.CompareCodeStrings(code, @class.ToString()); + AssertHelper.NoInvalidTokens(@class); + } + + [TestMethod] + public void ParseScriptSubtype() + { + var reader = new GDScriptReader(); + + var code = @"# Custom init function so that it doesn't error +func init(t: InventoryItem.Type, cms: Vector2) -> void: + type = t + custom_minimum_size = cms"; + + var @class = reader.ParseFileContent(code); + + AssertHelper.CompareCodeStrings(code, @class.ToString()); + AssertHelper.NoInvalidTokens(@class); + } } } \ No newline at end of file diff --git a/src/GDShrapt.Reader/Expressions/GDGetUniqueNodeExpression.cs b/src/GDShrapt.Reader/Expressions/GDGetUniqueNodeExpression.cs new file mode 100644 index 0000000..081578c --- /dev/null +++ b/src/GDShrapt.Reader/Expressions/GDGetUniqueNodeExpression.cs @@ -0,0 +1,124 @@ +namespace GDShrapt.Reader +{ + public sealed class GDGetUniqueNodeExpression : GDExpression, + ITokenOrSkipReceiver, + ITokenOrSkipReceiver + { + public override int Priority => GDHelper.GetOperationPriority(GDOperationType.GetNode); + + public GDPercent Percent + { + get => _form.Token0; + set => _form.Token0 = value; + } + + public GDPathList Path + { + get => _form.Token1 ?? (_form.Token1 = new GDPathList()); + set => _form.Token1 = value; + } + + public enum State + { + Percent, + Path, + Completed + } + + readonly GDTokensForm _form; + public override GDTokensForm Form => _form; + public GDTokensForm TypedForm => _form; + public GDGetUniqueNodeExpression() + { + _form = new GDTokensForm(this); + } + + internal override void HandleChar(char c, GDReadingState state) + { + switch (_form.State) + { + case State.Percent: + if (this.ResolveSpaceToken(c, state)) + return; + this.ResolvePercent(c, state); + break; + case State.Path: + _form.State = State.Completed; + + if (this.ResolveSpaceToken(c, state)) + return; + state.PushAndPass(Path, c); + break; + default: + state.PopAndPass(c); + break; + } + } + + internal override void HandleNewLineChar(GDReadingState state) + { + state.PopAndPassNewLine(); + } + + public override GDNode CreateEmptyInstance() + { + return new GDGetNodeExpression(); + } + + internal override void Visit(IGDVisitor visitor) + { + visitor.Visit(this); + } + + internal override void Left(IGDVisitor visitor) + { + visitor.Left(this); + } + + void ITokenReceiver.HandleReceivedToken(GDPercent token) + { + if (_form.IsOrLowerState(State.Percent)) + { + _form.State = State.Path; + Percent = token; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenSkipReceiver.HandleReceivedTokenSkip() + { + if (_form.IsOrLowerState(State.Percent)) + { + _form.State = State.Path; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenReceiver.HandleReceivedToken(GDPathList token) + { + if (_form.IsOrLowerState(State.Path)) + { + _form.State = State.Path; + Path = token; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenSkipReceiver.HandleReceivedTokenSkip() + { + if (_form.IsOrLowerState(State.Path)) + { + _form.State = State.Path; + return; + } + + throw new GDInvalidStateException(); + } + } +} diff --git a/src/GDShrapt.Reader/Resolvers/GDExpressionResolver.cs b/src/GDShrapt.Reader/Resolvers/GDExpressionResolver.cs index cf90518..9247a0e 100644 --- a/src/GDShrapt.Reader/Resolvers/GDExpressionResolver.cs +++ b/src/GDShrapt.Reader/Resolvers/GDExpressionResolver.cs @@ -138,6 +138,13 @@ internal override void HandleChar(char c, GDReadingState state) state.PassChar(c); return; } + + if (c == '%') + { + PushAndSave(state, new GDGetUniqueNodeExpression()); + state.PassChar(c); + return; + } } else { diff --git a/src/GDShrapt.Reader/Resolvers/GDIntendedResolver.cs b/src/GDShrapt.Reader/Resolvers/GDIntendedResolver.cs index 5506593..73ff833 100644 --- a/src/GDShrapt.Reader/Resolvers/GDIntendedResolver.cs +++ b/src/GDShrapt.Reader/Resolvers/GDIntendedResolver.cs @@ -241,8 +241,13 @@ protected void SendIntendationTokensToOwner() protected void PassIntendationSequence(GDReadingState state) { + if (_intendationTokensSent) + return; + for (int i = 0; i < _sequenceBuilder.Length; i++) state.PassChar(_sequenceBuilder[i]); + + ResetIntendation(); } protected void ResetIntendation() diff --git a/src/GDShrapt.Reader/Resolvers/GDResolvingHelper.cs b/src/GDShrapt.Reader/Resolvers/GDResolvingHelper.cs index 6ffb3e6..5c35042 100644 --- a/src/GDShrapt.Reader/Resolvers/GDResolvingHelper.cs +++ b/src/GDShrapt.Reader/Resolvers/GDResolvingHelper.cs @@ -29,6 +29,19 @@ public static void ResolveKeyword(this ITokenOrSkipReceiver receiver, char state.PushAndPass(new GDKeywordResolver(receiver), c); } + public static bool ResolvePercent(this ITokenOrSkipReceiver receiver, char c, GDReadingState state) + { + var result = c == '%'; + if (result) + receiver.HandleReceivedToken(new GDPercent()); + else + { + receiver.HandleReceivedTokenSkip(); + state.PassChar(c); + } + return result; + } + public static bool ResolveDollar(this ITokenOrSkipReceiver receiver, char c, GDReadingState state) { var result = c == '$'; @@ -376,7 +389,7 @@ public static bool ResolveCommentToken(this ITokenReceiver receiver, { if (IsCommentStartChar(c)) { - receiver.HandleReceivedToken(new GDComment()); + receiver.HandleReceivedToken(state.Push(new GDComment())); state.PassChar(c); return true; } diff --git a/src/GDShrapt.Reader/Resolvers/GDStatementsResolver.cs b/src/GDShrapt.Reader/Resolvers/GDStatementsResolver.cs index f5a0dbf..ddf8a76 100644 --- a/src/GDShrapt.Reader/Resolvers/GDStatementsResolver.cs +++ b/src/GDShrapt.Reader/Resolvers/GDStatementsResolver.cs @@ -30,6 +30,12 @@ internal override void HandleCharAfterIntendation(char c, GDReadingState state) if (_resolvedAsExpression) { + if (c.IsExpressionStopChar()) + { + Owner.HandleAsInvalidToken(c, state, x => x.IsSpace() || x.IsNewLine()); + return; + } + // Resolving multiple expressions on the same string var statement = new GDExpressionStatement(); Owner.HandleReceivedToken(statement); diff --git a/src/GDShrapt.Reader/Resolvers/GDTypeResolver.cs b/src/GDShrapt.Reader/Resolvers/GDTypeResolver.cs index d7ae8e9..d366737 100644 --- a/src/GDShrapt.Reader/Resolvers/GDTypeResolver.cs +++ b/src/GDShrapt.Reader/Resolvers/GDTypeResolver.cs @@ -94,6 +94,15 @@ internal override void HandleChar(char c, GDReadingState state) Owner.HandleReceivedToken(arrayTypeNode); state.Push(arrayTypeNode); + + if (_space != null) + { + for (int i = 0; i < _space.Sequence.Length; i++) + state.PassChar(_space.Sequence[i]); + + _space = null; + } + state.PassChar(c); return; } @@ -104,6 +113,29 @@ internal override void HandleChar(char c, GDReadingState state) return; } + if (c == '.' && _type != null) + { + var subTypeNode = new GDSubTypeNode(); + subTypeNode.Add(new GDSingleTypeNode() { Type = _type }); + + Owner.HandleReceivedToken(subTypeNode); + _type = null; + state.Pop(); + + state.Push(subTypeNode); + + if (_space != null) + { + for (int i = 0; i < _space.Sequence.Length; i++) + state.PassChar(_space.Sequence[i]); + + _space = null; + } + + state.PassChar(c); + return; + } + state.Pop(); Complete(state); state.PassChar(c); diff --git a/src/GDShrapt.Reader/SimpleTokens/GDPercent.cs b/src/GDShrapt.Reader/SimpleTokens/GDPercent.cs new file mode 100644 index 0000000..37ee937 --- /dev/null +++ b/src/GDShrapt.Reader/SimpleTokens/GDPercent.cs @@ -0,0 +1,12 @@ +namespace GDShrapt.Reader +{ + public sealed class GDPercent : GDSingleCharToken + { + public override char Char => '%'; + + public override GDSyntaxToken Clone() + { + return new GDPercent(); + } + } +} diff --git a/src/GDShrapt.Reader/Types/GDSubTypeNode.cs b/src/GDShrapt.Reader/Types/GDSubTypeNode.cs new file mode 100644 index 0000000..0801704 --- /dev/null +++ b/src/GDShrapt.Reader/Types/GDSubTypeNode.cs @@ -0,0 +1,163 @@ +namespace GDShrapt.Reader +{ + public class GDSubTypeNode : GDTypeNode, + ITokenOrSkipReceiver, + ITokenOrSkipReceiver, + ITokenOrSkipReceiver + { + public override bool IsArray => false; + public override GDTypeNode SubType => null; + public GDTypeNode OverType + { + get => _form.Token0; + set => _form.Token0 = value; + } + + public GDPoint Point + { + get => _form.Token1; + set => _form.Token1 = value; + } + + public GDType Type + { + get => _form.Token2; + set => _form.Token2 = value; + } + + public enum State + { + OverType, + Point, + Type, + Completed + } + + readonly GDTokensForm _form; + public override GDTokensForm Form => _form; + public GDTokensForm TypedForm => _form; + + public GDSubTypeNode() + { + _form = new GDTokensForm(this); + } + + public override GDNode CreateEmptyInstance() + { + return new GDArrayTypeNode(); + } + + internal override void Visit(IGDVisitor visitor) + { + visitor.Visit(this); + } + + internal override void Left(IGDVisitor visitor) + { + visitor.Left(this); + } + + internal override void HandleChar(char c, GDReadingState state) + { + switch (_form.State) + { + case State.OverType: + if (!this.ResolveSpaceToken(c, state)) + ((ITokenOrSkipReceiver)this).ResolveType(c, state); + break; + case State.Point: + if (!this.ResolveSpaceToken(c, state)) + this.ResolvePoint(c, state); + break; + case State.Type: + if (!this.ResolveSpaceToken(c, state)) + ((ITokenOrSkipReceiver)this).ResolveType(c, state); + break; + default: + state.PopAndPass(c); + break; + } + } + + internal override void HandleNewLineChar(GDReadingState state) + { + _form.State = State.Completed; + state.PopAndPassNewLine(); + } + + + void ITokenReceiver.HandleReceivedToken(GDTypeNode token) + { + if (_form.State == State.OverType) + { + _form.State = State.Point; + OverType = token; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenSkipReceiver.HandleReceivedTokenSkip() + { + if (_form.State == State.OverType) + { + _form.State = State.Point; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenReceiver.HandleReceivedToken(GDPoint token) + { + if (_form.State == State.Point) + { + _form.State = State.Type; + Point = token; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenSkipReceiver.HandleReceivedTokenSkip() + { + if (_form.State == State.Point) + { + _form.State = State.Type; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenReceiver.HandleReceivedToken(GDType token) + { + if (_form.State == State.Type) + { + _form.State = State.Completed; + Type = token; + return; + } + + throw new GDInvalidStateException(); + } + + void ITokenSkipReceiver.HandleReceivedTokenSkip() + { + if (_form.State == State.Type) + { + _form.State = State.Completed; + return; + } + + throw new GDInvalidStateException(); + } + + public override string BuildName() + { + return $"{OverType?.BuildName()}.{Type?.ToString()}"; + } + } +} diff --git a/src/GDShrapt.Reader/Walking/IGDVisitor.cs b/src/GDShrapt.Reader/Walking/IGDVisitor.cs index 745aa88..aecd740 100644 --- a/src/GDShrapt.Reader/Walking/IGDVisitor.cs +++ b/src/GDShrapt.Reader/Walking/IGDVisitor.cs @@ -162,5 +162,9 @@ public interface IGDVisitor : IGDBaseVisitor void Visit(GDTripleDoubleQuotasStringNode sn); void Visit(GDDoubleQuotasStringNode sn); void Visit(GDSingleQuotasStringNode sn); + void Visit(GDGetUniqueNodeExpression e); + void Left(GDGetUniqueNodeExpression e); + void Visit(GDSubTypeNode t); + void Left(GDSubTypeNode t); } } \ No newline at end of file