Goal
Define the public API for turning Expressif source text into an executable expression while keeping parsing and binding independently accessible and replaceable.
The API should support three usage levels:
// zero-ceremony default
var expression = Expressif.Create("@x | upper");
// explicit stages
var syntax = ExpressionParser.Parse(text);
var expression = ExpressionBinder.Bind(syntax);
// customized construction
var factory = new ExpressionFactory(
parser: customParser,
binder: customBinder);
var expression = factory.Create(text);
The design should preserve a simple default API while allowing hosts to replace the parser and/or binder without global mutable configuration.
Public architecture
ExpressionParser
text -> syntax
ExpressionBinder
syntax -> IExpression
ExpressionFactory
configurable composition of parser + binder
text -> IExpression
Expressif
static convenience facade using the default ExpressionFactory
Create is not a third interpretation of the language. It is only the composition:
Create(text) = Bind(Parse(text))
ExpressionParser
ExpressionParser belongs to Expressif.Syntax and is responsible only for syntax.
It should:
- tokenize and parse Expressif source text;
- produce the canonical syntax/AST model;
- preserve syntactic structure such as literals, references, function calls, pipelines, arrays, tuples, records, arguments, etc.;
- report syntax errors and source locations;
- not resolve runtime function implementations;
- not create executable expressions;
- not depend on runtime function catalogs/providers.
Example:
var syntax = ExpressionParser.Parse("@x | upper");
The result is syntax, not an executable expression.
ExpressionBinder
ExpressionBinder is responsible for converting a parsed syntax tree into the runtime expression model.
syntax tree -> bound/executable expression
It should:
- resolve function names against the available function catalog/providers;
- bind positional and named arguments to function parameters;
- bind variables, references, literals, pipelines, and structural expressions to their runtime equivalents;
- perform semantic validation required for binding;
- report binding/semantic errors separately from parsing errors;
- contain no parsing logic.
Example:
var expression = ExpressionBinder.Bind(syntax);
ExpressionFactory
ExpressionFactory is the configurable construction API.
Its public responsibility is:
source/syntax -> IExpression
It composes an IExpressionParser and an IExpressionBinder and lets applications replace either component without modifying global state.
Conceptually:
public sealed class ExpressionFactory
{
public ExpressionFactory(
IExpressionParser parser,
IExpressionBinder binder)
{
Parser = parser;
Binder = binder;
}
public IExpressionParser Parser { get; }
public IExpressionBinder Binder { get; }
public IExpression Create(string text)
=> Create(Parser.Parse(text));
public IExpression Create(ExpressionSyntax syntax)
=> Binder.Bind(syntax);
}
The exact interface and constructor shape may differ, but the responsibility boundary should remain the same.
Typical customization:
var factory = new ExpressionFactory(
parser: customParser,
binder: customBinder);
var expression = factory.Create(text);
Replacing only one component should be possible without unnecessary ceremony, for example by using the standard implementation for the other component.
ExpressionFactory should not expose its own parsing or binding semantics. It delegates to its configured parser and binder.
Expressif
Expressif is the zero-ceremony static facade for consumers that want the standard configuration.
The common case remains:
var expression = Expressif.Create("@x | upper");
Conceptually:
public static class Expressif
{
private static readonly ExpressionFactory DefaultFactory = ...;
public static IExpression Create(string text)
=> DefaultFactory.Create(text);
}
Expressif must not expose mutable global parser/binder configuration such as:
Expressif.Parser = customParser;
Expressif.Binder = customBinder;
Customization belongs to ExpressionFactory instances so tests and concurrent consumers remain isolated.
Usage model
Common case
var expression = Expressif.Create(text);
Explicit parsing and binding
var syntax = ExpressionParser.Parse(text);
var expression = ExpressionBinder.Bind(syntax);
Custom parser and/or binder
var factory = new ExpressionFactory(
parser: customParser,
binder: customBinder);
var expression = factory.Create(text);
The same factory can also operate on an already parsed syntax tree:
var expression = factory.Create(syntax);
Why this separation
This keeps the layers usable independently while preserving a small public surface:
Expressif.Syntax and the Language Server can parse source code without loading or executing runtime functions;
- tooling can inspect syntax even when binding is impossible or incomplete;
- the runtime can bind an already parsed syntax tree without reparsing it;
- syntax errors and semantic/binding errors remain distinct;
- applications can replace the parser or binder through an
ExpressionFactory instance;
- no global mutable configuration is required;
- the common case remains one call:
Expressif.Create(text).
The terminology should remain consistent:
Parse = text -> syntax
Bind = syntax -> executable expression
Create = source/syntax -> executable expression
(factory operation; parse + bind when source text is supplied)
Acceptance criteria
- Introduce
ExpressionBinder.Bind(...) as the canonical syntax-to-runtime entry point.
- Parsing remains exclusively owned by
ExpressionParser / Expressif.Syntax.
ExpressionFactory is the configurable composition point for parser and binder.
ExpressionFactory.Create(string) parses through its configured parser and binds through its configured binder.
ExpressionFactory.Create(ExpressionSyntax) binds an already parsed syntax tree without reparsing it.
- A consumer can replace the parser, binder, or both through an
ExpressionFactory instance.
- Add
Expressif.Create(string) as the public zero-ceremony API using the default factory.
Expressif.Parse(...) and Expressif.Bind(...) may expose the default lower-level stages directly.
- No mutable global parser/binder configuration is introduced.
- No parsing logic exists in
ExpressionBinder.
- No runtime binding/function-resolution logic exists in
ExpressionParser.
ExpressionFactory does not duplicate parsing or binding logic.
- Parsing and binding failures remain distinguishable.
- Add tests covering the explicit two-step API, the configurable factory API, and the
Expressif.Create(string) convenience API.
Goal
Define the public API for turning Expressif source text into an executable expression while keeping parsing and binding independently accessible and replaceable.
The API should support three usage levels:
The design should preserve a simple default API while allowing hosts to replace the parser and/or binder without global mutable configuration.
Public architecture
Createis not a third interpretation of the language. It is only the composition:ExpressionParserExpressionParserbelongs toExpressif.Syntaxand is responsible only for syntax.It should:
Example:
The result is syntax, not an executable expression.
ExpressionBinderExpressionBinderis responsible for converting a parsed syntax tree into the runtime expression model.It should:
Example:
ExpressionFactoryExpressionFactoryis the configurable construction API.Its public responsibility is:
It composes an
IExpressionParserand anIExpressionBinderand lets applications replace either component without modifying global state.Conceptually:
The exact interface and constructor shape may differ, but the responsibility boundary should remain the same.
Typical customization:
Replacing only one component should be possible without unnecessary ceremony, for example by using the standard implementation for the other component.
ExpressionFactoryshould not expose its own parsing or binding semantics. It delegates to its configured parser and binder.ExpressifExpressifis the zero-ceremony static facade for consumers that want the standard configuration.The common case remains:
Conceptually:
Expressifmust not expose mutable global parser/binder configuration such as:Customization belongs to
ExpressionFactoryinstances so tests and concurrent consumers remain isolated.Usage model
Common case
Explicit parsing and binding
Custom parser and/or binder
The same factory can also operate on an already parsed syntax tree:
Why this separation
This keeps the layers usable independently while preserving a small public surface:
Expressif.Syntaxand the Language Server can parse source code without loading or executing runtime functions;ExpressionFactoryinstance;Expressif.Create(text).The terminology should remain consistent:
Acceptance criteria
ExpressionBinder.Bind(...)as the canonical syntax-to-runtime entry point.ExpressionParser/Expressif.Syntax.ExpressionFactoryis the configurable composition point for parser and binder.ExpressionFactory.Create(string)parses through its configured parser and binds through its configured binder.ExpressionFactory.Create(ExpressionSyntax)binds an already parsed syntax tree without reparsing it.ExpressionFactoryinstance.Expressif.Create(string)as the public zero-ceremony API using the default factory.Expressif.Parse(...)andExpressif.Bind(...)may expose the default lower-level stages directly.ExpressionBinder.ExpressionParser.ExpressionFactorydoes not duplicate parsing or binding logic.Expressif.Create(string)convenience API.