diff --git a/Directory.Build.props b/Directory.Build.props
index f0818df..2d83b6f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,6 +15,7 @@
$(NoWarn);CA1716
$(NoWarn);CA1510
$(NoWarn);CA1711
+ $(NoWarn);CA1863
diff --git a/src/Acornima.Extras/Acornima.Extras.csproj b/src/Acornima.Extras/Acornima.Extras.csproj
index f0df537..df3abba 100644
--- a/src/Acornima.Extras/Acornima.Extras.csproj
+++ b/src/Acornima.Extras/Acornima.Extras.csproj
@@ -2,7 +2,7 @@
Acornima
- net6.0;net462;netstandard2.0;netstandard2.1
+ net8.0;net6.0;net462;netstandard2.1;netstandard2.0
true
Acornima.Extras
diff --git a/src/Acornima.Extras/ParserOptionsExtensions.cs b/src/Acornima.Extras/ParserOptionsExtensions.cs
index eab18e8..81ebddd 100644
--- a/src/Acornima.Extras/ParserOptionsExtensions.cs
+++ b/src/Acornima.Extras/ParserOptionsExtensions.cs
@@ -1,27 +1,55 @@
-using System;
+using Acornima.Ast;
namespace Acornima;
public static class ParserOptionsExtensions
{
- private static readonly OnNodeHandler s_parentSetter = node =>
+ public static TOptions RecordParentNodeInUserData(this TOptions options, bool enable = true)
+ where TOptions : ParserOptions
{
- foreach (var child in node.ChildNodes)
+ var helper = options._onNode?.Target as OnNodeHelper;
+ if (enable)
+ {
+ (helper ?? new OnNodeHelper()).EnableParentNodeRecoding(options);
+ }
+ else
{
- child.UserData = node;
+ helper?.DisableParentNodeRecoding(options);
}
- };
- public static TOptions RecordParentNodeInUserData(this TOptions options, bool enable = true)
- where TOptions : ParserOptions
+ return options;
+ }
+
+ private sealed class OnNodeHelper : IOnNodeHandlerWrapper
{
- options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options._onNode, s_parentSetter);
+ private OnNodeHandler? _onNode;
+ public OnNodeHandler? OnNode { get => _onNode; set => _onNode = value; }
- if (enable)
+ public void EnableParentNodeRecoding(ParserOptions options)
{
- options._onNode += s_parentSetter;
+ if (!ReferenceEquals(options._onNode?.Target, this))
+ {
+ _onNode = options._onNode;
+ options._onNode = SetParentNode;
+ }
}
- return options;
+ public void DisableParentNodeRecoding(ParserOptions options)
+ {
+ if (options._onNode == SetParentNode)
+ {
+ options._onNode = _onNode;
+ }
+ }
+
+ private void SetParentNode(Node node, OnNodeContext context)
+ {
+ foreach (var child in node.ChildNodes)
+ {
+ child.UserData = node;
+ }
+
+ _onNode?.Invoke(node, context);
+ }
}
}
diff --git a/src/Acornima/Acornima.csproj b/src/Acornima/Acornima.csproj
index 91a36d4..7376464 100644
--- a/src/Acornima/Acornima.csproj
+++ b/src/Acornima/Acornima.csproj
@@ -1,7 +1,7 @@
- net6.0;net462;netstandard2.0;netstandard2.1
+ net8.0;net6.0;net462;netstandard2.1;netstandard2.0
true
Acornima
diff --git a/src/Acornima/Helpers/ArrayList.cs b/src/Acornima/Helpers/ArrayList.cs
index 3a7b64f..b03a084 100644
--- a/src/Acornima/Helpers/ArrayList.cs
+++ b/src/Acornima/Helpers/ArrayList.cs
@@ -74,9 +74,9 @@ namespace Acornima.Helpers;
[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}, {nameof(Capacity)} = {{{nameof(Capacity)}}}, Version = {{{nameof(_localVersion)}}}")]
[DebuggerTypeProxy(typeof(ArrayList<>.DebugView))]
#endif
-internal partial struct ArrayList : IList
+internal partial struct ArrayList : IList, IReadOnlyList
{
- private const int MinAllocatedCount = 4;
+ internal const int MinAllocatedCount = 4;
private T[]? _items;
private int _count;
@@ -239,7 +239,7 @@ internal readonly ref T GetItemRef(int index)
internal readonly ref T LastItemRef() => ref GetItemRef(_count - 1);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static long GrowCapacity(int capacity)
+ internal static long GrowCapacity(int capacity)
{
// NOTE: Using a growth factor of 3/2 yields better benchmark results than 2.
// It also results in less excess when the underlying array is returned directly wrapped in a NodeList, Span, etc.
diff --git a/src/Acornima/Helpers/ReadOnlyRef.cs b/src/Acornima/Helpers/ReadOnlyRef.cs
new file mode 100644
index 0000000..1aa5ad1
--- /dev/null
+++ b/src/Acornima/Helpers/ReadOnlyRef.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Acornima.Helpers;
+
+///
+/// A struct that can store a read-only managed reference.
+///
+internal readonly ref struct ReadOnlyRef
+{
+#if NET7_0_OR_GREATER
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyRef(ref readonly T value)
+ {
+ Value = ref value;
+ }
+
+ public readonly ref readonly T Value;
+#else
+ private readonly ReadOnlySpan _value;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
+ public ReadOnlyRef(ref readonly T value)
+ {
+ _value = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in value), 1);
+ }
+#else
+ public ReadOnlyRef(ReadOnlySpan span, int index)
+ {
+ _value = span.Slice(index, 1);
+ }
+#endif
+
+ public ref readonly T Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref MemoryMarshal.GetReference(_value); }
+#endif
+}
diff --git a/src/Acornima/OnNodeContext.cs b/src/Acornima/OnNodeContext.cs
new file mode 100644
index 0000000..771bbc2
--- /dev/null
+++ b/src/Acornima/OnNodeContext.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Runtime.CompilerServices;
+using Acornima.Helpers;
+
+namespace Acornima;
+
+using static ExceptionHelper;
+
+public readonly ref struct OnNodeContext
+{
+ internal readonly ReadOnlyRef _scope;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal OnNodeContext(ReadOnlyRef scope, ArrayList scopeStack)
+ {
+ _scope = scope;
+ ScopeStack = scopeStack.AsReadOnlySpan();
+ }
+
+ public bool HasScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !Unsafe.IsNullRef(ref Unsafe.AsRef(in _scope.Value)); }
+
+ public ref readonly Scope Scope
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref readonly var scope = ref _scope.Value;
+ if (Unsafe.IsNullRef(ref Unsafe.AsRef(in scope)))
+ {
+ ThrowInvalidOperationException