From 0be0672be59c0ebac155b3a4cdc1ae9d7eefc998 Mon Sep 17 00:00:00 2001 From: Adam Simon Date: Tue, 18 Jun 2024 22:51:40 +0200 Subject: [PATCH] Store variable identifiers instead of plain names for preserving more info --- samples/Acornima.Cli/Commands/ParseCommand.cs | 18 +- .../Commands/PrintScopesCommand.cs | 6 +- .../ParserOptionsExtensions.cs | 59 ++++--- src/Acornima.Extras/ScopeInfo.cs | 31 ++-- src/Acornima.Extras/VariableCollection.cs | 154 ++++++++++++++++++ src/Acornima.Extras/VariableNameCollection.cs | 79 --------- src/Acornima/Parser.Expression.cs | 4 +- src/Acornima/Parser.LVal.cs | 31 ++-- src/Acornima/Parser.Statement.cs | 4 +- src/Acornima/Scope.cs | 68 ++++---- test/Acornima.Tests/ParserTests.cs | 30 ++-- 11 files changed, 287 insertions(+), 197 deletions(-) create mode 100644 src/Acornima.Extras/VariableCollection.cs delete mode 100644 src/Acornima.Extras/VariableNameCollection.cs diff --git a/samples/Acornima.Cli/Commands/ParseCommand.cs b/samples/Acornima.Cli/Commands/ParseCommand.cs index 479bb1f..c39053c 100644 --- a/samples/Acornima.Cli/Commands/ParseCommand.cs +++ b/samples/Acornima.Cli/Commands/ParseCommand.cs @@ -93,10 +93,20 @@ public int OnExecute() ? (node => { var nodeType = node.TypeText; - var scopeInfo = node.UserData as ScopeInfo; - return scopeInfo is not null - ? $"{nodeType}{(scopeInfo.AssociatedNode is IHoistingScope ? "*" : string.Empty)} [{string.Join(", ", scopeInfo.VarNames.Concat(scopeInfo.LexicalNames).Concat(scopeInfo.FunctionNames))}]" - : nodeType; + if (node.UserData is ScopeInfo scopeInfo) + { + var isHoistingScope = scopeInfo.AssociatedNode is IHoistingScope; + var names = scopeInfo.VarVariables.Select(id => id.Name) + .Concat(scopeInfo.LexicalVariables.Select(id => id.Name)) + .Concat(scopeInfo.Functions.Select(id => id.Name)) + .Distinct() + .OrderBy(name => name); + return $"{nodeType}{(isHoistingScope ? "*" : string.Empty)} [{string.Join(", ", names)}]"; + } + else + { + return nodeType; + } }) : (node => { diff --git a/samples/Acornima.Cli/Commands/PrintScopesCommand.cs b/samples/Acornima.Cli/Commands/PrintScopesCommand.cs index f972625..aecc383 100644 --- a/samples/Acornima.Cli/Commands/PrintScopesCommand.cs +++ b/samples/Acornima.Cli/Commands/PrintScopesCommand.cs @@ -58,7 +58,11 @@ public int OnExecute() node => { var scopeInfo = (ScopeInfo)node.UserData!; - var names = scopeInfo.VarNames.Concat(scopeInfo.LexicalNames).Concat(scopeInfo.FunctionNames); + var names = scopeInfo.VarVariables.Select(id => id.Name) + .Concat(scopeInfo.LexicalVariables.Select(id => id.Name)) + .Concat(scopeInfo.Functions.Select(id => id.Name)) + .Distinct() + .OrderBy(name => name); return $"{node.TypeText} ({string.Join(", ", names)})"; }); diff --git a/src/Acornima.Extras/ParserOptionsExtensions.cs b/src/Acornima.Extras/ParserOptionsExtensions.cs index 5e0d039..ba3e460 100644 --- a/src/Acornima.Extras/ParserOptionsExtensions.cs +++ b/src/Acornima.Extras/ParserOptionsExtensions.cs @@ -154,13 +154,13 @@ private void SetScopeInfoCore(Node node, in Scope scope, ReadOnlySpan sco scopeInfoRef ??= new ScopeInfo(); } - ScopeInfo scopeInfo; + var scopeInfo = _scopes.GetItemRef(scope.Id); ref readonly var parentScope = ref scopeStack.Last(); var parentScopeInfo = scope.Id != scopeStack[0].Id ? _scopes[parentScope.Id] : null; - var varNames = scope.VarNames; - var lexicalNames = scope.LexicalNames; - string? additionalVarName = null; + var varVariables = scope.VarVariables; + var lexicalVariables = scope.LexicalVariables; + Identifier? additionalLexicalVariable = null; // In the case of function and catch clause scopes, we need to create a separate scope for parameters, // otherwise variables declared in the body would be "visible" to the parameter nodes. @@ -170,59 +170,56 @@ private void SetScopeInfoCore(Node node, in Scope scope, ReadOnlySpan sco case NodeType.CatchClause: var catchClause = node.As(); - scopeInfo = new ScopeInfo(); - scopeInfo.Initialize(node, - parent: parentScopeInfo, - varScope: _scopes[scopeStack[scope.CurrentVarScopeIndex].Id], - thisScope: _scopes[scopeStack[scope.CurrentThisScopeIndex].Id], - varNames, - lexicalNames: lexicalNames.Slice(0, scope.LexicalParamCount), - functionNames: scope.FunctionNames); + node.UserData = parentScopeInfo = new ScopeInfo().Initialize( + node, + parent: parentScopeInfo, + varScope: _scopes[scopeStack[scope.CurrentVarScopeIndex].Id], + thisScope: _scopes[scopeStack[scope.CurrentThisScopeIndex].Id], + varVariables, + lexicalVariables: lexicalVariables.Slice(0, scope.LexicalParamCount), + functions: scope.Functions); - node.UserData = parentScopeInfo = scopeInfo; node = catchClause.Body; - lexicalNames = lexicalNames.Slice(scope.LexicalParamCount); + lexicalVariables = lexicalVariables.Slice(scope.LexicalParamCount); break; case NodeType.ArrowFunctionExpression or NodeType.FunctionDeclaration or NodeType.FunctionExpression: var function = node.As(); var functionBody = function.Body as FunctionBody; - scopeInfo = functionBody is not null ? new ScopeInfo() : _scopes.GetItemRef(scope.Id); - scopeInfo.Initialize(node, + node.UserData = parentScopeInfo = (functionBody is not null ? new ScopeInfo() : scopeInfo).Initialize( + node, parent: parentScopeInfo, varScope: _scopes[scopeStack[parentScope.CurrentVarScopeIndex].Id], thisScope: _scopes[scopeStack[parentScope.CurrentThisScopeIndex].Id], - varNames: varNames.Slice(0, scope.VarParamCount), - lexicalNames: default, - functionNames: default, - additionalVarName: function.Id?.Name); + varVariables: varVariables.Slice(0, scope.VarParamCount), + lexicalVariables: default, + functions: default, + additionalVarVariable: function.Id); - node.UserData = parentScopeInfo = scopeInfo; if (functionBody is null) { return; } + node = functionBody; - varNames = varNames.Slice(scope.VarParamCount); + varVariables = varVariables.Slice(scope.VarParamCount); break; case NodeType.ClassDeclaration or NodeType.ClassExpression: - additionalVarName = node.As()?.Id?.Name; + additionalLexicalVariable = node.As()?.Id; break; } - scopeInfo = _scopes.GetItemRef(scope.Id); - scopeInfo.Initialize(node, + node.UserData = scopeInfo.Initialize( + node, parent: parentScopeInfo, varScope: scope.CurrentVarScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentVarScopeIndex].Id], thisScope: scope.CurrentThisScopeIndex == scopeStack.Length ? scopeInfo : _scopes[scopeStack[scope.CurrentThisScopeIndex].Id], - varNames, - lexicalNames, - functionNames: scope.FunctionNames, - additionalVarName); - - node.UserData = scopeInfo; + varVariables, + lexicalVariables, + functions: scope.Functions, + additionalLexicalVariable: additionalLexicalVariable); } } } diff --git a/src/Acornima.Extras/ScopeInfo.cs b/src/Acornima.Extras/ScopeInfo.cs index eb2918f..1db9c56 100644 --- a/src/Acornima.Extras/ScopeInfo.cs +++ b/src/Acornima.Extras/ScopeInfo.cs @@ -6,19 +6,14 @@ namespace Acornima; public sealed class ScopeInfo { - private VariableNameCollection _varNames; - private VariableNameCollection _lexicalNames; - private VariableNameCollection _functionNames; - public static ScopeInfo From(Node associatedNode, ScopeInfo? parent, ScopeInfo? varScope, ScopeInfo? thisScope, - ReadOnlySpan varNames, ReadOnlySpan lexicalNames, ReadOnlySpan functionNames) + ReadOnlySpan varVariables, ReadOnlySpan lexicalVariables, ReadOnlySpan functions) { var scope = new ScopeInfo(); - scope.Initialize(associatedNode ?? throw new ArgumentNullException(nameof(associatedNode)), + return scope.Initialize(associatedNode ?? throw new ArgumentNullException(nameof(associatedNode)), parent, varScope ?? scope, thisScope ?? scope, - varNames, lexicalNames, functionNames); - return scope; + varVariables, lexicalVariables, functions); } internal ScopeInfo() @@ -27,17 +22,19 @@ internal ScopeInfo() VarScope = ThisScope = this; } - internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope, - ReadOnlySpan varNames, ReadOnlySpan lexicalNames, ReadOnlySpan functionNames, - string? additionalVarName = null) + internal ScopeInfo Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope, + ReadOnlySpan varVariables, ReadOnlySpan lexicalVariables, ReadOnlySpan functions, + Identifier? additionalVarVariable = null, Identifier? additionalLexicalVariable = null) { AssociatedNode = associatedNode; Parent = parent; VarScope = varScope; ThisScope = thisScope; - _varNames = new VariableNameCollection(varNames, additionalVarName); - _lexicalNames = new VariableNameCollection(lexicalNames, additionalName: null); - _functionNames = new VariableNameCollection(functionNames, additionalName: null); + VarVariables = new VariableCollection(varVariables, additionalVarVariable); + LexicalVariables = new VariableCollection(lexicalVariables, additionalLexicalVariable); + Functions = new VariableCollection(functions, additionalItem: null); + + return this; } public Node AssociatedNode { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } @@ -49,17 +46,17 @@ internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varSc /// /// A list of distinct var-declared names sorted in ascending order in the current lexical scope. /// - public VariableNameCollection VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _varNames; } + public VariableCollection VarVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } /// /// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope. /// - public VariableNameCollection LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexicalNames; } + public VariableCollection LexicalVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } /// /// A list of distinct lexically-declared names sorted in ascending order in the current lexical scope. /// - public VariableNameCollection FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functionNames; } + public VariableCollection Functions { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; } /// /// Gets or sets the arbitrary, user-defined data object associated with the current . diff --git a/src/Acornima.Extras/VariableCollection.cs b/src/Acornima.Extras/VariableCollection.cs new file mode 100644 index 0000000..0e07731 --- /dev/null +++ b/src/Acornima.Extras/VariableCollection.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using Acornima.Ast; + +namespace Acornima; + +[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}")] +[DebuggerTypeProxy(typeof(DebugView))] +public readonly struct VariableCollection : IReadOnlyCollection +{ + private readonly Identifier[]? _items; + + internal VariableCollection(ReadOnlySpan items, Identifier? additionalItem) + { + if (additionalItem is not null) + { + if (items.Length == 0) + { + _items = new[] { additionalItem }; + return; + } + _items = new Identifier[items.Length + 1]; + _items[0] = additionalItem; + items.CopyTo(_items.AsSpan(1)); + } + else + { + if (items.Length == 0) + { + return; + } + _items = items.ToArray(); + } + + Array.Sort(_items, NameComparer.Instance); + } + + public VariableCollection(ReadOnlySpan items) + : this(items, additionalItem: null) { } + + public VariableCollection(IEnumerable items) + { + _items = items.ToArray(); + Array.Sort(_items, NameComparer.Instance); + } + + public int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _items?.Length ?? 0; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Contains(Identifier item) + { + return _items is not null && Array.IndexOf(_items, item) >= 0; + } + + public bool Contains(string name) + { + for (int lo = 0, hi = Count - 1; lo <= hi;) + { + var i = lo + ((hi - lo) >> 1); + var order = string.CompareOrdinal(_items![i].Name, name); + if (order < 0) + { + lo = i + 1; + } + else if (order > 0) + { + hi = i - 1; + } + else + { + return true; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public struct Enumerator : IEnumerator + { + private readonly Identifier[]? _items; + private readonly int _count; + private int _index; + + internal Enumerator(VariableCollection list) + { + _items = list._items; + _count = _items?.Length ?? 0; + _index = -1; + } + + public readonly void Dispose() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() + { + var index = _index + 1; + if (index < _count) + { + _index = index; + return true; + } + + return false; + } + + public void Reset() + { + _index = -1; + } + + /// + /// According to the specification, + /// accessing before calling or after returning is undefined behavior. + /// Thus, to maximize performance, this implementation doesn't do any null or range checks, just let the default exceptions occur on invalid access. + /// + public readonly Identifier Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _items![_index]; } + + readonly object? IEnumerator.Current => Current; + } + + private sealed class NameComparer : IComparer + { + public static readonly NameComparer Instance = new(); + + private NameComparer() { } + + public int Compare(Identifier? x, Identifier? y) => string.CompareOrdinal(x!.Name, y!.Name); + } + + [DebuggerNonUserCode] + private sealed class DebugView + { + private readonly VariableCollection _collection; + + public DebugView(VariableCollection collection) + { + _collection = collection; + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public Identifier[] Items => _collection.ToArray(); + } +} diff --git a/src/Acornima.Extras/VariableNameCollection.cs b/src/Acornima.Extras/VariableNameCollection.cs deleted file mode 100644 index 079d6d1..0000000 --- a/src/Acornima.Extras/VariableNameCollection.cs +++ /dev/null @@ -1,79 +0,0 @@ -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; - - internal VariableNameCollection(ReadOnlySpan names, string? additionalName) - { - if (additionalName is not null) - { - if (names.Length == 0) - { - _names = new ArrayList(new[] { additionalName }); - return; - } - _names = new ArrayList(initialCapacity: names.Length + 1) { additionalName }; - } - else - { - if (names.Length == 0) - { - return; - } - _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 VariableNameCollection(ReadOnlySpan names) - : this(names, null) { } - - 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/Parser.Expression.cs b/src/Acornima/Parser.Expression.cs index 1c50afa..3241b1d 100644 --- a/src/Acornima/Parser.Expression.cs +++ b/src/Acornima/Parser.Expression.cs @@ -1758,8 +1758,8 @@ private void CheckParams(in NodeList parameters, bool allowDuplicates) CheckLValInnerPattern(param!, BindingType.Var, checkClashes: nameHash); } - ref var varNames = ref CurrentScope._var; - varNames.ParamCount = varNames.Count; + ref var varList = ref CurrentScope._var; + varList.ParamCount = varList.Count; } // Parses a comma-separated list of expressions, and returns them as diff --git a/src/Acornima/Parser.LVal.cs b/src/Acornima/Parser.LVal.cs index 500af28..1a44c1c 100644 --- a/src/Acornima/Parser.LVal.cs +++ b/src/Acornima/Parser.LVal.cs @@ -469,7 +469,7 @@ private void CheckLValSimple(Node expr, BindingType bindingType = BindingType.No if (bindingType != BindingType.Outside) { - DeclareName(identifier.Name, bindingType, identifier.Start); + DeclareName(identifier, bindingType); } } break; @@ -561,18 +561,19 @@ private void CheckLValInnerPattern(Node pattern, BindingType bindingType = Bindi } } - private void DeclareName(string name, BindingType bindingType, int pos) + private void DeclareName(Identifier id, BindingType bindingType) { // https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scope.js > `pp.declareName = function` var redeclared = false; + var name = id.Name; ref var scope = ref NullRef(); switch (bindingType) { 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); + redeclared = scope._lexical.Contains(name) || scope._functions.Contains(name) || scope._var.Contains(name); + scope._lexical.Add(id); if (_inModule && (scope._flags & ScopeFlags.Top) != 0) { _undefinedExports!.Remove(name); @@ -581,29 +582,29 @@ private void DeclareName(string name, BindingType bindingType, int pos) case BindingType.SimpleCatch: scope = ref CurrentScope; - scope._lexical.Add(name); + scope._lexical.Add(id); 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); + ? scope._lexical.Contains(name) + : scope._lexical.Contains(name) || scope._var.Contains(name); + scope._functions.Add(id); 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.Contains(name) && !((scope._flags & ScopeFlags.SimpleCatch) != 0 && scope._lexical[0] == name) + || (scope._flags & _functionsAsVarInScopeFlags) == 0 && scope._functions.Contains(name)) { redeclared = true; break; } - scope._var.Add(name); + scope._var.Add(id); if (_inModule && (scope._flags & ScopeFlags.Top) != 0) { _undefinedExports!.Remove(name); @@ -618,8 +619,8 @@ private void DeclareName(string name, BindingType bindingType, int pos) if (redeclared) { - // RaiseRecoverable(pos, $"Identifier '{name}' has already been declared"); // original acornjs error reporting - Raise(pos, VarRedeclaration, new object[] { name }); + // RaiseRecoverable(id.Start, $"Identifier '{name}' has already been declared"); // original acornjs error reporting + Raise(id.Start, VarRedeclaration, new object[] { name }); } } @@ -629,8 +630,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.Contains(id.Name) + && !rootScope._var.Contains(id.Name)) { _undefinedExports![id.Name] = id.Start; } diff --git a/src/Acornima/Parser.Statement.cs b/src/Acornima/Parser.Statement.cs index 7e351cb..aa75f04 100644 --- a/src/Acornima/Parser.Statement.cs +++ b/src/Acornima/Parser.Statement.cs @@ -739,8 +739,8 @@ private Node ParseCatchClauseParam() EnterScope(scopeFlags); CheckLValPattern(param, bindingType); - ref var lexicalNames = ref CurrentScope._lexical; - lexicalNames.ParamCount = lexicalNames.Count; + ref var lexicalList = ref CurrentScope._lexical; + lexicalList.ParamCount = lexicalList.Count; Expect(TokenType.ParenRight); diff --git a/src/Acornima/Scope.cs b/src/Acornima/Scope.cs index ea3840a..042bf72 100644 --- a/src/Acornima/Scope.cs +++ b/src/Acornima/Scope.cs @@ -19,11 +19,9 @@ internal void Reset(int id, ScopeFlags flags, int currentVarScopeIndex, int curr _flags = flags; _currentVarScopeIndex = currentVarScopeIndex; _currentThisScopeIndex = currentThisScopeIndex; - _var.Clear(); - _var.ParamCount = 0; - _lexical.Clear(); - _lexical.ParamCount = 0; - _functions.Clear(); + _var.Reset(); + _lexical.Reset(); + _functions.Reset(); } internal int _id; @@ -40,36 +38,36 @@ internal void Reset(int id, ScopeFlags flags, int currentVarScopeIndex, int curr internal int _currentThisScopeIndex; public int CurrentThisScopeIndex { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _currentThisScopeIndex; } - internal NameList _var; + internal VariableList _var; /// - /// A list of var-declared names in the current lexical scope. + /// A list of var-declared names in the current lexical scope. In the case of function scopes, also includes parameter names (at the beginning of the span). /// - public ReadOnlySpan VarNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.AsReadOnlySpan(); } + public ReadOnlySpan VarVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.AsReadOnlySpan(); } /// - /// The number of parameter names at the beginning of the span. May be greater than zero in the case of function scopes. + /// The number of parameter names at the beginning of the span. /// public int VarParamCount { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.ParamCount; } - internal NameList _lexical; + internal VariableList _lexical; /// - /// A list of lexically-declared names in the current lexical scope. + /// A list of lexically-declared names in the current lexical scope. In the case of catch clause scopes, also includes parameter names (at the beginning of the span). /// - public ReadOnlySpan LexicalNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.AsReadOnlySpan(); } + public ReadOnlySpan LexicalVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.AsReadOnlySpan(); } /// - /// The number of parameter names at the beginning of the span. May be greater than zero in the case of catch clause scopes. + /// The number of parameter names at the beginning of the span. /// public int LexicalParamCount { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.ParamCount; } - internal ArrayList _functions; + internal VariableList _functions; /// /// A list of lexically-declared names in the current lexical scope. /// - public ReadOnlySpan FunctionNames { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functions.AsReadOnlySpan(); } + public ReadOnlySpan Functions { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functions.AsReadOnlySpan(); } // This is a heavily slimmed down version of ArrayList for collecting variable and param names. // Ideally, we'd just use an ArrayList for this purpose. However, we also need to store an extra 32-bit integer @@ -83,9 +81,9 @@ internal void Reset(int id, ScopeFlags flags, int currentVarScopeIndex, int curr [DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}")] [DebuggerTypeProxy(typeof(DebugView))] #endif - internal struct NameList + internal struct VariableList { - private string[]? _items; + private Identifier[]? _items; private int _count; public int ParamCount; @@ -101,7 +99,7 @@ public readonly string this[int index] return ThrowIndexOutOfRangeException(); } - return _items![index]; + return _items![index].Name; } } @@ -111,7 +109,7 @@ public readonly int Count get => _count; } - public void Add(string item) + public void Add(Identifier item) { var capacity = _items?.Length ?? 0; @@ -124,38 +122,46 @@ public void Add(string item) _items![_count++] = item; } - public void Clear() + public void Reset() { if (_count != 0) { Array.Clear(_items!, 0, _count); _count = 0; } + ParamCount = 0; } - public readonly int IndexOf(string item) + public readonly bool Contains(string name) { - return _count != 0 ? Array.IndexOf(_items!, item, 0, _count) : -1; + for (var i = 0; i < _count; i++) + { + if (_items![i].Name == name) + { + return true; + } + } + return false; } /// - /// WARNING: Items should not be added or removed from the while the returned is in use. + /// WARNING: Items should not be added or removed from the while the returned is in use. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal readonly ReadOnlySpan AsReadOnlySpan() + internal readonly ReadOnlySpan AsReadOnlySpan() { - return new ReadOnlySpan(_items, 0, _count); + return new ReadOnlySpan(_items, 0, _count); } #if DEBUG - public readonly string[] ToArray() + public readonly Identifier[] ToArray() { if (_count == 0) { - return Array.Empty(); + return Array.Empty(); } - var array = new string[_count]; + var array = new Identifier[_count]; Array.Copy(_items!, 0, array, 0, _count); return array; } @@ -163,15 +169,15 @@ public readonly string[] ToArray() [DebuggerNonUserCode] private sealed class DebugView { - private readonly NameList _list; + private readonly VariableList _list; - public DebugView(NameList list) + public DebugView(VariableList list) { _list = list; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] - public string[] Items => _list.ToArray(); + public Identifier[] Items => _list.ToArray(); } #endif } diff --git a/test/Acornima.Tests/ParserTests.cs b/test/Acornima.Tests/ParserTests.cs index 759a451..bd8f646 100644 --- a/test/Acornima.Tests/ParserTests.cs +++ b/test/Acornima.Tests/ParserTests.cs @@ -290,27 +290,27 @@ public void RecordsScopeInfoInUserDataCorrectly(bool registerUserHandler, bool e Assert.Null(nodesWithScopes[0].Parent); Assert.Same(nodesWithScopes[0], nodesWithScopes[0].VarScope); Assert.Same(nodesWithScopes[0], nodesWithScopes[0].ThisScope); - Assert.Equal(new[] { "toObj" }, nodesWithScopes[0].FunctionNames); - Assert.Empty(nodesWithScopes[0].LexicalNames); - Assert.Empty(nodesWithScopes[0].VarNames); + Assert.Equal(new[] { "toObj" }, nodesWithScopes[0].Functions.Select(id => id.Name)); + Assert.Empty(nodesWithScopes[0].LexicalVariables); + Assert.Empty(nodesWithScopes[0].VarVariables); var functionDeclaration = script.Body[0].As(); Assert.Same(functionDeclaration, nodesWithScopes[1].AssociatedNode); Assert.Same(nodesWithScopes[0], nodesWithScopes[1].Parent); Assert.Same(nodesWithScopes[0], nodesWithScopes[1].VarScope); Assert.Same(nodesWithScopes[0], nodesWithScopes[1].ThisScope); - Assert.Empty(nodesWithScopes[1].FunctionNames); - Assert.Empty(nodesWithScopes[1].LexicalNames); - Assert.Equal(new[] { "a", "b", "toObj" }, nodesWithScopes[1].VarNames); + Assert.Empty(nodesWithScopes[1].Functions); + Assert.Empty(nodesWithScopes[1].LexicalVariables); + Assert.Equal(new[] { "a", "b", "toObj" }, nodesWithScopes[1].VarVariables.Select(id => id.Name)); var functionBody = functionDeclaration.Body; Assert.Same(functionBody, nodesWithScopes[2].AssociatedNode); Assert.Same(nodesWithScopes[1], nodesWithScopes[2].Parent); Assert.Same(nodesWithScopes[2], nodesWithScopes[2].VarScope); Assert.Same(nodesWithScopes[2], nodesWithScopes[2].ThisScope); - Assert.Empty(nodesWithScopes[2].FunctionNames); - Assert.Empty(nodesWithScopes[2].LexicalNames); - Assert.Empty(nodesWithScopes[2].VarNames); + Assert.Empty(nodesWithScopes[2].Functions); + Assert.Empty(nodesWithScopes[2].LexicalVariables); + Assert.Empty(nodesWithScopes[2].VarVariables); var arrowFunctionExpression = functionBody.Body[0] .As().Argument! @@ -321,18 +321,18 @@ public void RecordsScopeInfoInUserDataCorrectly(bool registerUserHandler, bool e Assert.Same(nodesWithScopes[2], nodesWithScopes[3].Parent); Assert.Same(nodesWithScopes[2], nodesWithScopes[3].VarScope); Assert.Same(nodesWithScopes[2], nodesWithScopes[3].ThisScope); - Assert.Empty(nodesWithScopes[3].FunctionNames); - Assert.Empty(nodesWithScopes[3].LexicalNames); - Assert.Equal(new[] { "x" }, nodesWithScopes[3].VarNames); + Assert.Empty(nodesWithScopes[3].Functions); + Assert.Empty(nodesWithScopes[3].LexicalVariables); + Assert.Equal(new[] { "x" }, nodesWithScopes[3].VarVariables.Select(id => id.Name)); functionBody = arrowFunctionExpression.Body.As(); Assert.Same(functionBody, nodesWithScopes[4].AssociatedNode); Assert.Same(nodesWithScopes[3], nodesWithScopes[4].Parent); Assert.Same(nodesWithScopes[4], nodesWithScopes[4].VarScope); Assert.Same(nodesWithScopes[2], nodesWithScopes[4].ThisScope); - Assert.Empty(nodesWithScopes[4].FunctionNames); - Assert.Equal(new[] { "y" }, nodesWithScopes[4].LexicalNames); - Assert.Empty(nodesWithScopes[4].VarNames); + Assert.Empty(nodesWithScopes[4].Functions); + Assert.Equal(new[] { "y" }, nodesWithScopes[4].LexicalVariables.Select(id => id.Name)); + Assert.Empty(nodesWithScopes[4].VarVariables); Assert.Equal(registerUserHandler, userHandlerCalled); }