-
-
Notifications
You must be signed in to change notification settings - Fork 560
/
Engine.Ast.cs
250 lines (216 loc) · 9.29 KB
/
Engine.Ast.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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
using System.Runtime.InteropServices;
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Jint.Runtime.Interpreter.Statements;
using Environment = Jint.Runtime.Environments.Environment;
namespace Jint;
public partial class Engine
{
/// <summary>
/// Prepares a script for the engine that includes static analysis data to speed up execution during run-time.
/// </summary>
/// <remarks>
/// Returned instance is reusable and thread-safe. You should prepare scripts only once and then reuse them.
/// </remarks>
public static Prepared<Script> PrepareScript(string code, string? source = null, bool strict = false, ScriptPreparationOptions? options = null)
{
options ??= ScriptPreparationOptions.Default;
var astAnalyzer = new AstAnalyzer(options);
var parserOptions = options.GetParserOptions();
var parser = new Parser(parserOptions with { OnNode = astAnalyzer.NodeVisitor });
try
{
var preparedScript = parser.ParseScript(code, source, strict);
return new Prepared<Script>(preparedScript, parserOptions);
}
catch (Exception e)
{
throw new ScriptPreparationException("Could not prepare script: " + e.Message, e);
}
}
/// <summary>
/// Prepares a module for the engine that includes static analysis data to speed up execution during run-time.
/// </summary>
/// <remarks>
/// Returned instance is reusable and thread-safe. You should prepare modules only once and then reuse them.
/// </remarks>
public static Prepared<Module> PrepareModule(string code, string? source = null, ModulePreparationOptions? options = null)
{
options ??= ModulePreparationOptions.Default;
var astAnalyzer = new AstAnalyzer(options);
var parserOptions = options.GetParserOptions();
var parser = new Parser(parserOptions with { OnNode = astAnalyzer.NodeVisitor });
try
{
var preparedModule = parser.ParseModule(code, source);
return new Prepared<Module>(preparedModule, parserOptions);
}
catch (Exception e)
{
throw new ScriptPreparationException("Could not prepare script: " + e.Message, e);
}
}
private sealed class AstAnalyzer
{
private readonly IPreparationOptions<IParsingOptions> _preparationOptions;
private readonly Dictionary<string, Environment.BindingName> _bindingNames = new(StringComparer.Ordinal);
public AstAnalyzer(IPreparationOptions<IParsingOptions> preparationOptions)
{
_preparationOptions = preparationOptions;
}
public void NodeVisitor(Node node, OnNodeContext _)
{
switch (node.Type)
{
case NodeType.Identifier:
var identifier = (Identifier) node;
var name = identifier.Name;
if (!_bindingNames.TryGetValue(name, out var bindingName))
{
_bindingNames[name] = bindingName = new Environment.BindingName(JsString.CachedCreate(name));
}
node.UserData = new JintIdentifierExpression(identifier, bindingName);
break;
case NodeType.Literal:
var literal = (Literal) node;
var constantValue = JintLiteralExpression.ConvertToJsValue(literal);
node.UserData = constantValue is not null ? new JintConstantExpression(literal, constantValue) : null;
break;
case NodeType.MemberExpression:
node.UserData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true);
break;
case NodeType.ArrowFunctionExpression:
case NodeType.FunctionDeclaration:
case NodeType.FunctionExpression:
var function = (IFunction) node;
node.UserData = JintFunctionDefinition.BuildState(function);
break;
case NodeType.Program:
node.UserData = new CachedHoistingScope((Program) node);
break;
case NodeType.UnaryExpression:
node.UserData = JintUnaryExpression.BuildConstantExpression((NonUpdateUnaryExpression) node);
break;
case NodeType.BinaryExpression:
var binaryExpression = (NonLogicalBinaryExpression) node;
if (_preparationOptions.FoldConstants
&& binaryExpression.Operator != Operator.InstanceOf
&& binaryExpression.Operator != Operator.In
&& binaryExpression is { Left: Literal leftLiteral, Right: Literal rightLiteral })
{
var left = JintLiteralExpression.ConvertToJsValue(leftLiteral);
var right = JintLiteralExpression.ConvertToJsValue(rightLiteral);
if (left is not null && right is not null)
{
// we have fixed result
try
{
var result = JintBinaryExpression.Build(binaryExpression);
var context = new EvaluationContext();
node.UserData = new JintConstantExpression(binaryExpression, (JsValue) result.EvaluateWithoutNodeTracking(context));
}
catch
{
// probably caused an error and error reporting doesn't work without engine
}
}
}
break;
case NodeType.ReturnStatement:
var returnStatement = (ReturnStatement) node;
if (returnStatement.Argument is Literal returnedLiteral)
{
var returnValue = JintLiteralExpression.ConvertToJsValue(returnedLiteral);
if (returnValue is not null)
{
node.UserData = new ConstantStatement(returnStatement, CompletionType.Return, returnValue);
}
}
break;
}
}
}
}
internal sealed class CachedHoistingScope
{
public CachedHoistingScope(Program program)
{
Scope = HoistingScope.GetProgramLevelDeclarations(program);
VarNames = new List<Key>();
GatherVarNames(Scope, VarNames);
LexNames = new List<CachedLexicalName>();
GatherLexNames(Scope, LexNames);
}
internal static void GatherVarNames(HoistingScope scope, List<Key> boundNames)
{
var varDeclarations = scope._variablesDeclarations;
if (varDeclarations != null)
{
for (var i = 0; i < varDeclarations.Count; i++)
{
var d = varDeclarations[i];
d.GetBoundNames(boundNames);
}
}
}
internal static void GatherLexNames(HoistingScope scope, List<CachedLexicalName> boundNames)
{
var lexDeclarations = scope._lexicalDeclarations;
if (lexDeclarations != null)
{
var temp = new List<Key>();
for (var i = 0; i < lexDeclarations.Count; i++)
{
var d = lexDeclarations[i];
temp.Clear();
d.GetBoundNames(temp);
for (var j = 0; j < temp.Count; j++)
{
boundNames.Add(new CachedLexicalName(temp[j], d.IsConstantDeclaration()));
}
}
}
}
[StructLayout(LayoutKind.Auto)]
internal readonly record struct CachedLexicalName(Key Name, bool Constant);
public HoistingScope Scope { get; }
public List<Key> VarNames { get; }
public List<CachedLexicalName> LexNames { get; }
}
internal static class AstPreparationExtensions
{
internal static HoistingScope GetHoistingScope(this Program program)
{
return program.UserData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program);
}
internal static List<Key> GetVarNames(this Program program, HoistingScope hoistingScope)
{
List<Key> boundNames;
if (program.UserData is CachedHoistingScope cached)
{
boundNames = cached.VarNames;
}
else
{
boundNames = new List<Key>();
CachedHoistingScope.GatherVarNames(hoistingScope, boundNames);
}
return boundNames;
}
internal static List<CachedHoistingScope.CachedLexicalName> GetLexNames(this Program program, HoistingScope hoistingScope)
{
List<CachedHoistingScope.CachedLexicalName> boundNames;
if (program.UserData is CachedHoistingScope cached)
{
boundNames = cached.LexNames;
}
else
{
boundNames = new List<CachedHoistingScope.CachedLexicalName>();
CachedHoistingScope.GatherLexNames(hoistingScope, boundNames);
}
return boundNames;
}
}