Skip to content
Merged
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
49 changes: 44 additions & 5 deletions bindings/csharp/Expressif.Syntax.Tests/SyntaxBindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,40 @@ public void CompoundValuesBindNestedValuesAndRecordFields()
});
}

[Test]
public void ParameterizedExpressionsPreserveSourceAndPipeline()
{
var root = (OpenExpressionSyntax)ExpressifSyntax.Parse("skip-last-chars({@length | subtract(1) | max(0)})");
var argument = root.Pipeline.Single().Arguments.Single();
var parameterized = (ParameterizedExpressionSyntax)argument.Value;

Assert.Multiple(() =>
{
Assert.That(parameterized.Source, Is.TypeOf<VariableSyntax>());
Assert.That(((VariableSyntax)parameterized.Source).Name, Is.EqualTo("length"));
Assert.That(parameterized.Expression.Pipeline.Select(call => call.Name), Is.EqualTo(new[] { "subtract", "max" }));
Assert.That(parameterized.Children, Is.EqualTo(new SyntaxNode[] { parameterized.Source, parameterized.Expression }));
Assert.That(parameterized.Text, Is.EqualTo("{@length | subtract(1) | max(0)}"));
});
}

[TestCase("{{1, 2} | sum}", typeof(ArrayLiteralSyntax), "{1, 2}")]
[TestCase("{T(1, 2) | some-function}", typeof(TupleLiteralSyntax), "T(1, 2)")]
[TestCase("{{name := \"Alice\"} | some-function}", typeof(RecordLiteralSyntax), "{name := \"Alice\"}")]
public void ParameterizedExpressionsPreserveCompoundSources(string argument, Type sourceType, string sourceText)
{
var root = (OpenExpressionSyntax)ExpressifSyntax.Parse($"foo({argument})");
var parameterized = (ParameterizedExpressionSyntax)root.Pipeline.Single().Arguments.Single().Value;

Assert.Multiple(() =>
{
Assert.That(parameterized.Source, Is.TypeOf(sourceType));
Assert.That(parameterized.Source.Text, Is.EqualTo(sourceText));
Assert.That(parameterized.Text, Is.EqualTo(argument));
Assert.That(parameterized.Expression.Pipeline, Has.Count.EqualTo(1));
});
}

[Test]
public void BinderSupportsEveryGrammarValueNodeType()
{
Expand Down Expand Up @@ -193,12 +227,17 @@ public void SourceTextAndRangesAreLossless()
});
}

[TestCase("add(")]
[TestCase("10 |")]
[TestCase("\"unterminated")]
public void MalformedInputExposesTreeSitterErrors(string source)
[TestCase("add(", false)]
[TestCase("10 |", false)]
[TestCase("\"unterminated", false)]
[TestCase("foo({| lower})", true)]
public void MalformedInputExposesTreeSitterErrors(string source, bool hasMissingError)
{
var exception = Assert.Throws<ExpressifSyntaxException>(() => ExpressifSyntax.Parse(source));
Assert.That(exception!.Errors, Is.Not.Empty);
Assert.Multiple(() =>
{
Assert.That(exception!.Errors, Is.Not.Empty);
Assert.That(exception.Errors.Any(error => error.IsMissing), Is.EqualTo(hasMissingError));
});
}
}
12 changes: 11 additions & 1 deletion bindings/csharp/Expressif.Syntax/ExpressifSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,17 @@ private static PositionalArgumentSyntax BindArgument(TsNode node)
throw Unknown(node);

var valueNode = SingleNamedChild(node, "positional_argument");
return new(Span(node), node.Text, BindValue(valueNode));
ExpressionSyntax value = valueNode.Type == "parameterized_expression"
? BindParameterizedExpression(valueNode)
: BindValue(valueNode);
return new(Span(node), node.Text, value);
}

private static ParameterizedExpressionSyntax BindParameterizedExpression(TsNode node)
{
var source = node.GetChildForField("source") ?? throw Unknown(node);
var expression = node.GetChildForField("expression") ?? throw Unknown(node);
return new(Span(node), node.Text, BindValue(source), BindOpen(expression));
}

private static ValueSyntax BindValue(TsNode node) => node.Type switch
Expand Down
24 changes: 19 additions & 5 deletions bindings/csharp/Expressif.Syntax/SyntaxNodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public enum SyntaxKind
TupleLiteral,
RecordLiteral,
RecordField,
ParameterizedExpression,
}

public readonly record struct SourceSpan(int Start, int Length)
Expand All @@ -46,7 +47,7 @@ protected SyntaxNode(SyntaxKind kind, SourceSpan span, string text, IEnumerable<
public IReadOnlyList<SyntaxNode> Children => children;
}

public abstract class RootExpressionSyntax : SyntaxNode
public abstract class RootExpressionSyntax : ExpressionSyntax
{
protected RootExpressionSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumerable<SyntaxNode> children)
: base(kind, span, text, children) { }
Expand Down Expand Up @@ -76,7 +77,7 @@ internal ClosedExpressionSyntax(SourceSpan span, string text, ValueSyntax value,

public abstract class ExpressionSyntax : SyntaxNode
{
protected ExpressionSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumerable<SyntaxNode> children)
protected ExpressionSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumerable<SyntaxNode>? children)
: base(kind, span, text, children) { }
}

Expand All @@ -95,6 +96,19 @@ internal FunctionCallSyntax(SourceSpan span, string text, string name, bool hasP
public IReadOnlyList<PositionalArgumentSyntax> Arguments { get; }
}

public sealed class ParameterizedExpressionSyntax : ExpressionSyntax
{
internal ParameterizedExpressionSyntax(SourceSpan span, string text, ValueSyntax source, OpenExpressionSyntax expression)
: base(SyntaxKind.ParameterizedExpression, span, text, [source, expression])
{
Source = source;
Expression = expression;
}

public ValueSyntax Source { get; }
public OpenExpressionSyntax Expression { get; }
}

public abstract class ArgumentSyntax : SyntaxNode
{
protected ArgumentSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumerable<SyntaxNode> children)
Expand All @@ -103,13 +117,13 @@ protected ArgumentSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumera

public sealed class PositionalArgumentSyntax : ArgumentSyntax
{
internal PositionalArgumentSyntax(SourceSpan span, string text, ValueSyntax value)
internal PositionalArgumentSyntax(SourceSpan span, string text, ExpressionSyntax value)
: base(SyntaxKind.PositionalArgument, span, text, [value]) => Value = value;

public ValueSyntax Value { get; }
public ExpressionSyntax Value { get; }
}

public abstract class ValueSyntax : SyntaxNode
public abstract class ValueSyntax : ExpressionSyntax
Comment thread
Seddryck marked this conversation as resolved.
{
protected ValueSyntax(SyntaxKind kind, SourceSpan span, string text, IEnumerable<SyntaxNode>? children = null)
: base(kind, span, text, children) { }
Expand Down
10 changes: 9 additions & 1 deletion grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,15 @@ export default grammar({
repeat(seq(",", $.positional_argument)),
),

positional_argument: ($) => $.value,
positional_argument: ($) => choice($.value, $.parameterized_expression),

parameterized_expression: ($) => seq(
"{",
field("source", $.value),
"|",
field("expression", $.open_expression),
"}",
),

value: ($) => choice(
$.variable,
Expand Down
46 changes: 44 additions & 2 deletions src/grammar.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions src/node-types.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading