Skip to content

Commit 5b842c3

Browse files
committed
Precompute scope references (Parent, VarScope, ThisScope) during parsing + make parent node and scope info tracking composable
1 parent 57e4453 commit 5b842c3

File tree

11 files changed

+330
-133
lines changed

11 files changed

+330
-133
lines changed

samples/Acornima.Cli/Commands/PrintScopesCommand.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ public int OnExecute()
5050
_ => throw new InvalidOperationException()
5151
};
5252

53-
5453
var treePrinter = new TreePrinter(_console);
5554
treePrinter.Print(new[] { rootNode },
5655
node => node
Lines changed: 133 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,166 @@
11
using System;
22
using Acornima.Ast;
3+
using Acornima.Helpers;
34

45
namespace Acornima;
56

67
public static class ParserOptionsExtensions
78
{
8-
private static readonly OnNodeHandler s_parentSetter = (Node node, in Scope _, ReadOnlySpan<Scope> _) =>
9+
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true)
10+
where TOptions : ParserOptions
911
{
10-
foreach (var child in node.ChildNodes)
12+
var helper = options._onNode?.GetInvocationList() is { } invocationList
13+
? (OnNodeHelper?)Array.Find(invocationList, invocation => invocation.Target is OnNodeHelper)?.Target
14+
: null;
15+
16+
if (enable)
1117
{
12-
child.UserData = node;
18+
(helper ??= new OnNodeHelper()).EnableParentNodeRecoding(options);
1319
}
14-
};
20+
else
21+
{
22+
helper?.DisableParentNodeRecoding(options);
23+
}
24+
25+
return options;
26+
}
1527

16-
private static readonly OnNodeHandler s_scopeInfoSetter = (Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) =>
28+
public static TOptions RecordScopeInfoInUserData<TOptions>(this TOptions options, bool enable = true)
29+
where TOptions : ParserOptions
1730
{
18-
if (!Scope.IsNullRef(scope))
31+
var helper = options._onNode?.GetInvocationList() is { } invocationList
32+
? (OnNodeHelper?)Array.Find(invocationList, invocation => invocation.Target is OnNodeHelper)?.Target
33+
: null;
34+
35+
if (enable)
1936
{
20-
node.UserData = new ScopeInfo(node, scope, scopeStack);
37+
(helper ??= new OnNodeHelper()).EnableScopeInfoRecoding(options);
38+
}
39+
else
40+
{
41+
helper?.DisableScopeInfoRecoding(options);
2142
}
2243

23-
foreach (var child in node.ChildNodes)
44+
return options;
45+
}
46+
47+
private sealed class OnNodeHelper
48+
{
49+
private OnNodeHandler? _handler;
50+
private ArrayList<ScopeInfo> _scopes;
51+
52+
private void ReleaseLargeBuffers()
2453
{
25-
if (child.UserData is ScopeInfo scopeInfo)
54+
_scopes.Clear();
55+
if (_scopes.Capacity > 64)
2656
{
27-
scopeInfo.AssociatedNodeParent = node;
57+
_scopes.Capacity = 64;
2858
}
29-
else
59+
}
60+
61+
public void EnableParentNodeRecoding(ParserOptions options)
62+
{
63+
if (_handler is null)
3064
{
31-
child.UserData = node;
65+
options._onReleaseLargeBuffers += ReleaseLargeBuffers;
66+
options._onNode += _handler = SetParentNode;
67+
}
68+
else if (_handler == SetScopeInfo)
69+
{
70+
options._onNode -= _handler;
71+
options._onNode += _handler = SetParentNodeAndScopeInfo;
3272
}
3373
}
34-
};
3574

36-
/// <remarks>
37-
/// WARNING: Enabling this together with <see cref="RecordScopeInfoInUserData"/> is an undefined behavior.
38-
/// </remarks>
39-
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true)
40-
where TOptions : ParserOptions
41-
{
42-
options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options._onNode, s_parentSetter);
75+
public void DisableParentNodeRecoding(ParserOptions options)
76+
{
77+
if (_handler == SetParentNodeAndScopeInfo)
78+
{
79+
options._onNode -= _handler;
80+
options._onNode += _handler = SetScopeInfo;
81+
}
82+
else if (_handler == SetParentNode)
83+
{
84+
options._onNode -= _handler;
85+
options._onReleaseLargeBuffers -= ReleaseLargeBuffers;
86+
ReleaseLargeBuffers();
87+
}
88+
}
4389

44-
if (enable)
90+
public void EnableScopeInfoRecoding(ParserOptions options)
4591
{
46-
options._onNode += s_parentSetter;
92+
if (_handler is null)
93+
{
94+
options._onReleaseLargeBuffers += ReleaseLargeBuffers;
95+
options._onNode += _handler = SetScopeInfo;
96+
}
97+
else if (_handler == SetParentNode)
98+
{
99+
options._onNode -= _handler;
100+
options._onNode += _handler = SetParentNodeAndScopeInfo;
101+
}
47102
}
48103

49-
return options;
50-
}
104+
public void DisableScopeInfoRecoding(ParserOptions options)
105+
{
106+
if (_handler == SetParentNodeAndScopeInfo)
107+
{
108+
options._onNode -= _handler;
109+
options._onNode += _handler = SetParentNode;
110+
}
111+
else if (_handler == SetScopeInfo)
112+
{
113+
options._onNode -= _handler;
114+
options._onReleaseLargeBuffers -= ReleaseLargeBuffers;
115+
ReleaseLargeBuffers();
116+
}
117+
}
51118

52-
/// <remarks>
53-
/// WARNING: Enabling this together with <see cref="RecordParentNodeInUserData"/> is an undefined behavior.
54-
/// </remarks>
55-
public static TOptions RecordScopeInfoInUserData<TOptions>(this TOptions options, bool enable = true)
56-
where TOptions : ParserOptions
57-
{
58-
options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options.OnNode, s_scopeInfoSetter);
119+
private void SetParentNode(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack)
120+
{
121+
foreach (var child in node.ChildNodes)
122+
{
123+
child.UserData = node;
124+
}
125+
}
59126

60-
if (enable)
127+
private void SetScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack)
61128
{
62-
options._onNode += s_scopeInfoSetter;
129+
if (!Scope.IsNullRef(scope))
130+
{
131+
for (var n = scope.Id - _scopes.Count; n >= 0; n--)
132+
{
133+
ref var scopeInfoRef = ref _scopes.PushRef();
134+
scopeInfoRef ??= new ScopeInfo();
135+
}
136+
137+
var scopeInfo = _scopes[scope.Id];
138+
scopeInfo.Initialize(node,
139+
parent: scope.Id != scopeStack[0].Id ? _scopes[scopeStack.Last().Id] : null,
140+
varScope: scope.CurrentVarScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentVarScopeIndex].Id],
141+
thisScope: scope.CurrentThisScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentThisScopeIndex].Id],
142+
varNames: scope.VarNames,
143+
lexicalNames: scope.LexicalNames,
144+
functionNames: scope.FunctionNames);
145+
node.UserData = scopeInfo;
146+
}
63147
}
64148

65-
return options;
149+
private void SetParentNodeAndScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack)
150+
{
151+
SetScopeInfo(node, scope, scopeStack);
152+
153+
foreach (var child in node.ChildNodes)
154+
{
155+
if (child.UserData is ScopeInfo scopeInfo)
156+
{
157+
scopeInfo.UserData = node;
158+
}
159+
else
160+
{
161+
child.UserData = node;
162+
}
163+
}
164+
}
66165
}
67166
}

src/Acornima.Extras/ScopeInfo.cs

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,76 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
2+
using System.Runtime.CompilerServices;
43
using Acornima.Ast;
54

65
namespace Acornima;
76

87
public sealed class ScopeInfo
98
{
10-
private readonly int _id;
11-
private readonly int _currentVarScopeId;
12-
private readonly int _currentThisScopeId;
9+
private VariableNameCollection _varNames;
10+
private VariableNameCollection _lexicalNames;
11+
private VariableNameCollection _functionNames;
1312

14-
public ScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack)
13+
public static ScopeInfo From(Node associatedNode,
14+
ScopeInfo? parent, ScopeInfo? varScope, ScopeInfo? thisScope,
15+
ReadOnlySpan<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames)
1516
{
16-
AssociatedNode = node;
17-
_id = scope.Id;
18-
_currentVarScopeId = scope.CurrentVarScopeIndex == scopeStack.Length ? scope.Id : scopeStack[scope.CurrentVarScopeIndex].Id;
19-
_currentThisScopeId = scope.CurrentThisScopeIndex == scopeStack.Length ? scope.Id : scopeStack[scope.CurrentThisScopeIndex].Id;
20-
VarNames = scope.VarNames.ToArray();
21-
LexicalNames = scope.LexicalNames.ToArray();
22-
FunctionNames = scope.FunctionNames.ToArray();
17+
var scope = new ScopeInfo();
18+
scope.Initialize(associatedNode ?? throw new ArgumentNullException(nameof(associatedNode)),
19+
parent, varScope ?? scope, thisScope ?? scope,
20+
varNames, lexicalNames, functionNames);
21+
return scope;
2322
}
2423

25-
public Node AssociatedNode { get; }
26-
public Node? AssociatedNodeParent { get; internal set; }
24+
internal ScopeInfo()
25+
{
26+
AssociatedNode = null!;
27+
VarScope = ThisScope = this;
28+
}
29+
30+
internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope,
31+
ReadOnlySpan<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames)
32+
{
33+
AssociatedNode = associatedNode;
34+
Parent = parent;
35+
VarScope = varScope;
36+
ThisScope = thisScope;
37+
_varNames = new VariableNameCollection(varNames);
38+
_lexicalNames = new VariableNameCollection(lexicalNames);
39+
_functionNames = new VariableNameCollection(functionNames);
40+
}
2741

28-
public string[] VarNames { get; }
29-
public string[] LexicalNames { get; }
30-
public string[] FunctionNames { get; }
42+
public Node AssociatedNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }
3143

32-
// These lookups could as well be cached.
33-
public ScopeInfo? Parent => FindAncestor(_ => true);
34-
public ScopeInfo? VarScope => FindAncestor(scope => scope._id == _currentVarScopeId);
35-
public ScopeInfo? ThisScope => FindAncestor(scope => scope._id == _currentThisScopeId);
44+
public ScopeInfo? Parent { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }
45+
public ScopeInfo VarScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }
46+
public ScopeInfo ThisScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }
3647

37-
private ScopeInfo? FindAncestor(Func<ScopeInfo, bool> predicate)
48+
/// <summary>
49+
/// A list of distinct var-declared names sorted in ascending order in the current lexical scope.
50+
/// </summary>
51+
public VariableNameCollection VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _varNames; }
52+
53+
/// <summary>
54+
/// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope.
55+
/// </summary>
56+
public VariableNameCollection LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexicalNames; }
57+
58+
/// <summary>
59+
/// A list of distinct lexically-declared <see cref="FunctionDeclaration"/> names sorted in ascending order in the current lexical scope.
60+
/// </summary>
61+
public VariableNameCollection FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functionNames; }
62+
63+
/// <summary>
64+
/// Gets or sets the arbitrary, user-defined data object associated with the current <see cref="ScopeInfo"/>.
65+
/// </summary>
66+
/// <remarks>
67+
/// The operation is not guaranteed to be thread-safe. In case concurrent access or update is possible, the necessary synchronization is caller's responsibility.
68+
/// </remarks>
69+
public object? UserData
3870
{
39-
var node = AssociatedNode;
40-
while ((node = GetParentNode(node!)!) is not null)
41-
{
42-
if (node.UserData is ScopeInfo scopeInfo && predicate(scopeInfo))
43-
{
44-
return scopeInfo;
45-
}
46-
}
47-
48-
return null;
71+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
72+
get;
73+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
74+
set;
4975
}
50-
51-
private static Node? GetParentNode(Node node)
52-
=> node.UserData is ScopeInfo scopeInfo ? scopeInfo.AssociatedNodeParent : (Node?)node.UserData;
5376
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Diagnostics;
5+
using System.Linq;
6+
using System.Runtime.CompilerServices;
7+
using Acornima.Helpers;
8+
9+
namespace Acornima;
10+
11+
[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}")]
12+
[DebuggerTypeProxy(typeof(DebugView))]
13+
public readonly struct VariableNameCollection : IReadOnlyCollection<string>
14+
{
15+
private readonly ArrayList<string> _names;
16+
17+
public VariableNameCollection(ReadOnlySpan<string> names)
18+
{
19+
if (names.Length > 0)
20+
{
21+
_names = new ArrayList<string>(initialCapacity: names.Length);
22+
for (var i = 0; i < names.Length; i++)
23+
{
24+
var name = names[i];
25+
var index = _names.AsReadOnlySpan().BinarySearch(name);
26+
if (index < 0)
27+
{
28+
_names.Insert(~index, name);
29+
}
30+
}
31+
_names.TrimExcess();
32+
}
33+
}
34+
35+
public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _names.Count; }
36+
37+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
38+
public bool Contains(string name)
39+
{
40+
return _names.AsReadOnlySpan().BinarySearch(name) >= 0;
41+
}
42+
43+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44+
public IEnumerator<string> GetEnumerator() => _names.GetEnumerator();
45+
46+
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
47+
48+
[DebuggerNonUserCode]
49+
private sealed class DebugView
50+
{
51+
private readonly VariableNameCollection _collection;
52+
53+
public DebugView(VariableNameCollection collection)
54+
{
55+
_collection = collection;
56+
}
57+
58+
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
59+
public string[] Items => _collection.ToArray();
60+
}
61+
}

src/Acornima/Helpers/ArrayList.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ namespace Acornima.Helpers;
7474
[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}, {nameof(Capacity)} = {{{nameof(Capacity)}}}, Version = {{{nameof(_localVersion)}}}")]
7575
[DebuggerTypeProxy(typeof(ArrayList<>.DebugView))]
7676
#endif
77-
internal struct ArrayList<T> : IList<T>
77+
internal struct ArrayList<T> : IList<T>, IReadOnlyList<T>
7878
{
7979
private const int MinAllocatedCount = 4;
8080

0 commit comments

Comments
 (0)