Skip to content

Commit

Permalink
Store variable identifiers instead of plain names for preserving more…
Browse files Browse the repository at this point in the history
… info
  • Loading branch information
adams85 committed Jun 19, 2024
1 parent 831a75f commit 0be0672
Show file tree
Hide file tree
Showing 11 changed files with 287 additions and 197 deletions.
18 changes: 14 additions & 4 deletions samples/Acornima.Cli/Commands/ParseCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
{
Expand Down
6 changes: 5 additions & 1 deletion samples/Acornima.Cli/Commands/PrintScopesCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)})";
});

Expand Down
59 changes: 28 additions & 31 deletions src/Acornima.Extras/ParserOptionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ private void SetScopeInfoCore(Node node, in Scope scope, ReadOnlySpan<Scope> 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.
Expand All @@ -170,59 +170,56 @@ private void SetScopeInfoCore(Node node, in Scope scope, ReadOnlySpan<Scope> sco
case NodeType.CatchClause:
var catchClause = node.As<CatchClause>();

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<IFunction>();
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<IClass>()?.Id?.Name;
additionalLexicalVariable = node.As<IClass>()?.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);
}
}
}
31 changes: 14 additions & 17 deletions src/Acornima.Extras/ScopeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames)
ReadOnlySpan<Identifier> varVariables, ReadOnlySpan<Identifier> lexicalVariables, ReadOnlySpan<Identifier> 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()
Expand All @@ -27,17 +22,19 @@ internal ScopeInfo()
VarScope = ThisScope = this;
}

internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope,
ReadOnlySpan<string> varNames, ReadOnlySpan<string> lexicalNames, ReadOnlySpan<string> functionNames,
string? additionalVarName = null)
internal ScopeInfo Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varScope, ScopeInfo thisScope,
ReadOnlySpan<Identifier> varVariables, ReadOnlySpan<Identifier> lexicalVariables, ReadOnlySpan<Identifier> 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; }
Expand All @@ -49,17 +46,17 @@ internal void Initialize(Node associatedNode, ScopeInfo? parent, ScopeInfo varSc
/// <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; }
public VariableCollection VarVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }

/// <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; }
public VariableCollection LexicalVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }

/// <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; }
public VariableCollection Functions { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; private set; }

/// <summary>
/// Gets or sets the arbitrary, user-defined data object associated with the current <see cref="ScopeInfo"/>.
Expand Down
154 changes: 154 additions & 0 deletions src/Acornima.Extras/VariableCollection.cs
Original file line number Diff line number Diff line change
@@ -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<Identifier>
{
private readonly Identifier[]? _items;

internal VariableCollection(ReadOnlySpan<Identifier> 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<Identifier> items)
: this(items, additionalItem: null) { }

public VariableCollection(IEnumerable<Identifier> 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<Identifier> IEnumerable<Identifier>.GetEnumerator() => GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public struct Enumerator : IEnumerator<Identifier>
{
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;
}

/// <remarks>
/// According to the <see href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerator-1.current#remarks">specification</see>,
/// accessing <see cref="Current"/> before calling <see cref="MoveNext"/> or after <see cref="MoveNext"/> returning <see langword="false"/> 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.
/// </remarks>
public readonly Identifier Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _items![_index]; }

readonly object? IEnumerator.Current => Current;
}

private sealed class NameComparer : IComparer<Identifier>
{
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();
}
}
Loading

0 comments on commit 0be0672

Please sign in to comment.