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
55 changes: 50 additions & 5 deletions bindings/csharp/Expressif.Syntax.Tests/SyntaxBindingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ public void CompoundValuesBindNestedValuesAndRecordFields()

Assert.Multiple(() =>
{
Assert.That(record.Fields.Select(field => field.Name), Is.EqualTo(new[] { "name", "scores" }));
Assert.That(record.Fields[1].QuotingStyle, Is.EqualTo(QuotingStyle.Backtick));
Assert.That(record.Fields.Select(field => field.Name.Value), Is.EqualTo(new[] { "name", "scores" }));
Assert.That(record.Fields[1].Name.QuotingStyle, Is.EqualTo(QuotingStyle.Backtick));
Assert.That(record.Fields[0].Value, Is.TypeOf<VariableSyntax>());
Assert.That(((ArrayLiteralSyntax)record.Fields[1].Value).Values[1], Is.TypeOf<RecordAccessSyntax>());
Assert.That(record.Children, Is.EqualTo(record.Fields));
Expand Down Expand Up @@ -399,7 +399,7 @@ public void IncomingValueCanBeEmbeddedInARecordField()
Assert.That(field.Value.Kind, Is.EqualTo(SyntaxKind.IncomingValue));
Assert.That(field.Value.Text, Is.EqualTo("..."));
Assert.That(field.Value.Span, Is.EqualTo(new SourceSpan(14, 3)));
Assert.That(field.Children, Is.EqualTo(new SyntaxNode[] { field.Value }));
Assert.That(field.Children, Is.EqualTo(new SyntaxNode[] { field.Name, field.Value }));
});
}

Expand Down Expand Up @@ -434,7 +434,7 @@ public void RecordSpreadRemainsDistinctAndPreservesEntryOrder()
SyntaxKind.RecordSpread,
SyntaxKind.RecordField,
}));
Assert.That(record.Fields.Select(field => field.Name), Is.EqualTo(new[] { "before", "after" }));
Assert.That(record.Fields.Select(field => field.Name.Value), Is.EqualTo(new[] { "before", "after" }));
Assert.That(record.Children, Is.EqualTo(record.Entries));
Assert.That(spread.Text, Is.EqualTo("..."));
Assert.That(spread.Span, Is.EqualTo(new SourceSpan(19, 3)));
Expand All @@ -449,7 +449,8 @@ public void RecordLiteralMaterializesEntriesOnlyOnce()
static IEnumerable<RecordEntrySyntax> CreateEntries()
{
yield return new RecordFieldSyntax(
new SourceSpan(2, 10), "value := 1", "value", null,
new SourceSpan(2, 10), "value := 1",
new RecordFieldNameSyntax(new SourceSpan(2, 5), "value", "value", false, null),
new NumericLiteralSyntax(new SourceSpan(11, 1), "1"));
yield return new RecordSpreadSyntax(new SourceSpan(14, 3), "...");
}
Expand Down Expand Up @@ -646,6 +647,50 @@ public void RepeatedUnaryShorthandRemainsNestedSyntax()
});
}

[TestCase("{foo := 10, bar := 20}")]
[TestCase("{_tmp := 20, _x1 := 30}")]
[TestCase("{foo := 10, _internalValue := 20, bar := 30}")]
public void PublicPrivateAndMixedRecordFieldsParse(string source)
=> Assert.That(ExpressifSyntax.Parse(source), Is.TypeOf<ClosedExpressionSyntax>());

[Test]
public void RecordFieldNamesExposeVisibilityAndPreserveSource()
{
const string source = "{foo := 10, _tmp := 20, `display name` := 30}";
var record = (RecordLiteralSyntax)((ClosedExpressionSyntax)ExpressifSyntax.Parse(source)).Value;
var names = record.Fields.Select(field => field.Name).ToArray();

Assert.Multiple(() =>
{
Assert.That(names.Select(name => name.Kind), Is.All.EqualTo(SyntaxKind.RecordFieldName));
Assert.That(names.Select(name => name.Value), Is.EqualTo(new[] { "foo", "_tmp", "display name" }));
Assert.That(names.Select(name => name.IsPrivate), Is.EqualTo(new[] { false, true, false }));
Assert.That(names.Select(name => name.Text), Is.EqualTo(new[] { "foo", "_tmp", "`display name`" }));
Assert.That(names.Select(name => name.Span), Is.EqualTo(new[]
{
new SourceSpan(1, 3),
new SourceSpan(12, 4),
new SourceSpan(24, 14),
}));
Assert.That(names[2].QuotingStyle, Is.EqualTo(QuotingStyle.Backtick));
Assert.That(names.SelectMany(name => name.Children), Is.Empty);
Assert.That(record.Fields[1].Children,
Is.EqualTo(new SyntaxNode[] { names[1], record.Fields[1].Value }));
});
}

[TestCase("123 | !equal-to(125)")]
[TestCase("123 | ! equal-to(125) ")]
[TestCase("123 | !equal-to(125) |OR even ")]
[TestCase("123 | ( ! equal-to(125) ) ")]
[TestCase("123 | ( ! equal-to(125) |OR even ) |AND !null ")]
public void NegatedPredicatesParseInPipelinesAndGroupedExpressions(string source)
{
var root = ExpressifSyntax.Parse(source);

Assert.That(root, Is.TypeOf<ClosedExpressionSyntax>());
}

[TestCase("|AND")]
[TestCase("|OR")]
[TestCase("|XOR")]
Expand Down
12 changes: 9 additions & 3 deletions bindings/csharp/Expressif.Syntax/ExpressifSyntax.cs
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,23 @@ private static RecordAccessSyntax BindRecordAccess(TsNode node)
private static RecordFieldSyntax BindRecordField(TsNode node)
{
var nameContainer = node.GetChildForField("name") ?? throw Unknown(node);
var name = SingleNamedChild(nameContainer, nameContainer.Type);
var visibility = SingleNamedChild(nameContainer, nameContainer.Type);
var name = visibility.Type == "public_record_field_name"
? SingleNamedChild(visibility, visibility.Type)
: visibility;
var value = node.GetChildForField("value") ?? throw Unknown(node);
QuotingStyle? quotingStyle = name.Type switch
{
"double_quoted_literal" => QuotingStyle.DoubleQuote,
"backtick_quoted_literal" => QuotingStyle.Backtick,
"unquoted_record_field_name" => null,
"unquoted_record_field_name" or "private_record_field_name" => null,
_ => throw Unknown(name),
};
var nameText = quotingStyle is null ? name.Text : name.Text[1..^1];
return new(Span(node), node.Text, nameText, quotingStyle, BindValue(value));
var nameSyntax = new RecordFieldNameSyntax(
Span(visibility), visibility.Text, nameText,
visibility.Type == "private_record_field_name", quotingStyle);
return new(Span(node), node.Text, nameSyntax, BindValue(value));
}

private static TsNode SingleNamedChild(TsNode node, string container)
Expand Down
29 changes: 24 additions & 5 deletions bindings/csharp/Expressif.Syntax/SyntaxNodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public enum SyntaxKind
TupleLiteral,
RecordLiteral,
RecordField,
RecordFieldName,
RecordSpread,
IncomingValue,
ParameterizedExpression,
Expand Down Expand Up @@ -311,19 +312,37 @@ protected RecordEntrySyntax(SyntaxKind kind, SourceSpan span, string text, IEnum

public sealed class RecordFieldSyntax : RecordEntrySyntax
{
internal RecordFieldSyntax(SourceSpan span, string text, string name, QuotingStyle? quotingStyle, ValueSyntax value)
: base(SyntaxKind.RecordField, span, text, [value])
internal RecordFieldSyntax(SourceSpan span, string text, RecordFieldNameSyntax name, ValueSyntax value)
: base(SyntaxKind.RecordField, span, text, [name, value])
{
Name = name;
QuotingStyle = quotingStyle;
Value = value;
}

public string Name { get; }
public QuotingStyle? QuotingStyle { get; }
public RecordFieldNameSyntax Name { get; }
public ValueSyntax Value { get; }
}

public sealed class RecordFieldNameSyntax : SyntaxNode
{
internal RecordFieldNameSyntax(
SourceSpan span,
string text,
string value,
bool isPrivate,
QuotingStyle? quotingStyle)
: base(SyntaxKind.RecordFieldName, span, text)
{
Value = value;
IsPrivate = isPrivate;
QuotingStyle = quotingStyle;
}

public string Value { get; }
public bool IsPrivate { get; }
public QuotingStyle? QuotingStyle { get; }
}

public sealed class RecordSpreadSyntax : RecordEntrySyntax
{
internal RecordSpreadSyntax(SourceSpan span, string text) : base(SyntaxKind.RecordSpread, span, text) { }
Expand Down
11 changes: 10 additions & 1 deletion grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export default grammar({
),

_pipeline_expression: ($) => choice(
$.binary_expression,
$.unary_expression,
$.function_call,
prec(1, $.record_access),
$.tuple_projection,
Expand Down Expand Up @@ -334,12 +336,19 @@ export default grammar({
incoming_value: (_) => "...",

record_field_name: ($) => choice(
$.private_record_field_name,
$.public_record_field_name,
),

private_record_field_name: (_) => /_[A-Za-z0-9_]*(?:-[A-Za-z0-9_]+)*/,

public_record_field_name: ($) => choice(
$.unquoted_record_field_name,
$.double_quoted_literal,
$.backtick_quoted_literal,
),

unquoted_record_field_name: (_) => /[A-Za-z_][A-Za-z0-9_]*(?:-[A-Za-z0-9_]+)*/,
unquoted_record_field_name: (_) => /[A-Za-z][A-Za-z0-9_]*(?:-[A-Za-z0-9_]+)*/,

numeric_literal: (_) => /-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?/,

Expand Down
27 changes: 26 additions & 1 deletion src/grammar.json

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

43 changes: 37 additions & 6 deletions src/node-types.json

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

Loading
Loading