Skip to content

Commit 7ccea95

Browse files
committed
Expose variable scope information tracked by the parser to consumers via the ParserOptions.OnNode callback
1 parent bb3683e commit 7ccea95

17 files changed

+542
-172
lines changed

Directory.Build.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<NoWarn>$(NoWarn);CA1716</NoWarn> <!-- Identifiers should not match keywords -->
1616
<NoWarn>$(NoWarn);CA1510</NoWarn> <!-- Use ArgumentNullException.ThrowIfNull -->
1717
<NoWarn>$(NoWarn);CA1711</NoWarn> <!-- Identifiers should not have incorrect suffix -->
18+
<NoWarn>$(NoWarn);CA1863</NoWarn> <!-- Cache a 'CompositeFormat' for repeated use in this formatting operation -->
1819
</PropertyGroup>
1920

2021
</Project>

src/Acornima.Extras/Acornima.Extras.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<RootNamespace>Acornima</RootNamespace>
5-
<TargetFrameworks>net6.0;net462;netstandard2.0;netstandard2.1</TargetFrameworks>
5+
<TargetFrameworks>net8.0;net6.0;net462;netstandard2.1;netstandard2.0</TargetFrameworks>
66
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
77

88
<AssemblyTitle>Acornima.Extras</AssemblyTitle>
Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,55 @@
1-
using System;
1+
using Acornima.Ast;
22

33
namespace Acornima;
44

55
public static class ParserOptionsExtensions
66
{
7-
private static readonly OnNodeHandler s_parentSetter = node =>
7+
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true)
8+
where TOptions : ParserOptions
89
{
9-
foreach (var child in node.ChildNodes)
10+
var helper = options._onNode?.Target as OnNodeHelper;
11+
if (enable)
12+
{
13+
(helper ?? new OnNodeHelper()).EnableParentNodeRecoding(options);
14+
}
15+
else
1016
{
11-
child.UserData = node;
17+
helper?.DisableParentNodeRecoding(options);
1218
}
13-
};
1419

15-
public static TOptions RecordParentNodeInUserData<TOptions>(this TOptions options, bool enable = true)
16-
where TOptions : ParserOptions
20+
return options;
21+
}
22+
23+
private sealed class OnNodeHelper : IOnNodeHandlerWrapper
1724
{
18-
options._onNode = (OnNodeHandler?)Delegate.RemoveAll(options._onNode, s_parentSetter);
25+
private OnNodeHandler? _onNode;
26+
public OnNodeHandler? OnNode { get => _onNode; set => _onNode = value; }
1927

20-
if (enable)
28+
public void EnableParentNodeRecoding(ParserOptions options)
2129
{
22-
options._onNode += s_parentSetter;
30+
if (!ReferenceEquals(options._onNode?.Target, this))
31+
{
32+
_onNode = options._onNode;
33+
options._onNode = SetParentNode;
34+
}
2335
}
2436

25-
return options;
37+
public void DisableParentNodeRecoding(ParserOptions options)
38+
{
39+
if (options._onNode == SetParentNode)
40+
{
41+
options._onNode = _onNode;
42+
}
43+
}
44+
45+
private void SetParentNode(Node node, OnNodeContext context)
46+
{
47+
foreach (var child in node.ChildNodes)
48+
{
49+
child.UserData = node;
50+
}
51+
52+
_onNode?.Invoke(node, context);
53+
}
2654
}
2755
}

src/Acornima/Acornima.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net6.0;net462;netstandard2.0;netstandard2.1</TargetFrameworks>
4+
<TargetFrameworks>net8.0;net6.0;net462;netstandard2.1;netstandard2.0</TargetFrameworks>
55
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
66

77
<AssemblyTitle>Acornima</AssemblyTitle>

src/Acornima/Helpers/ArrayList.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,9 @@ 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 partial struct ArrayList<T> : IList<T>
77+
internal partial struct ArrayList<T> : IList<T>, IReadOnlyList<T>
7878
{
79-
private const int MinAllocatedCount = 4;
79+
internal const int MinAllocatedCount = 4;
8080

8181
private T[]? _items;
8282
private int _count;
@@ -239,7 +239,7 @@ internal readonly ref T GetItemRef(int index)
239239
internal readonly ref T LastItemRef() => ref GetItemRef(_count - 1);
240240

241241
[MethodImpl(MethodImplOptions.AggressiveInlining)]
242-
private static long GrowCapacity(int capacity)
242+
internal static long GrowCapacity(int capacity)
243243
{
244244
// NOTE: Using a growth factor of 3/2 yields better benchmark results than 2.
245245
// It also results in less excess when the underlying array is returned directly wrapped in a NodeList, Span, etc.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
namespace Acornima.Helpers;
6+
7+
/// <summary>
8+
/// A struct that can store a read-only managed reference.
9+
/// </summary>
10+
internal readonly ref struct ReadOnlyRef<T>
11+
{
12+
#if NET7_0_OR_GREATER
13+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14+
public ReadOnlyRef(ref readonly T value)
15+
{
16+
Value = ref value;
17+
}
18+
19+
public readonly ref readonly T Value;
20+
#else
21+
private readonly ReadOnlySpan<T> _value;
22+
23+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
24+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
25+
public ReadOnlyRef(ref readonly T value)
26+
{
27+
_value = MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in value), 1);
28+
}
29+
#else
30+
public ReadOnlyRef(ReadOnlySpan<T> span, int index)
31+
{
32+
_value = span.Slice(index, 1);
33+
}
34+
#endif
35+
36+
public ref readonly T Value { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref MemoryMarshal.GetReference(_value); }
37+
#endif
38+
}

src/Acornima/OnNodeContext.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
using System.Runtime.CompilerServices;
3+
using Acornima.Helpers;
4+
5+
namespace Acornima;
6+
7+
using static ExceptionHelper;
8+
9+
public readonly ref struct OnNodeContext
10+
{
11+
internal readonly ReadOnlyRef<Scope> _scope;
12+
13+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
14+
internal OnNodeContext(ReadOnlyRef<Scope> scope, ArrayList<Scope> scopeStack)
15+
{
16+
_scope = scope;
17+
ScopeStack = scopeStack.AsReadOnlySpan();
18+
}
19+
20+
public bool HasScope { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => !Unsafe.IsNullRef(ref Unsafe.AsRef(in _scope.Value)); }
21+
22+
public ref readonly Scope Scope
23+
{
24+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25+
get
26+
{
27+
ref readonly var scope = ref _scope.Value;
28+
if (Unsafe.IsNullRef(ref Unsafe.AsRef(in scope)))
29+
{
30+
ThrowInvalidOperationException<object>();
31+
}
32+
return ref scope;
33+
}
34+
}
35+
36+
public ReadOnlySpan<Scope> ScopeStack { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }
37+
}

src/Acornima/Parser.Expression.cs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1649,13 +1649,13 @@ private FunctionExpression ParseMethod(bool isGenerator, bool isAsync = false, b
16491649

16501650
NodeList<Node> parameters = ParseBindingList(close: TokenType.ParenRight, allowEmptyElement: false, allowTrailingComma: _tokenizerOptions._ecmaVersion >= EcmaVersion.ES8)!;
16511651
CheckYieldAwaitInDefaultParams();
1652-
var body = ParseFunctionBody(id: null, parameters, isArrowFunction: false, isMethod: true, ExpressionContext.Default, out _);
1652+
var scope = ParseFunctionBody(id: null, parameters, isArrowFunction: false, isMethod: true, ExpressionContext.Default, out _, out var body);
16531653

16541654
_yieldPosition = oldYieldPos;
16551655
_awaitPosition = oldAwaitPos;
16561656
_awaitIdentifierPosition = oldAwaitIdentPos;
16571657

1658-
return FinishNode(startMarker, new FunctionExpression(id: null, parameters, (FunctionBody)body, isGenerator, isAsync));
1658+
return FinishNode(startMarker, new FunctionExpression(id: null, parameters, (FunctionBody)body, isGenerator, isAsync), scope);
16591659
}
16601660

16611661
// Parse arrow function expression with given parameters.
@@ -1674,24 +1674,22 @@ private ArrowFunctionExpression ParseArrowExpression(in Marker startMarker, in N
16741674
EnterScope(FunctionFlags(isAsync, generator: false) | ScopeFlags.Arrow);
16751675

16761676
NodeList<Node> paramList = ToAssignableList(parameters!, isBinding: true, isParams: true)!;
1677-
var body = ParseFunctionBody(id: null, paramList, isArrowFunction: true, isMethod: false, context, out var expression);
1677+
var scope = ParseFunctionBody(id: null, paramList, isArrowFunction: true, isMethod: false, context, out var expression, out var body);
16781678

16791679
_yieldPosition = oldYieldPos;
16801680
_awaitPosition = oldAwaitPos;
16811681
_awaitIdentifierPosition = oldAwaitIdentPos;
16821682

1683-
return FinishNode(startMarker, new ArrowFunctionExpression(paramList, body, expression, isAsync));
1683+
return FinishNode(startMarker, new ArrowFunctionExpression(paramList, body, expression, isAsync), scope);
16841684
}
16851685

16861686
// Parse function body and check parameters.
1687-
private StatementOrExpression ParseFunctionBody(Identifier? id, in NodeList<Node> parameters,
1688-
bool isArrowFunction, bool isMethod, ExpressionContext context, out bool expression)
1687+
private ReadOnlyRef<Scope> ParseFunctionBody(Identifier? id, in NodeList<Node> parameters, bool isArrowFunction, bool isMethod, ExpressionContext context,
1688+
out bool expression, out StatementOrExpression body)
16891689
{
16901690
// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/expression.js > `pp.parseFunctionBody = function`
16911691

16921692
expression = isArrowFunction && _tokenizer._type != TokenType.BraceLeft;
1693-
1694-
StatementOrExpression body;
16951693
if (expression)
16961694
{
16971695
CheckParams(parameters, allowDuplicates: false);
@@ -1722,15 +1720,13 @@ private StatementOrExpression ParseFunctionBody(Identifier? id, in NodeList<Node
17221720
// flag (restore them to their old value afterwards).
17231721
var oldLabels = _labels;
17241722
_labels = new ArrayList<Label>();
1725-
ParseBlock(ref statements, createNewLexicalScope: false, exitStrict: strict && !oldStrict);
1723+
var scope = ParseBlock(ref statements, createNewLexicalScope: false, exitStrict: strict && !oldStrict);
17261724
_labels = oldLabels;
17271725

1728-
body = FinishNode(startMarker, new FunctionBody(NodeList.From(ref statements), strict));
1726+
body = FinishNode(startMarker, new FunctionBody(NodeList.From(ref statements), strict), scope);
17291727
}
17301728

1731-
ExitScope();
1732-
1733-
return body;
1729+
return ExitScope();
17341730
}
17351731

17361732
private static bool IsSimpleParamList(in NodeList<Node> parameters)
@@ -1761,6 +1757,9 @@ private void CheckParams(in NodeList<Node> parameters, bool allowDuplicates)
17611757
Debug.Assert(param is not null);
17621758
CheckLValInnerPattern(param!, BindingType.Var, checkClashes: nameHash);
17631759
}
1760+
1761+
ref var varList = ref CurrentScope._var;
1762+
varList.ParamCount = varList.Count;
17641763
}
17651764

17661765
// Parses a comma-separated list of expressions, and returns them as

src/Acornima/Parser.Helpers.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,29 +25,33 @@ internal Marker StartNode()
2525
return new Marker(_tokenizer._start, _tokenizer._startLocation);
2626
}
2727

28-
internal T FinishNodeAt<T>(in Marker startMarker, in Marker endMarker, T node) where T : Node
28+
[MethodImpl(MethodImplOptions.NoInlining)]
29+
internal T FinishNodeAt<T>(in Marker startMarker, in Marker endMarker, T node, ReadOnlyRef<Scope> scope = default) where T : Node
2930
{
3031
// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/node.js > `function finishNodeAt`, `pp.finishNodeAt = function`
3132

3233
node._range = new Range(startMarker.Index, endMarker.Index);
3334
node._location = new SourceLocation(startMarker.Position, endMarker.Position, _tokenizer._sourceFile);
34-
_options._onNode?.Invoke(node);
35+
_options._onNode?.Invoke(node, new OnNodeContext(scope, _scopeStack));
3536
return node;
3637
}
3738

38-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39-
internal T FinishNode<T>(in Marker startMarker, T node) where T : Node
39+
[MethodImpl(MethodImplOptions.NoInlining)]
40+
internal T FinishNode<T>(in Marker startMarker, T node, ReadOnlyRef<Scope> scope = default) where T : Node
4041
{
4142
// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/node.js > `pp.finishNode = function`
4243

43-
return FinishNodeAt(startMarker, new Marker(_tokenizer._lastTokenEnd, _tokenizer._lastTokenEndLocation), node);
44+
node._range = new Range(startMarker.Index, _tokenizer._lastTokenEnd);
45+
node._location = new SourceLocation(startMarker.Position, _tokenizer._lastTokenEndLocation, _tokenizer._sourceFile);
46+
_options._onNode?.Invoke(node, new OnNodeContext(scope, _scopeStack));
47+
return node;
4448
}
4549

4650
private T ReinterpretNode<T>(Node originalNode, T node) where T : Node
4751
{
4852
node._range = originalNode._range;
4953
node._location = originalNode._location;
50-
_options._onNode?.Invoke(node);
54+
_options._onNode?.Invoke(node, new OnNodeContext(default, _scopeStack));
5155
return node;
5256
}
5357

0 commit comments

Comments
 (0)