-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Precompute scope references (Parent, VarScope, ThisScope) during pars…
…ing + make parent node and scope info tracking composable
- Loading branch information
Showing
11 changed files
with
330 additions
and
133 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,166 @@ | ||
using System; | ||
using Acornima.Ast; | ||
using Acornima.Helpers; | ||
|
||
namespace Acornima; | ||
|
||
public static class ParserOptionsExtensions | ||
{ | ||
private static readonly OnNodeHandler s_parentSetter = (Node node, in Scope _, ReadOnlySpan<Scope> _) => | ||
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true) | ||
where TOptions : ParserOptions | ||
{ | ||
foreach (var child in node.ChildNodes) | ||
var helper = options._onNode?.GetInvocationList() is { } invocationList | ||
? (OnNodeHelper?)Array.Find(invocationList, invocation => invocation.Target is OnNodeHelper)?.Target | ||
: null; | ||
|
||
if (enable) | ||
{ | ||
child.UserData = node; | ||
(helper ??= new OnNodeHelper()).EnableParentNodeRecoding(options); | ||
} | ||
}; | ||
else | ||
{ | ||
helper?.DisableParentNodeRecoding(options); | ||
} | ||
|
||
return options; | ||
} | ||
|
||
private static readonly OnNodeHandler s_scopeInfoSetter = (Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) => | ||
public static TOptions RecordScopeInfoInUserData<TOptions>(this TOptions options, bool enable = true) | ||
where TOptions : ParserOptions | ||
{ | ||
if (!Scope.IsNullRef(scope)) | ||
var helper = options._onNode?.GetInvocationList() is { } invocationList | ||
? (OnNodeHelper?)Array.Find(invocationList, invocation => invocation.Target is OnNodeHelper)?.Target | ||
: null; | ||
|
||
if (enable) | ||
{ | ||
node.UserData = new ScopeInfo(node, scope, scopeStack); | ||
(helper ??= new OnNodeHelper()).EnableScopeInfoRecoding(options); | ||
} | ||
else | ||
{ | ||
helper?.DisableScopeInfoRecoding(options); | ||
} | ||
|
||
foreach (var child in node.ChildNodes) | ||
return options; | ||
} | ||
|
||
private sealed class OnNodeHelper | ||
{ | ||
private OnNodeHandler? _handler; | ||
private ArrayList<ScopeInfo> _scopes; | ||
|
||
private void ReleaseLargeBuffers() | ||
{ | ||
if (child.UserData is ScopeInfo scopeInfo) | ||
_scopes.Clear(); | ||
if (_scopes.Capacity > 64) | ||
{ | ||
scopeInfo.AssociatedNodeParent = node; | ||
_scopes.Capacity = 64; | ||
} | ||
else | ||
} | ||
|
||
public void EnableParentNodeRecoding(ParserOptions options) | ||
{ | ||
if (_handler is null) | ||
{ | ||
child.UserData = node; | ||
options._onNode += _handler = SetParentNode; | ||
options._onReleaseLargeBuffers += ReleaseLargeBuffers; | ||
} | ||
else if (_handler == SetScopeInfo) | ||
{ | ||
options._onNode -= _handler; | ||
options._onNode += _handler = SetParentNodeAndScopeInfo; | ||
} | ||
} | ||
}; | ||
|
||
/// <remarks> | ||
/// WARNING: Enabling this together with <see cref="RecordScopeInfoInUserData"/> is an undefined behavior. | ||
/// </remarks> | ||
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true) | ||
where TOptions : ParserOptions | ||
{ | ||
options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options._onNode, s_parentSetter); | ||
public void DisableParentNodeRecoding(ParserOptions options) | ||
{ | ||
if (_handler == SetParentNodeAndScopeInfo) | ||
{ | ||
options._onNode -= _handler; | ||
options._onNode += _handler = SetScopeInfo; | ||
} | ||
else if (_handler == SetParentNode) | ||
{ | ||
ReleaseLargeBuffers(); | ||
options._onReleaseLargeBuffers -= ReleaseLargeBuffers; | ||
options._onNode -= _handler; | ||
} | ||
} | ||
|
||
if (enable) | ||
public void EnableScopeInfoRecoding(ParserOptions options) | ||
{ | ||
options._onNode += s_parentSetter; | ||
if (_handler is null) | ||
{ | ||
options._onNode += _handler = SetScopeInfo; | ||
options._onReleaseLargeBuffers += ReleaseLargeBuffers; | ||
} | ||
else if (_handler == SetParentNode) | ||
{ | ||
options._onNode -= _handler; | ||
options._onNode += _handler = SetParentNodeAndScopeInfo; | ||
} | ||
} | ||
|
||
return options; | ||
} | ||
public void DisableScopeInfoRecoding(ParserOptions options) | ||
{ | ||
if (_handler == SetParentNodeAndScopeInfo) | ||
{ | ||
options._onNode -= _handler; | ||
options._onNode += _handler = SetParentNode; | ||
} | ||
else if (_handler == SetScopeInfo) | ||
{ | ||
ReleaseLargeBuffers(); | ||
options._onReleaseLargeBuffers -= ReleaseLargeBuffers; | ||
options._onNode -= _handler; | ||
} | ||
} | ||
|
||
/// <remarks> | ||
/// WARNING: Enabling this together with <see cref="RecordParentNodeInUserData"/> is an undefined behavior. | ||
/// </remarks> | ||
public static TOptions RecordScopeInfoInUserData<TOptions>(this TOptions options, bool enable = true) | ||
where TOptions : ParserOptions | ||
{ | ||
options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options.OnNode, s_scopeInfoSetter); | ||
private void SetParentNode(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) | ||
{ | ||
foreach (var child in node.ChildNodes) | ||
{ | ||
child.UserData = node; | ||
} | ||
} | ||
|
||
if (enable) | ||
private void SetScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) | ||
{ | ||
options._onNode += s_scopeInfoSetter; | ||
if (!Scope.IsNullRef(scope)) | ||
{ | ||
for (var n = scope.Id - _scopes.Count; n >= 0; n--) | ||
{ | ||
ref var scopeInfoRef = ref _scopes.PushRef(); | ||
scopeInfoRef ??= new ScopeInfo(); | ||
} | ||
|
||
var scopeInfo = _scopes[scope.Id]; | ||
scopeInfo.Initialize(node, | ||
parent: scope.Id != scopeStack[0].Id ? _scopes[scopeStack.Last().Id] : null, | ||
varScope: scope.CurrentVarScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentVarScopeIndex].Id], | ||
thisScope: scope.CurrentThisScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentThisScopeIndex].Id], | ||
varNames: scope.VarNames, | ||
lexicalNames: scope.LexicalNames, | ||
functionNames: scope.FunctionNames); | ||
node.UserData = scopeInfo; | ||
} | ||
} | ||
|
||
return options; | ||
private void SetParentNodeAndScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) | ||
{ | ||
SetScopeInfo(node, scope, scopeStack); | ||
|
||
foreach (var child in node.ChildNodes) | ||
{ | ||
if (child.UserData is ScopeInfo scopeInfo) | ||
{ | ||
scopeInfo.UserData = node; | ||
} | ||
else | ||
{ | ||
child.UserData = node; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,53 +1,76 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using Acornima.Ast; | ||
|
||
namespace Acornima; | ||
|
||
public sealed class ScopeInfo | ||
{ | ||
private readonly int _id; | ||
private readonly int _currentVarScopeId; | ||
private readonly int _currentThisScopeId; | ||
private VariableNameCollection _varNames; | ||
private VariableNameCollection _lexicalNames; | ||
private VariableNameCollection _functionNames; | ||
|
||
public ScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) | ||
public static ScopeInfo From(Node associatedNode, | ||
ScopeInfo? parent, ScopeInfo? varScope, ScopeInfo? thisScope, | ||
ReadOnlySpan<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames) | ||
{ | ||
AssociatedNode = node; | ||
_id = scope.Id; | ||
_currentVarScopeId = scope.CurrentVarScopeIndex == scopeStack.Length ? scope.Id : scopeStack[scope.CurrentVarScopeIndex].Id; | ||
_currentThisScopeId = scope.CurrentThisScopeIndex == scopeStack.Length ? scope.Id : scopeStack[scope.CurrentThisScopeIndex].Id; | ||
VarNames = scope.VarNames.ToArray(); | ||
LexicalNames = scope.LexicalNames.ToArray(); | ||
FunctionNames = scope.FunctionNames.ToArray(); | ||
var scope = new ScopeInfo(); | ||
scope.Initialize(associatedNode ?? throw new ArgumentNullException(nameof(associatedNode)), | ||
parent, varScope ?? scope, thisScope ?? scope, | ||
varNames, lexicalNames, functionNames); | ||
return scope; | ||
} | ||
|
||
public Node AssociatedNode { get; } | ||
public Node? AssociatedNodeParent { get; internal set; } | ||
internal ScopeInfo() | ||
{ | ||
AssociatedNode = null!; | ||
VarScope = ThisScope = this; | ||
} | ||
|
||
internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope, | ||
ReadOnlySpan<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames) | ||
{ | ||
AssociatedNode = associatedNode; | ||
Parent = parent; | ||
VarScope = varScope; | ||
ThisScope = thisScope; | ||
_varNames = new VariableNameCollection(varNames); | ||
_lexicalNames = new VariableNameCollection(lexicalNames); | ||
_functionNames = new VariableNameCollection(functionNames); | ||
} | ||
|
||
public string[] VarNames { get; } | ||
public string[] LexicalNames { get; } | ||
public string[] FunctionNames { get; } | ||
public Node AssociatedNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } | ||
|
||
// These lookups could as well be cached. | ||
public ScopeInfo? Parent => FindAncestor(_ => true); | ||
public ScopeInfo? VarScope => FindAncestor(scope => scope._id == _currentVarScopeId); | ||
public ScopeInfo? ThisScope => FindAncestor(scope => scope._id == _currentThisScopeId); | ||
public ScopeInfo? Parent { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } | ||
public ScopeInfo VarScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } | ||
public ScopeInfo ThisScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } | ||
|
||
private ScopeInfo? FindAncestor(Func<ScopeInfo, bool> predicate) | ||
/// <summary> | ||
/// A list of distinct var-declared names sorted in ascending order in the current lexical scope. | ||
/// </summary> | ||
public VariableNameCollection VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _varNames; } | ||
|
||
/// <summary> | ||
/// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope. | ||
/// </summary> | ||
public VariableNameCollection LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexicalNames; } | ||
|
||
/// <summary> | ||
/// A list of distinct lexically-declared <see cref="FunctionDeclaration"/> names sorted in ascending order in the current lexical scope. | ||
/// </summary> | ||
public VariableNameCollection FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functionNames; } | ||
|
||
/// <summary> | ||
/// Gets or sets the arbitrary, user-defined data object associated with the current <see cref="ScopeInfo"/>. | ||
/// </summary> | ||
/// <remarks> | ||
/// The operation is not guaranteed to be thread-safe. In case concurrent access or update is possible, the necessary synchronization is caller's responsibility. | ||
/// </remarks> | ||
public object? UserData | ||
{ | ||
var node = AssociatedNode; | ||
while ((node = GetParentNode(node!)!) is not null) | ||
{ | ||
if (node.UserData is ScopeInfo scopeInfo && predicate(scopeInfo)) | ||
{ | ||
return scopeInfo; | ||
} | ||
} | ||
|
||
return null; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
get; | ||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
set; | ||
} | ||
|
||
private static Node? GetParentNode(Node node) | ||
=> node.UserData is ScopeInfo scopeInfo ? scopeInfo.AssociatedNodeParent : (Node?)node.UserData; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using Acornima.Helpers; | ||
|
||
namespace Acornima; | ||
|
||
[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}")] | ||
[DebuggerTypeProxy(typeof(DebugView))] | ||
public readonly struct VariableNameCollection : IReadOnlyCollection<string> | ||
{ | ||
private readonly ArrayList<string> _names; | ||
|
||
public VariableNameCollection(ReadOnlySpan<string> names) | ||
{ | ||
if (names.Length > 0) | ||
{ | ||
_names = new ArrayList<string>(initialCapacity: names.Length); | ||
for (var i = 0; i < names.Length; i++) | ||
{ | ||
var name = names[i]; | ||
var index = _names.AsReadOnlySpan().BinarySearch(name); | ||
if (index < 0) | ||
{ | ||
_names.Insert(~index, name); | ||
} | ||
} | ||
_names.TrimExcess(); | ||
} | ||
} | ||
|
||
public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _names.Count; } | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public bool Contains(string name) | ||
{ | ||
return _names.AsReadOnlySpan().BinarySearch(name) >= 0; | ||
} | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public IEnumerator<string> GetEnumerator() => _names.GetEnumerator(); | ||
|
||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | ||
|
||
[DebuggerNonUserCode] | ||
private sealed class DebugView | ||
{ | ||
private readonly VariableNameCollection _collection; | ||
|
||
public DebugView(VariableNameCollection collection) | ||
{ | ||
_collection = collection; | ||
} | ||
|
||
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] | ||
public string[] Items => _collection.ToArray(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.