From 4e7921ca0a3372f32029d88868da6761d040ea39 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Fri, 31 May 2024 23:08:29 +0200 Subject: [PATCH] Precompute scope references (Parent, VarScope, ThisScope) during parsing + make parent node and scope info tracking composable --- .../Commands/PrintScopesCommand.cs | 1 - .../ParserOptionsExtensions.cs | 167 ++++++++++++++---- src/Acornima.Extras/ScopeInfo.cs | 95 ++++++---- src/Acornima.Extras/VariableNameCollection.cs | 61 +++++++ src/Acornima/Helpers/ArrayList.cs | 2 +- src/Acornima/Parser.LVal.cs | 30 ++-- src/Acornima/Parser.State.cs | 36 ++-- src/Acornima/Parser.Statement.cs | 6 +- src/Acornima/ParserOptions.cs | 2 + src/Acornima/Scope.cs | 57 +++--- src/Acornima/ScopeFlags.cs | 6 +- 11 files changed, 330 insertions(+), 133 deletions(-) create mode 100644 src/Acornima.Extras/VariableNameCollection.cs diff --git a/samples/Acornima.Cli/Commands/PrintScopesCommand.cs b/samples/Acornima.Cli/Commands/PrintScopesCommand.cs index f3198d2..f972625 100644 --- a/samples/Acornima.Cli/Commands/PrintScopesCommand.cs +++ b/samples/Acornima.Cli/Commands/PrintScopesCommand.cs @@ -50,7 +50,6 @@ public int OnExecute() _ => throw new InvalidOperationException() }; - var treePrinter = new TreePrinter(_console); treePrinter.Print(new[] { rootNode }, node => node diff --git a/src/Acornima.Extras/ParserOptionsExtensions.cs b/src/Acornima.Extras/ParserOptionsExtensions.cs index f98ab11..9145422 100644 --- a/src/Acornima.Extras/ParserOptionsExtensions.cs +++ b/src/Acornima.Extras/ParserOptionsExtensions.cs @@ -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 _) => + public static TOptions RecordParentNodeInUserData(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 scopeStack) => + public static TOptions RecordScopeInfoInUserData(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 _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; } } - }; - /// - /// WARNING: Enabling this together with is an undefined behavior. - /// - public static TOptions RecordParentNodeInUserData(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; + } + } - /// - /// WARNING: Enabling this together with is an undefined behavior. - /// - public static TOptions RecordScopeInfoInUserData(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 scopeStack) + { + foreach (var child in node.ChildNodes) + { + child.UserData = node; + } + } - if (enable) + private void SetScopeInfo(Node node, in Scope scope, ReadOnlySpan 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 scopeStack) + { + SetScopeInfo(node, scope, scopeStack); + + foreach (var child in node.ChildNodes) + { + if (child.UserData is ScopeInfo scopeInfo) + { + scopeInfo.UserData = node; + } + else + { + child.UserData = node; + } + } + } } } diff --git a/src/Acornima.Extras/ScopeInfo.cs b/src/Acornima.Extras/ScopeInfo.cs index 51ea049..d7b532a 100644 --- a/src/Acornima.Extras/ScopeInfo.cs +++ b/src/Acornima.Extras/ScopeInfo.cs @@ -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 scopeStack) + public static ScopeInfo From(Node associatedNode, + ScopeInfo? parent, ScopeInfo? varScope, ScopeInfo? thisScope, + ReadOnlySpan varNames, ReadOnlySpan lexicalNames, ReadOnlySpan 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 varNames, ReadOnlySpan lexicalNames, ReadOnlySpan 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 predicate) + /// + /// A list of distinct var-declared names sorted in ascending order in the current lexical scope. + /// + public VariableNameCollection VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _varNames; } + + /// + /// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope. + /// + public VariableNameCollection LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexicalNames; } + + /// + /// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope. + /// + public VariableNameCollection FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functionNames; } + + /// + /// Gets or sets the arbitrary, user-defined data object associated with the current . + /// + /// + /// The operation is not guaranteed to be thread-safe. In case concurrent access or update is possible, the necessary synchronization is caller's responsibility. + /// + 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; } diff --git a/src/Acornima.Extras/VariableNameCollection.cs b/src/Acornima.Extras/VariableNameCollection.cs new file mode 100644 index 0000000..b56e52a --- /dev/null +++ b/src/Acornima.Extras/VariableNameCollection.cs @@ -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 +{ + private readonly ArrayList _names; + + public VariableNameCollection(ReadOnlySpan names) + { + if (names.Length > 0) + { + _names = new ArrayList(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 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(); + } +} diff --git a/src/Acornima/Helpers/ArrayList.cs b/src/Acornima/Helpers/ArrayList.cs index 31f39fb..41c4b42 100644 --- a/src/Acornima/Helpers/ArrayList.cs +++ b/src/Acornima/Helpers/ArrayList.cs @@ -74,7 +74,7 @@ namespace Acornima.Helpers; [DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}, {nameof(Capacity)} = {{{nameof(Capacity)}}}, Version = {{{nameof(_localVersion)}}}")] [DebuggerTypeProxy(typeof(ArrayList<>.DebugView))] #endif -internal struct ArrayList : IList +internal struct ArrayList : IList, IReadOnlyList { private const int MinAllocatedCount = 4; diff --git a/src/Acornima/Parser.LVal.cs b/src/Acornima/Parser.LVal.cs index fbd5852..ee47352 100644 --- a/src/Acornima/Parser.LVal.cs +++ b/src/Acornima/Parser.LVal.cs @@ -576,9 +576,9 @@ private void DeclareName(string name, BindingType bindingType, int pos) { case BindingType.Lexical: scope = ref CurrentScope; - redeclared = scope.Lexical.IndexOf(name) >= 0 || scope.Functions.IndexOf(name) >= 0 || scope.Var.IndexOf(name) >= 0; - scope.Lexical.Add(name); - if (_inModule && (scope.Flags & ScopeFlags.Top) != 0) + redeclared = scope._lexical.IndexOf(name) >= 0 || scope._functions.IndexOf(name) >= 0 || scope._var.IndexOf(name) >= 0; + scope._lexical.Add(name); + if (_inModule && (scope._flags & ScopeFlags.Top) != 0) { _undefinedExports!.Remove(name); } @@ -586,34 +586,34 @@ private void DeclareName(string name, BindingType bindingType, int pos) case BindingType.SimpleCatch: scope = ref CurrentScope; - scope.Lexical.Add(name); + scope._lexical.Add(name); break; case BindingType.Function: scope = ref CurrentScope; - redeclared = (scope.Flags & _functionsAsVarInScopeFlags) != 0 - ? scope.Lexical.IndexOf(name) >= 0 - : scope.Lexical.IndexOf(name) >= 0 || scope.Var.IndexOf(name) >= 0; - scope.Functions.Add(name); + redeclared = (scope._flags & _functionsAsVarInScopeFlags) != 0 + ? scope._lexical.IndexOf(name) >= 0 + : scope._lexical.IndexOf(name) >= 0 || scope._var.IndexOf(name) >= 0; + scope._functions.Add(name); break; default: for (var i = _scopeStack.Count - 1; i >= 0; --i) { scope = ref _scopeStack.GetItemRef(i); - if (scope.Lexical.IndexOf(name) >= 0 && !((scope.Flags & ScopeFlags.SimpleCatch) != 0 && scope.Lexical[0] == name) - || (scope.Flags & _functionsAsVarInScopeFlags) == 0 && scope.Functions.IndexOf(name) >= 0) + if (scope._lexical.IndexOf(name) >= 0 && !((scope._flags & ScopeFlags.SimpleCatch) != 0 && scope._lexical[0] == name) + || (scope._flags & _functionsAsVarInScopeFlags) == 0 && scope._functions.IndexOf(name) >= 0) { redeclared = true; break; } - scope.Var.Add(name); - if (_inModule && (scope.Flags & ScopeFlags.Top) != 0) + scope._var.Add(name); + if (_inModule && (scope._flags & ScopeFlags.Top) != 0) { _undefinedExports!.Remove(name); } - if ((scope.Flags & ScopeFlags.Var) != 0) + if ((scope._flags & ScopeFlags.Var) != 0) { break; } @@ -634,8 +634,8 @@ private void CheckLocalExport(Identifier id) ref readonly var rootScope = ref _scopeStack.GetItemRef(0); // scope.functions must be empty as Module code is always strict. - if (rootScope.Lexical.IndexOf(id.Name) < 0 - && rootScope.Var.IndexOf(id.Name) < 0) + if (rootScope._lexical.IndexOf(id.Name) < 0 + && rootScope._var.IndexOf(id.Name) < 0) { _undefinedExports![id.Name] = id.Start; } diff --git a/src/Acornima/Parser.State.cs b/src/Acornima/Parser.State.cs index e5b28e0..b1007fd 100644 --- a/src/Acornima/Parser.State.cs +++ b/src/Acornima/Parser.State.cs @@ -146,6 +146,8 @@ private void ReleaseLargeBuffers() } _tokenizer.ReleaseLargeBuffers(); + + _options._onReleaseLargeBuffers?.Invoke(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -174,13 +176,13 @@ private void EnterScope(ScopeFlags flags) if ((flags & ScopeFlags.Var) != 0) { currentVarScopeIndex = _scopeStack.Count; - currentThisScopeIndex = (flags & ScopeFlags.Arrow) == 0 ? _scopeStack.Count : _scopeStack.PeekRef().CurrentThisScopeIndex; + currentThisScopeIndex = (flags & ScopeFlags.Arrow) == 0 ? _scopeStack.Count : _scopeStack.PeekRef()._currentThisScopeIndex; } else { ref readonly var currentScope = ref _scopeStack.PeekRef(); - currentVarScopeIndex = currentScope.CurrentVarScopeIndex; - currentThisScopeIndex = currentScope.CurrentThisScopeIndex; + currentVarScopeIndex = currentScope._currentVarScopeIndex; + currentThisScopeIndex = currentScope._currentThisScopeIndex; } _scopeStack.PushRef().Reset(_scopeId++, flags, currentVarScopeIndex, currentThisScopeIndex); @@ -204,7 +206,7 @@ private ref Scope CurrentVarScope(out int index) // NOTE: to improve performance, we calculate and store the index of the current var scope on the fly // instead of looking it up at every call as it's done in acornjs (see also `EnterScope`). - index = _scopeStack.PeekRef().CurrentVarScopeIndex; + index = _scopeStack.PeekRef()._currentVarScopeIndex; return ref _scopeStack.GetItemRef(index); } @@ -216,7 +218,7 @@ private ref Scope CurrentThisScope(out int index) // NOTE: to improve performance, we calculate and store the index of the current this scope on the fly // instead of looking it up at every call as it's done in acornjs (see also `EnterScope`). - index = _scopeStack.PeekRef().CurrentThisScopeIndex; + index = _scopeStack.PeekRef()._currentThisScopeIndex; return ref _scopeStack.GetItemRef(index); } @@ -225,7 +227,7 @@ private bool InFunction() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get inFunction` - return (CurrentVarScope(out _).Flags & ScopeFlags.Function) != 0; + return (CurrentVarScope(out _)._flags & ScopeFlags.Function) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -233,7 +235,7 @@ private bool InGenerator() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get inGenerator` - return (CurrentVarScope(out _).Flags & (ScopeFlags.Generator | ScopeFlags.InClassFieldInit)) == ScopeFlags.Generator; + return (CurrentVarScope(out _)._flags & (ScopeFlags.Generator | ScopeFlags.InClassFieldInit)) == ScopeFlags.Generator; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -241,7 +243,7 @@ private bool InAsync() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get inAsync` - return (CurrentVarScope(out _).Flags & (ScopeFlags.Async | ScopeFlags.InClassFieldInit)) == ScopeFlags.Async; + return (CurrentVarScope(out _)._flags & (ScopeFlags.Async | ScopeFlags.InClassFieldInit)) == ScopeFlags.Async; } private bool CanAwait() @@ -252,14 +254,14 @@ private bool CanAwait() { ref readonly var scope = ref _scopeStack.GetItemRef(i); - if ((scope.Flags & (ScopeFlags.InClassFieldInit | ScopeFlags.ClassStaticBlock)) != 0) + if ((scope._flags & (ScopeFlags.InClassFieldInit | ScopeFlags.ClassStaticBlock)) != 0) { return false; } - if ((scope.Flags & ScopeFlags.Function) != 0) + if ((scope._flags & ScopeFlags.Function) != 0) { - return (scope.Flags & ScopeFlags.Async) != 0; + return (scope._flags & ScopeFlags.Async) != 0; } } @@ -271,7 +273,7 @@ private bool AllowSuper() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get allowSuper` - return _options._allowSuperOutsideMethod || (CurrentThisScope(out _).Flags & (ScopeFlags.Super | ScopeFlags.InClassFieldInit)) != 0; + return _options._allowSuperOutsideMethod || (CurrentThisScope(out _)._flags & (ScopeFlags.Super | ScopeFlags.InClassFieldInit)) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -279,7 +281,7 @@ private bool AllowDirectSuper() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get allowDirectSuper` - return (CurrentThisScope(out _).Flags & ScopeFlags.DirectSuper) != 0; + return (CurrentThisScope(out _)._flags & ScopeFlags.DirectSuper) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -291,7 +293,7 @@ private bool TreatFunctionsAsVar() // NOTE: to improve performance, we calculate and store this flag on the fly // instead of recalculating it at every call as it's done in acornjs. - return (CurrentScope.Flags & _functionsAsVarInScopeFlags) != 0; + return (CurrentScope._flags & _functionsAsVarInScopeFlags) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -300,7 +302,7 @@ private bool AllowNewDotTarget() // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get allowNewDotTarget` return _options.AllowNewTargetOutsideFunction - || (CurrentThisScope(out _).Flags & (ScopeFlags.Function | ScopeFlags.ClassStaticBlock | ScopeFlags.InClassFieldInit)) != 0; + || (CurrentThisScope(out _)._flags & (ScopeFlags.Function | ScopeFlags.ClassStaticBlock | ScopeFlags.InClassFieldInit)) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -308,13 +310,13 @@ private bool InClassStaticBlock() { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/state.js > `get inClassStaticBlock` - return (CurrentVarScope(out _).Flags & ScopeFlags.ClassStaticBlock) != 0; + return (CurrentVarScope(out _)._flags & ScopeFlags.ClassStaticBlock) != 0; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool InClassFieldInit() { - return (CurrentThisScope(out _).Flags & ScopeFlags.InClassFieldInit) != 0; + return (CurrentThisScope(out _)._flags & ScopeFlags.InClassFieldInit) != 0; } private enum LabelKind : byte diff --git a/src/Acornima/Parser.Statement.cs b/src/Acornima/Parser.Statement.cs index adf354f..2c589cf 100644 --- a/src/Acornima/Parser.Statement.cs +++ b/src/Acornima/Parser.Statement.cs @@ -1428,13 +1428,13 @@ private ClassProperty ParseClassField(in Marker startMarker, Expression key, boo { // To raise SyntaxError if 'arguments' exists in the initializer. ref var scope = ref CurrentThisScope(out var thisScopeIndex); - var oldScopeFlags = scope.Flags; - scope.Flags |= ScopeFlags.InClassFieldInit; + var oldScopeFlags = scope._flags; + scope._flags |= ScopeFlags.InClassFieldInit; value = ParseMaybeAssign(ref NullRef()); scope = _scopeStack.GetItemRef(thisScopeIndex); - scope.Flags = oldScopeFlags; + scope._flags = oldScopeFlags; } else { diff --git a/src/Acornima/ParserOptions.cs b/src/Acornima/ParserOptions.cs index e144dd4..f08bd44 100644 --- a/src/Acornima/ParserOptions.cs +++ b/src/Acornima/ParserOptions.cs @@ -226,4 +226,6 @@ public ParseErrorHandler ErrorHandler /// later during parsing, that is, on nodes which won't become a part of the final AST. /// public OnNodeHandler? OnNode { get => _onNode; init => _onNode = value; } + + internal Action? _onReleaseLargeBuffers; } diff --git a/src/Acornima/Scope.cs b/src/Acornima/Scope.cs index ca695e1..e61391a 100644 --- a/src/Acornima/Scope.cs +++ b/src/Acornima/Scope.cs @@ -1,50 +1,61 @@ -using Acornima.Helpers; -using System.Runtime.CompilerServices; using System; +using System.Runtime.CompilerServices; +using Acornima.Ast; +using Acornima.Helpers; namespace Acornima; +// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scope.js > `class Scope` + public struct Scope { public static bool IsNullRef(in Scope scope) => Unsafe.IsNullRef(ref Unsafe.AsRef(scope)); - // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scope.js > `class Scope` - [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void Reset(int id, ScopeFlags flags, int currentVarScopeIndex, int currentThisScopeIndex) { - Id = id; - Flags = flags; - CurrentVarScopeIndex = currentVarScopeIndex; - CurrentThisScopeIndex = currentThisScopeIndex; - Var.Clear(); - Lexical.Clear(); - Functions.Clear(); + _id = id; + _flags = flags; + _currentVarScopeIndex = currentVarScopeIndex; + _currentThisScopeIndex = currentThisScopeIndex; + _var.Clear(); + _lexical.Clear(); + _functions.Clear(); } - public int Id { get; internal set; } - internal ScopeFlags Flags; - public int CurrentVarScopeIndex { get; internal set; } - public int CurrentThisScopeIndex { get; internal set; } + internal int _id; + /// + /// It is guaranteed that values are assigned sequentially, starting with zero (assigned to the root scope). + /// + public int Id { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _id; } + + internal ScopeFlags _flags; + + internal int _currentVarScopeIndex; + public int CurrentVarScopeIndex { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _currentVarScopeIndex; } + + internal int _currentThisScopeIndex; + public int CurrentThisScopeIndex { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _currentThisScopeIndex; } + + internal ArrayList _var; /// /// A list of var-declared names in the current lexical scope. /// - internal ArrayList Var; + public ReadOnlySpan VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.AsReadOnlySpan(); } - public ReadOnlySpan VarNames => Var.AsReadOnlySpan(); + internal ArrayList _lexical; /// /// A list of lexically-declared names in the current lexical scope. /// - internal ArrayList Lexical; + public ReadOnlySpan LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.AsReadOnlySpan(); } - public ReadOnlySpan LexicalNames => Lexical.AsReadOnlySpan(); + internal ArrayList _functions; /// - /// A list of lexically-declared FunctionDeclaration names in the current lexical scope. + /// A list of lexically-declared names in the current lexical scope. /// - internal ArrayList Functions; - - public ReadOnlySpan FunctionNames => Functions.AsReadOnlySpan(); + public ReadOnlySpan FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functions.AsReadOnlySpan(); } } + diff --git a/src/Acornima/ScopeFlags.cs b/src/Acornima/ScopeFlags.cs index 57fe7e0..d4ad371 100644 --- a/src/Acornima/ScopeFlags.cs +++ b/src/Acornima/ScopeFlags.cs @@ -1,13 +1,13 @@ -using System; +using System; namespace Acornima; +// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scopeflags.js + // Each scope gets a bitset that may contain these flags [Flags] internal enum ScopeFlags { - // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scopeflags.js - None = 0, Top = 1 << 0, Function = 1 << 1,