|
1 | 1 | using System;
|
2 | 2 | using Acornima.Ast;
|
| 3 | +using Acornima.Helpers; |
3 | 4 |
|
4 | 5 | namespace Acornima;
|
5 | 6 |
|
6 | 7 | public static class ParserOptionsExtensions
|
7 | 8 | {
|
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 |
9 | 11 | {
|
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) |
11 | 17 | {
|
12 |
| - child.UserData = node; |
| 18 | + (helper ??= new OnNodeHelper()).EnableParentNodeRecoding(options); |
13 | 19 | }
|
14 |
| - }; |
| 20 | + else |
| 21 | + { |
| 22 | + helper?.DisableParentNodeRecoding(options); |
| 23 | + } |
| 24 | + |
| 25 | + return options; |
| 26 | + } |
15 | 27 |
|
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 |
17 | 30 | {
|
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) |
19 | 36 | {
|
20 |
| - node.UserData = new ScopeInfo(node, scope, scopeStack); |
| 37 | + (helper ??= new OnNodeHelper()).EnableScopeInfoRecoding(options); |
| 38 | + } |
| 39 | + else |
| 40 | + { |
| 41 | + helper?.DisableScopeInfoRecoding(options); |
21 | 42 | }
|
22 | 43 |
|
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() |
24 | 53 | {
|
25 |
| - if (child.UserData is ScopeInfo scopeInfo) |
| 54 | + _scopes.Clear(); |
| 55 | + if (_scopes.Capacity > 64) |
26 | 56 | {
|
27 |
| - scopeInfo.AssociatedNodeParent = node; |
| 57 | + _scopes.Capacity = 64; |
28 | 58 | }
|
29 |
| - else |
| 59 | + } |
| 60 | + |
| 61 | + public void EnableParentNodeRecoding(ParserOptions options) |
| 62 | + { |
| 63 | + if (_handler is null) |
30 | 64 | {
|
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; |
32 | 72 | }
|
33 | 73 | }
|
34 |
| - }; |
35 | 74 |
|
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 | + } |
43 | 89 |
|
44 |
| - if (enable) |
| 90 | + public void EnableScopeInfoRecoding(ParserOptions options) |
45 | 91 | {
|
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 | + } |
47 | 102 | }
|
48 | 103 |
|
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 | + } |
51 | 118 |
|
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 | + } |
59 | 126 |
|
60 |
| - if (enable) |
| 127 | + private void SetScopeInfo(Node node, in Scope scope, ReadOnlySpan<Scope> scopeStack) |
61 | 128 | {
|
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 | + } |
63 | 147 | }
|
64 | 148 |
|
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 | + } |
66 | 165 | }
|
67 | 166 | }
|
0 commit comments