|
@@ -1,9 +1,12 @@
|
|
using System.Runtime.InteropServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
+using System.Text.RegularExpressions;
|
|
using Esprima;
|
|
using Esprima;
|
|
using Esprima.Ast;
|
|
using Esprima.Ast;
|
|
using Jint.Native;
|
|
using Jint.Native;
|
|
|
|
+using Jint.Runtime;
|
|
using Jint.Runtime.Interpreter;
|
|
using Jint.Runtime.Interpreter;
|
|
using Jint.Runtime.Interpreter.Expressions;
|
|
using Jint.Runtime.Interpreter.Expressions;
|
|
|
|
+using Jint.Runtime.Interpreter.Statements;
|
|
using Environment = Jint.Runtime.Environments.Environment;
|
|
using Environment = Jint.Runtime.Environments.Environment;
|
|
|
|
|
|
namespace Jint;
|
|
namespace Jint;
|
|
@@ -18,11 +21,10 @@ public partial class Engine
|
|
/// </remarks>
|
|
/// </remarks>
|
|
public static Script PrepareScript(string script, string? source = null, bool strict = false)
|
|
public static Script PrepareScript(string script, string? source = null, bool strict = false)
|
|
{
|
|
{
|
|
- var astAnalyzer = new AstAnalyzer();
|
|
|
|
|
|
+ var astAnalyzer = new AstAnalyzer(new ScriptPreparationOptions());
|
|
var options = ParserOptions.Default with
|
|
var options = ParserOptions.Default with
|
|
{
|
|
{
|
|
- AllowReturnOutsideFunction = true,
|
|
|
|
- OnNodeCreated = astAnalyzer.NodeVisitor
|
|
|
|
|
|
+ AllowReturnOutsideFunction = true, OnNodeCreated = astAnalyzer.NodeVisitor
|
|
};
|
|
};
|
|
|
|
|
|
return new JavaScriptParser(options).ParseScript(script, source, strict);
|
|
return new JavaScriptParser(options).ParseScript(script, source, strict);
|
|
@@ -36,47 +38,137 @@ public partial class Engine
|
|
/// </remarks>
|
|
/// </remarks>
|
|
public static Module PrepareModule(string script, string? source = null)
|
|
public static Module PrepareModule(string script, string? source = null)
|
|
{
|
|
{
|
|
- var astAnalyzer = new AstAnalyzer();
|
|
|
|
- var options = ParserOptions.Default with { OnNodeCreated = astAnalyzer.NodeVisitor };
|
|
|
|
|
|
+ var astAnalyzer = new AstAnalyzer(new ScriptPreparationOptions());
|
|
|
|
+ var options = ParserOptions.Default with
|
|
|
|
+ {
|
|
|
|
+ OnNodeCreated = astAnalyzer.NodeVisitor
|
|
|
|
+ };
|
|
|
|
|
|
return new JavaScriptParser(options).ParseModule(script, source);
|
|
return new JavaScriptParser(options).ParseModule(script, source);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ [StructLayout(LayoutKind.Auto)]
|
|
|
|
+ private readonly record struct ScriptPreparationOptions(bool CompileRegex, bool FoldConstants)
|
|
|
|
+ {
|
|
|
|
+ public ScriptPreparationOptions() : this(CompileRegex: true, FoldConstants: true)
|
|
|
|
+ {
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
private sealed class AstAnalyzer
|
|
private sealed class AstAnalyzer
|
|
{
|
|
{
|
|
|
|
+ private readonly ScriptPreparationOptions _options;
|
|
private readonly Dictionary<string, Environment.BindingName> _bindingNames = new(StringComparer.Ordinal);
|
|
private readonly Dictionary<string, Environment.BindingName> _bindingNames = new(StringComparer.Ordinal);
|
|
|
|
+ private readonly Dictionary<string, Regex> _regexes = new(StringComparer.Ordinal);
|
|
|
|
+
|
|
|
|
+ public AstAnalyzer(ScriptPreparationOptions options)
|
|
|
|
+ {
|
|
|
|
+ _options = options;
|
|
|
|
+ }
|
|
|
|
|
|
public void NodeVisitor(Node node)
|
|
public void NodeVisitor(Node node)
|
|
{
|
|
{
|
|
switch (node.Type)
|
|
switch (node.Type)
|
|
{
|
|
{
|
|
case Nodes.Identifier:
|
|
case Nodes.Identifier:
|
|
|
|
+ var identifier = (Identifier) node;
|
|
|
|
+ var name = identifier.Name;
|
|
|
|
+
|
|
|
|
+ if (!_bindingNames.TryGetValue(name, out var bindingName))
|
|
{
|
|
{
|
|
- var name = ((Identifier) node).Name;
|
|
|
|
|
|
+ _bindingNames[name] = bindingName = new Environment.BindingName(JsString.CachedCreate(name));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ node.AssociatedData = new JintIdentifierExpression(identifier, bindingName);
|
|
|
|
+ break;
|
|
|
|
|
|
- if (!_bindingNames.TryGetValue(name, out var bindingName))
|
|
|
|
|
|
+ case Nodes.Literal:
|
|
|
|
+ var literal = (Literal) node;
|
|
|
|
+
|
|
|
|
+ var constantValue = JintLiteralExpression.ConvertToJsValue(literal);
|
|
|
|
+ node.AssociatedData = constantValue is not null ? new JintConstantExpression(literal, constantValue) : null;
|
|
|
|
+
|
|
|
|
+ if (node.AssociatedData is null && literal.TokenType == TokenType.RegularExpression && _options.CompileRegex)
|
|
|
|
+ {
|
|
|
|
+ var regExpLiteral = (RegExpLiteral) literal;
|
|
|
|
+ var regExpParseResult = regExpLiteral.ParseResult;
|
|
|
|
+ if (regExpParseResult.Success)
|
|
{
|
|
{
|
|
- _bindingNames[name] = bindingName = new Environment.BindingName(JsString.CachedCreate(name));
|
|
|
|
- }
|
|
|
|
|
|
+ if (!_regexes.TryGetValue(regExpLiteral.Raw, out var regex))
|
|
|
|
+ {
|
|
|
|
+ regex = regExpParseResult.Regex!;
|
|
|
|
+ if ((regex.Options & RegexOptions.Compiled) == RegexOptions.None)
|
|
|
|
+ {
|
|
|
|
+ regex = new Regex(regex.ToString(), regex.Options | RegexOptions.Compiled, regex.MatchTimeout);
|
|
|
|
+ }
|
|
|
|
|
|
- node.AssociatedData = bindingName;
|
|
|
|
- break;
|
|
|
|
|
|
+ _regexes[regExpLiteral.Raw] = regex;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ regExpLiteral.AssociatedData = regex;
|
|
|
|
+ }
|
|
}
|
|
}
|
|
- case Nodes.Literal:
|
|
|
|
- node.AssociatedData = JintLiteralExpression.ConvertToJsValue((Literal) node);
|
|
|
|
|
|
+
|
|
break;
|
|
break;
|
|
|
|
+
|
|
case Nodes.MemberExpression:
|
|
case Nodes.MemberExpression:
|
|
node.AssociatedData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true);
|
|
node.AssociatedData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true);
|
|
break;
|
|
break;
|
|
|
|
+
|
|
case Nodes.ArrowFunctionExpression:
|
|
case Nodes.ArrowFunctionExpression:
|
|
case Nodes.FunctionDeclaration:
|
|
case Nodes.FunctionDeclaration:
|
|
case Nodes.FunctionExpression:
|
|
case Nodes.FunctionExpression:
|
|
var function = (IFunction) node;
|
|
var function = (IFunction) node;
|
|
node.AssociatedData = JintFunctionDefinition.BuildState(function);
|
|
node.AssociatedData = JintFunctionDefinition.BuildState(function);
|
|
break;
|
|
break;
|
|
|
|
+
|
|
case Nodes.Program:
|
|
case Nodes.Program:
|
|
node.AssociatedData = new CachedHoistingScope((Program) node);
|
|
node.AssociatedData = new CachedHoistingScope((Program) node);
|
|
break;
|
|
break;
|
|
|
|
+
|
|
|
|
+ case Nodes.UnaryExpression:
|
|
|
|
+ node.AssociatedData = JintUnaryExpression.BuildConstantExpression((UnaryExpression) node);
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Nodes.BinaryExpression:
|
|
|
|
+ var binaryExpression = (BinaryExpression) node;
|
|
|
|
+ if (_options.FoldConstants
|
|
|
|
+ && binaryExpression.Operator != BinaryOperator.InstanceOf
|
|
|
|
+ && binaryExpression.Operator != BinaryOperator.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.AssociatedData = new JintConstantExpression(binaryExpression, (JsValue) result.EvaluateWithoutNodeTracking(context));
|
|
|
|
+ }
|
|
|
|
+ catch
|
|
|
|
+ {
|
|
|
|
+ // probably caused an error and error reporting doesn't work without engine
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case Nodes.ReturnStatement:
|
|
|
|
+ var returnStatement = (ReturnStatement) node;
|
|
|
|
+ if (returnStatement.Argument is Literal returnedLiteral)
|
|
|
|
+ {
|
|
|
|
+ var returnValue = JintLiteralExpression.ConvertToJsValue(returnedLiteral);
|
|
|
|
+ if (returnValue is not null)
|
|
|
|
+ {
|
|
|
|
+ node.AssociatedData = new ConstantStatement(returnStatement, CompletionType.Return, returnValue);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|