-
Notifications
You must be signed in to change notification settings - Fork 3
/
Scope.cs
184 lines (151 loc) · 6.36 KB
/
Scope.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using Acornima.Ast;
using Acornima.Helpers;
namespace Acornima;
using static ExceptionHelper;
// https://github.com/acornjs/acorn/blob/8.11.3/acorn/src/scope.js > `class Scope`
public struct Scope
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Reset(int id, ScopeFlags flags, int currentVarScopeIndex, int currentThisScopeIndex)
{
_id = id;
_flags = flags;
_currentVarScopeIndex = currentVarScopeIndex;
_currentThisScopeIndex = currentThisScopeIndex;
_var.Reset();
_lexical.Reset();
_functions.Reset();
}
internal int _id;
/// <remarks>
/// It is guaranteed that <see cref="Id"/> values are assigned sequentially, starting with zero (assigned to the root scope).
/// </remarks>
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 VariableList _var;
/// <summary>
/// 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).
/// </summary>
public ReadOnlySpan<Identifier> VarVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.AsReadOnlySpan(); }
/// <summary>
/// The number of parameter names at the beginning of the <see cref="VarVariables"/> span.
/// </summary>
public int VarParamCount { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _var.ParamCount; }
internal VariableList _lexical;
/// <summary>
/// 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).
/// </summary>
public ReadOnlySpan<Identifier> LexicalVariables { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.AsReadOnlySpan(); }
/// <summary>
/// The number of parameter names at the beginning of the <see cref="LexicalVariables"/> span.
/// </summary>
public int LexicalParamCount { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _lexical.ParamCount; }
internal VariableList _functions;
/// <summary>
/// A list of lexically-declared <see cref="FunctionDeclaration"/> names in the current lexical scope.
/// </summary>
public ReadOnlySpan<Identifier> Functions { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _functions.AsReadOnlySpan(); }
// This is a heavily slimmed down version of ArrayList<string> for collecting variable and param names.
// Ideally, we'd just use an ArrayList<string> for this purpose. However, we also need to store an extra 32-bit integer
// that indicates the number of parameters in the case of function and catch clause scopes.
// ArrayLists need only 12 bytes but are padded to 64 bytes on x64 architectures. The layout of the Scope struct is
// so unfortunate that adding an extra int field to it would increase its size by 8 bytes while there'd be 3*4=12 bytes of
// wasted space because of the padding of ArrayList fields. Unfortunately, there seems to be no cleaner and safer way
// to utilize that wasted space than duplicating some code.
#if DEBUG
[DebuggerDisplay($"{nameof(Count)} = {{{nameof(Count)}}}")]
[DebuggerTypeProxy(typeof(DebugView))]
#endif
internal struct VariableList
{
private Identifier[]? _items;
private int _count;
public int ParamCount;
public readonly string this[int index]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
// Following trick can reduce the range check by one
if ((uint)index >= (uint)_count)
{
return ThrowIndexOutOfRangeException<string>();
}
return _items![index].Name;
}
}
public readonly int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _count;
}
public void Add(Identifier item)
{
var capacity = _items?.Length ?? 0;
if (_count == capacity)
{
Array.Resize(ref _items, Math.Max(checked((int)ArrayList<string>.GrowCapacity(capacity)), ArrayList<string>.MinAllocatedCount));
}
Debug.Assert(_items is not null);
_items![_count++] = item;
}
public void Reset()
{
if (_count != 0)
{
Array.Clear(_items!, 0, _count);
_count = 0;
}
ParamCount = 0;
}
public readonly bool Contains(string name)
{
for (var i = 0; i < _count; i++)
{
if (_items![i].Name == name)
{
return true;
}
}
return false;
}
/// <remarks>
/// WARNING: Items should not be added or removed from the <see cref="VariableList"/> while the returned <see cref="ReadOnlySpan{T}"/> is in use.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal readonly ReadOnlySpan<Identifier> AsReadOnlySpan()
{
return new ReadOnlySpan<Identifier>(_items, 0, _count);
}
#if DEBUG
public readonly Identifier[] ToArray()
{
if (_count == 0)
{
return Array.Empty<Identifier>();
}
var array = new Identifier[_count];
Array.Copy(_items!, 0, array, 0, _count);
return array;
}
[DebuggerNonUserCode]
private sealed class DebugView
{
private readonly VariableList _list;
public DebugView(VariableList list)
{
_list = list;
}
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public Identifier[] Items => _list.ToArray();
}
#endif
}
}