Skip to content

Implement C# bindings for basic expressions, function calls and literals #4

Description

@Seddryck

Description

Implement the first functional C# bindings for the syntax introduced by PR #2, feat: implement basic expressions, function calls and literals.

This issue depends on the .NET project/bootstrap work from #3. It must focus on the C# syntax API and its mapping to the Tree-sitter CST, not on solution/build infrastructure.

The C# bindings should expose a typed syntax model corresponding to the distinctions already established by the grammar rather than forcing consumers to manipulate raw Tree-sitter nodes and string node-type names.

Scope

Cover the CST produced for:

  • root expressions;
  • open expressions;
  • closed expressions;
  • expression/function pipelines;
  • function calls;
  • positional arguments;
  • values;
  • numeric literals;
  • boolean literals;
  • quoted literals;
  • temporal literals;
  • date, date-time and time literals.

The implementation must remain purely syntactic. Callable lookup, arity validation, parameter typing and conversion into runtime Expressif functions remain outside this project.

Typed syntax model

Expose public syntax-node types corresponding to the syntax model introduced by the grammar:

RootExpressionSyntax
├── OpenExpressionSyntax
└── ClosedExpressionSyntax

ExpressionSyntax
└── FunctionCallSyntax

ArgumentSyntax
└── PositionalArgumentSyntax

ValueSyntax
├── NumericLiteralSyntax
├── BooleanLiteralSyntax
├── QuotedLiteralSyntax
└── TemporalLiteralSyntax
    ├── DateLiteralSyntax
    ├── DateTimeLiteralSyntax
    └── TimeLiteralSyntax

The implementation may use abstract base classes, interfaces, records/classes, or another idiomatic C# hierarchy, but consumers should be able to distinguish these concepts through the type system without inspecting raw Tree-sitter node names.

Where the Tree-sitter grammar introduces implementation/container nodes such as source_file, argument_list, value, quoted_literal, or temporal_literal, the C# API does not need to mirror every wrapper node one-for-one if a cleaner typed API can hide them without losing syntax information.

Parser entry point

Provide a small public entry point for parsing Expressif source text into the typed C# syntax model.

For example, the API should make an operation equivalent to the following straightforward:

var root = ExpressifSyntax.Parse("10 | add(5)");

The exact type and method names may be adapted to the project conventions, but parsing should return a typed root syntax node rather than exposing only the underlying Tree-sitter tree.

Parsing must preserve access to syntax errors. Do not silently manufacture a valid typed tree from an invalid Tree-sitter parse.

Root, open and closed expressions

The C# binding must preserve the distinction introduced by the grammar:

lower                    → OpenExpressionSyntax
lower() | trim           → OpenExpressionSyntax
true                     → OpenExpressionSyntax
10                       → ClosedExpressionSyntax
10 | add(5)              → ClosedExpressionSyntax
#true                    → ClosedExpressionSyntax
"foo"                    → ClosedExpressionSyntax
#"2025-12-17"            → ClosedExpressionSyntax

For open expressions expose the ordered function/expression pipeline.

For closed expressions expose:

  • the initial value;
  • the ordered pipeline that follows it, which may be empty.

Ordering must follow source order exactly.

Function calls

Expose a FunctionCallSyntax abstraction with at least:

  • the function name as written in source;
  • ordered positional arguments;
  • whether parentheses were present when this distinction is retained by the CST/source span;
  • source span/text information through the common syntax-node API.

Function names must remain case-preserving.

Do not normalize names and do not validate whether a function exists.

All of the following are syntactically bindable:

lower
lower()
LOWER
text-to-lower
unknown(5, 10)

Positional arguments

Expose positional arguments in source order.

For this feature each argument contains a ValueSyntax.

Examples:

add(5)
between(5, 10)
foo(#true)
foo("bar")
foo(#"2025-12-17")

The API should make it possible to inspect the argument's typed value directly without navigating Tree-sitter wrapper nodes manually.

Numeric literals

Expose NumericLiteralSyntax while preserving the original source representation.

Examples:

0
42
-5
3.14
-3.14

Do not make parsing dependent on a single runtime numeric representation. A convenient conversion API may be added later, but the syntax binding must retain the lexical text losslessly.

Boolean literals

Expose BooleanLiteralSyntax for:

#true
#false

A strongly typed boolean value may be exposed in addition to the original source text.

Bare true and false must continue to bind as function calls because that is how the grammar classifies them.

Quoted literals

Expose QuotedLiteralSyntax for both supported quoting forms:

"foo"
""
"Alice said \"hello\"."
`foo`
` foo bar `

The binding must preserve:

  • the original source text;
  • the quoting style (double quote vs backtick);
  • escaped source representation.

It may additionally expose the logical/unescaped content, but doing so must not discard the original representation.

Temporal literals

Expose the temporal hierarchy distinctly:

#"2025-12-17"             → DateLiteralSyntax
#"2025-12-17T14:30:00"    → DateTimeLiteralSyntax
#"2025-12-17 14:30:00"    → DateTimeLiteralSyntax
#"14:30:00"               → TimeLiteralSyntax

Preserve the lexical source, including whether the date-time separator was T or a space.

Typed convenience values such as DateOnly, DateTime, or TimeOnly may be exposed only if they do not replace the source-preserving syntax representation.

Common syntax-node information

All public syntax nodes should provide a consistent way to access useful syntax metadata such as:

  • node kind/type;
  • source span/range;
  • original source text or an equivalent lossless slice;
  • parent/children or a deliberate typed traversal model where useful.

Avoid leaking raw Tree-sitter handles as the only usable API. Low-level access may still be exposed for advanced scenarios if useful.

The wrapper lifetime must be safe: a bound syntax node must not reference native Tree-sitter memory that has already been released.

Tree-sitter mapping

Map the generated CST from PR #2, including at least these named nodes:

root_expression
open_expression
closed_expression
expression
function_call
function_name
argument_list
positional_argument
value
numeric_literal
boolean_literal
quoted_literal
double_quoted_literal
backtick_quoted_literal
temporal_literal
date_literal
date_time_literal
time_literal

Unknown named node types should fail clearly during binding rather than being silently reinterpreted as another syntax kind. The design should nevertheless make it straightforward to extend the mapper as future grammar issues add new node types.

Tests

Add NUnit tests in Expressif.Syntax.Tests covering at minimum:

  • parsing returns OpenExpressionSyntax vs ClosedExpressionSyntax correctly;
  • one-function and multi-function pipelines preserve ordering;
  • zero-argument calls with and without parentheses;
  • mixed-case and hyphenated function names remain unchanged;
  • one and multiple positional arguments;
  • positive, negative and decimal numeric literals;
  • #true and #false bind as booleans;
  • bare true and false bind as function calls;
  • double-quoted and backtick-quoted literals;
  • empty quoted literals;
  • escaped double quotes;
  • date literals;
  • date-time literals using both T and space;
  • time literals;
  • ordinary quoted temporal-looking text remains QuotedLiteralSyntax;
  • source ranges/text are correct;
  • malformed input exposes parse/syntax errors rather than a misleading valid model.

Where practical, reuse the examples and variants already present in the Tree-sitter corpus tests so the C# binding test suite verifies the same language surface rather than inventing a smaller parallel subset.

Design constraints

  • Do not encode knowledge of the Expressif function catalogue.
  • Do not validate callable existence or arity.
  • Do not convert syntax nodes into runtime IFunction/predicate/accumulator implementations.
  • Do not collapse syntactically distinct literal forms into one untyped value object.
  • Do not lose original source representation when exposing convenient typed values.
  • Keep the mapping extensible for the later syntax features already planned for Expressif.Syntax.

Dependency

Blocked by #3.

The grammar/CST implemented by PR #2 is the source of truth for this first binding increment.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions