Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
343f26f
Benchmark the expression language, from parsing to the layers above it
sebastienros Nov 2, 2026
eecad8a
Add a Parlot parser for the expression language next to the ANTLR one
sebastienros Nov 2, 2026
795ace4
Parse expressions with the Parlot parser
sebastienros Nov 2, 2026
d3a4f0e
Remove ANTLR
sebastienros Nov 2, 2026
26cd12e
Run the benchmarks on any platform
sebastienros Nov 2, 2026
8de4591
Measure ObjectWrapper apart from the size of the expressions
sebastienros Nov 2, 2026
911071f
Write up the Parlot port: equivalence, public API changes, benchmarks…
sebastienros Nov 2, 2026
e23a61d
Fix what the review of the parser found
sebastienros Nov 2, 2026
de18281
Restore the line endings of the files the port rewrote
sebastienros Nov 2, 2026
3704000
Update the write-up after the fixes of the review
sebastienros Nov 2, 2026
481d41a
Fix what the second review found
sebastienros Nov 3, 2026
8eabfb3
Update the write-up after the second review
sebastienros Nov 3, 2026
f7d09b6
Apply the notes for the 4.0 groundwork
sebastienros Nov 3, 2026
a24e051
Merge remote-tracking branch 'upstream/main' into feature/parlot-expr…
sebastienros Nov 4, 2026
bd4974f
Fix the examples restore and the build schema
sebastienros Nov 4, 2026
339a148
Report the end of the input, and serialize syntax errors as exceptions
sebastienros Nov 4, 2026
75fd72e
Keep the design notes next to the grammar and complete the breaking c…
sebastienros Nov 4, 2026
32894f6
Drop the 1,900 unary-minus case from EvaluatingTooDeepATreeThrows
lahma Nov 4, 2026
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
1 change: 0 additions & 1 deletion .fallout/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"ExecutableTarget": {
"type": "string",
"enum": [
"Antlr",
"Ci",
"Clean",
"Compile",
Expand Down
4 changes: 1 addition & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ Build orchestration is Fallout (`build-support/build`), the maintained successor
./build.cmd Test # run the default test suite
./build.cmd Ci # Compile + Test + Pack — what CI runs
./build.cmd Clean
./build.cmd Antlr # regenerate the expression-language parser (Windows only)
```

Plain `dotnet` CLI works too — the solution is `Spring.Net.sln`:
Expand Down Expand Up @@ -59,7 +58,7 @@ Key mechanics (defined in `src/Directory.Build.props` and `test/Directory.Build.
- **Multi-targeting**: most libraries target `netstandard2.0;net472;net10.0`; web projects are net472-only. Projects reference the shared MSBuild variables `$(TargetFullFrameworkVersion)` (= net472) and `$(TargetModernFrameworkVersion)` (= net10.0) instead of literal TFMs. Framework differences are handled with conditional `<Compile Remove>`/`<ItemGroup>` blocks in csproj files, not scattered `#if` directives. Key them to what they mean (`'$(TargetFrameworkIdentifier)' == '.NETFramework'` or `NETFRAMEWORK`, never "not netstandard"). The one exception is the legacy-serialization obsoletion: `ObsoleteAttribute.DiagnosticId` only exists on modern .NET, so each serialization constructor and `GetObjectData` carries a `#if NET` guard around its `[Obsolete(Obsoletions...)]` (constants in `src/Spring/Obsoletions.cs`, linked into each library).
- **Strong naming**: all assemblies signed with the committed `Spring.Net.snk`.
- **Central package management**: versions live in `Directory.Packages.props`; csproj `PackageReference`s are versionless.
- **ANTLR expression parser**: the grammar is `src/Spring/Spring.Core/Expressions/Expression.g`. The generated lexer/parser under `Expressions/Parser/` and a vendored, hand-patched ANTLR 2.7.7 runtime under `Expressions/Parser/antlr/` are **committed — never hand-edit generated files**; regenerate with `./build.cmd Antlr` (see `src/Spring/Spring.Core/README_ANTLR.txt`).
- **Expression parser**: the grammar is `src/Spring/Spring.Core/Expressions/ExpressionGrammar.parlot.cs`, a build-only file that the `Parlot.SourceGenerator` analyzer turns into code of `Spring.Core`, with no runtime dependency on Parlot. It needs the Roslyn of the .NET 10 SDK. Its tokens, tree and syntax errors are in `ExpressionGrammar.cs`, and `ExpressionGrammarTests` holds the trees of the former ANTLR parser. `src/Spring/Spring.Core/Expressions/README.md` has the quirks kept on purpose and how the grammar stays equivalent.
- **Versioning**: the git tag is the version authority (`v3.1.0` → 3.1.0); untagged builds fall back to the `ProjectVersion` placeholder in `build-support/build/Build.cs` plus a `dev-`/`preview-` suffix. Publishing runs from the tag workflow only. `DetermineVersion()` sets three distinct facets, applied to **both** `Compile` and `Pack` so packaged and `bin/` assemblies agree: `AssemblyVersion` is pinned to `major.minor.0.0` (the assemblies are strong-named, so this is their binding identity — keeping it stable across patches means consumers need no new binding redirect for a fix release), `FileVersion` carries the exact numeric version, and `InformationalVersion` is left to MSBuild so SourceLink can append `+<sha>`. Never set a bare `Version` property instead — it overrides the prefix/suffix composition and silently drops the preview suffix.
- **Trusted publishing**: `.github/workflows/publish.yml` is the only workflow allowed to push to nuget.org. It needs `permissions: id-token: write` and `environment: nuget`; the `Publish` target exchanges that OIDC token for a short-lived API key in `build-support/build/Build.Publish.cs`. The nuget.org policy is keyed to the **workflow filename** `publish.yml` plus the `nuget` environment, so renaming, moving or splitting that workflow breaks publishing until the policy is updated. Do not reintroduce a `NUGET_API_KEY` secret — the `NuGetApiKey` parameter stays only as a manual override.
- **Package READMEs and package validation**: every packable project under `src/Spring` has a `README.md` next to its csproj, which `src/Directory.Build.props` packs as the NuGet readme; a new package needs one (what it does, `dotnet add package`, target frameworks, a minimal snippet whose types, members and XML are checked against the code, and links). Pack also runs package validation (`EnablePackageValidation`) against the 3.1.0 packages (`PackageValidationBaselineVersion`) and between the target frameworks of one package; never suppress a difference between two TFMs of one package (Spring.Messaging turns validation off for that reason, see its csproj). Accepted baseline differences live in each project's `CompatibilitySuppressions.xml`; regenerate one with `dotnet pack <csproj> -c Release -p:ApiCompatGenerateSuppressionFile=true -p:AssemblyVersion=<major>.<minor>.0.0` and review the diff. A direct `dotnet pack` needs that `-p:AssemblyVersion=<major>.<minor>.0.0` (what Fallout passes), because the default 1.0.0.0 is below the baseline and fails with CP0003; do not gate the baseline on AssemblyVersion, since Fallout's Restore passes no version and the baseline download would vanish. The baseline has a coverage gap: 3.1.0 shipped net462, which no 4.0 asset is compatible with, so Spring.Web, Spring.Web.Mvc5, Spring.Web.Conversation.NHibernate5 and Spring.Testing.NUnit get no baseline API comparison (PKV006), and for the other packages the 3.1.0 net462 asset is compared with the 4.0 netstandard2.0 build, so no net472 build is compared with 3.1.0. The gap closes when the baseline moves to 4.0.0 once 4.0.0 ships; then delete the 3.1.0 suppression files.
Expand All @@ -74,7 +73,6 @@ Key mechanics (defined in `src/Directory.Build.props` and `test/Directory.Build.
## Legacy areas — do not modify

- `doc/` — DocBook reference sources and their vendored toolchain (CI ignores `doc/**`); kept until the documentation moves to Markdown.
- `build-support/tools/antlr-2.7.6` — used by the `Antlr` target until the Parlot parser (#375) replaces ANTLR.

## Website

Expand Down
24 changes: 24 additions & 0 deletions BreakingChanges.txt
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,30 @@ Changes (3.1 to 4.0)
- Before: ActionExpressionText="#log.Fatal('msg', #e)"
- After: ActionExpressionText="'msg'" LogLevel="Critical"

11. The expression language is parsed by a parser generated with Parlot at build time, without a
runtime dependency on it, instead of the ANTLR 2 runtime that Spring.Core embedded:
- The namespaces Spring.Expressions.Parser.antlr, .antlr.debug, .antlr.collections and
.antlr.collections.impl are gone, with all their public types.
- Spring.Expressions.SpringAST has no base class any more, and getFirstChild, getNextSibling, addChild,
setFirstChild and setNextSibling use SpringAST instead of the AST interface. It lost the protected down and
right fields, ICloneable and Clone, SpringAST(IToken) and initialize(IToken), and the helpers it inherited
from BaseAST: EqualsList, EqualsListPartial, EqualsTree, EqualsTreePartial, findAll, findAllPartial, ToTree,
xmlSerialize, xmlSerializeNode, xmlSerializeRootOpen, xmlSerializeRootClose, encode, decode and
setVerboseStringConversion.
- Syntax errors are still Spring.Expressions.SyntaxErrorException, which is now public and derives from
Exception: catch it instead of RecognitionException or TokenStreamRecognitionException.
- An error can report a later position than it did, at the token that could not be read, and its
message can be different. The expressions accepted and their trees are the same, except that a
QualifiedIdentifier has no child nodes any more (getText() returns the same value). The serialized form of
a parsed expression is the same.
- U+FFFF in an expression is now an invalid char. ANTLR took it for the end of the input and silently dropped
what followed.
- A nested expression too deep for the stack of the thread, or 2,000 levels of parentheses or prefix
operators, is a SyntaxErrorException instead of a stack overflow.
- Evaluating a tree too deep for the stack of the thread throws InsufficientExecutionStackException
instead of overflowing it.
- Spring.Core depends on System.Memory explicitly on .NET Framework and .NET Standard 2.0.

Changes (3.0.x to 3.1)
========================

Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
<PackageVersion Include="NUnit.Analyzers" Version="4.15.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="6.3.1" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="Parlot.SourceGenerator" Version="2.2.1" />
<PackageVersion Include="Quartz" Version="3.22.9" />
<PackageVersion Include="System.CodeDom" Version="8.0.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.1" />
<PackageVersion Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageVersion Include="System.Data.SqlClient" Version="4.9.1" />
<PackageVersion Include="System.Memory" Version="4.5.5" />
<PackageVersion Include="System.Reflection.Emit" Version="4.7.0" />
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" />
<PackageVersion Include="System.Resources.Extensions" Version="10.0.14" />
Expand Down
13 changes: 0 additions & 13 deletions build-support/build/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,19 +161,6 @@ void DetermineVersion()
}
});

Target Antlr => _ => _
.Executes(() =>
{
var projectDir = Solution.GetProject("Spring.Core");
var expressionsDir = Path.Combine(projectDir.Directory, "Expressions");
var antlrExecutable = Path.Combine(Solution.Directory, "build-support/tools/antlr-2.7.6/antlr-2.7.6.exe");
var process = StartProcess(
toolPath: antlrExecutable,
arguments: $"-o {expressionsDir}/Parser {expressionsDir}/Expression.g"
);
process.WaitForExit();
});

Target Pack => _ => _
.After(Test)
.DependsOn(Restore)
Expand Down
Binary file removed build-support/tools/antlr-2.7.6/antlr-2.7.6.exe
Binary file not shown.
Binary file removed build-support/tools/antlr-2.7.6/antlr.jar
Binary file not shown.
Binary file removed build-support/tools/antlr-2.7.6/charset.dll
Binary file not shown.
Binary file removed build-support/tools/antlr-2.7.6/libiconv-2.dll
Binary file not shown.
7 changes: 7 additions & 0 deletions examples/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Packages.props', '$(MSBuildThisFileDirectory)../'))" />
<ItemGroup>
<!-- The examples pin transitive packages, and their test adapters need a newer System.Memory than Spring's 4.5.5 floor. -->
<PackageVersion Update="System.Memory" Version="4.6.3" />
</ItemGroup>
</Project>
Loading
Loading