Engine.Ast.cs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. using Jint.Native;
  2. using Jint.Runtime;
  3. using Jint.Runtime.Interpreter;
  4. using Jint.Runtime.Interpreter.Expressions;
  5. using Jint.Runtime.Interpreter.Statements;
  6. using Environment = Jint.Runtime.Environments.Environment;
  7. namespace Jint;
  8. public partial class Engine
  9. {
  10. /// <summary>
  11. /// Prepares a script for the engine that includes static analysis data to speed up execution during run-time.
  12. /// </summary>
  13. /// <remarks>
  14. /// Returned instance is reusable and thread-safe. You should prepare scripts only once and then reuse them.
  15. /// </remarks>
  16. public static Prepared<Script> PrepareScript(string code, string? source = null, bool strict = false, ScriptPreparationOptions? options = null)
  17. {
  18. options ??= ScriptPreparationOptions.Default;
  19. var astAnalyzer = new AstAnalyzer(options);
  20. var parserOptions = options.GetParserOptions();
  21. var parser = new Parser(parserOptions with { OnNode = astAnalyzer.NodeVisitor });
  22. try
  23. {
  24. var preparedScript = parser.ParseScript(code, source, strict);
  25. return new Prepared<Script>(preparedScript, parserOptions);
  26. }
  27. catch (Exception e)
  28. {
  29. throw new ScriptPreparationException("Could not prepare script: " + e.Message, e);
  30. }
  31. }
  32. /// <summary>
  33. /// Prepares a module for the engine that includes static analysis data to speed up execution during run-time.
  34. /// </summary>
  35. /// <remarks>
  36. /// Returned instance is reusable and thread-safe. You should prepare modules only once and then reuse them.
  37. /// </remarks>
  38. public static Prepared<Module> PrepareModule(string code, string? source = null, ModulePreparationOptions? options = null)
  39. {
  40. options ??= ModulePreparationOptions.Default;
  41. var astAnalyzer = new AstAnalyzer(options);
  42. var parserOptions = options.GetParserOptions();
  43. var parser = new Parser(parserOptions with { OnNode = astAnalyzer.NodeVisitor });
  44. try
  45. {
  46. var preparedModule = parser.ParseModule(code, source);
  47. return new Prepared<Module>(preparedModule, parserOptions);
  48. }
  49. catch (Exception e)
  50. {
  51. throw new ScriptPreparationException("Could not prepare script: " + e.Message, e);
  52. }
  53. }
  54. private sealed class AstAnalyzer
  55. {
  56. private readonly IPreparationOptions<IParsingOptions> _preparationOptions;
  57. private readonly Dictionary<string, Environment.BindingName> _bindingNames = new(StringComparer.Ordinal);
  58. public AstAnalyzer(IPreparationOptions<IParsingOptions> preparationOptions)
  59. {
  60. _preparationOptions = preparationOptions;
  61. }
  62. public void NodeVisitor(Node node, OnNodeContext _)
  63. {
  64. switch (node.Type)
  65. {
  66. case NodeType.Identifier:
  67. var identifier = (Identifier) node;
  68. var name = identifier.Name;
  69. if (!_bindingNames.TryGetValue(name, out var bindingName))
  70. {
  71. _bindingNames[name] = bindingName = new Environment.BindingName(JsString.CachedCreate(name));
  72. }
  73. node.UserData = new JintIdentifierExpression(identifier, bindingName);
  74. break;
  75. case NodeType.Literal:
  76. var literal = (Literal) node;
  77. var constantValue = JintLiteralExpression.ConvertToJsValue(literal);
  78. node.UserData = constantValue is not null ? new JintConstantExpression(literal, constantValue) : null;
  79. break;
  80. case NodeType.MemberExpression:
  81. node.UserData = JintMemberExpression.InitializeDeterminedProperty((MemberExpression) node, cache: true);
  82. break;
  83. case NodeType.ArrowFunctionExpression:
  84. case NodeType.FunctionDeclaration:
  85. case NodeType.FunctionExpression:
  86. node.UserData = JintFunctionDefinition.BuildState((IFunction) node);
  87. break;
  88. case NodeType.Program:
  89. node.UserData = new CachedHoistingScope((Program) node);
  90. break;
  91. case NodeType.UnaryExpression:
  92. node.UserData = JintUnaryExpression.BuildConstantExpression((NonUpdateUnaryExpression) node);
  93. break;
  94. case NodeType.BinaryExpression:
  95. var binaryExpression = (NonLogicalBinaryExpression) node;
  96. if (_preparationOptions.FoldConstants
  97. && binaryExpression.Operator != Operator.InstanceOf
  98. && binaryExpression.Operator != Operator.In
  99. && binaryExpression is { Left: Literal leftLiteral, Right: Literal rightLiteral })
  100. {
  101. var left = JintLiteralExpression.ConvertToJsValue(leftLiteral);
  102. var right = JintLiteralExpression.ConvertToJsValue(rightLiteral);
  103. if (left is not null && right is not null)
  104. {
  105. // we have fixed result
  106. try
  107. {
  108. var result = JintBinaryExpression.Build(binaryExpression);
  109. var context = new EvaluationContext();
  110. node.UserData = new JintConstantExpression(binaryExpression, (JsValue) result.EvaluateWithoutNodeTracking(context));
  111. }
  112. catch
  113. {
  114. // probably caused an error and error reporting doesn't work without engine
  115. }
  116. }
  117. }
  118. break;
  119. case NodeType.ReturnStatement:
  120. var returnStatement = (ReturnStatement) node;
  121. if (returnStatement.Argument is Literal returnedLiteral)
  122. {
  123. var returnValue = JintLiteralExpression.ConvertToJsValue(returnedLiteral);
  124. if (returnValue is not null)
  125. {
  126. node.UserData = new ConstantStatement(returnStatement, CompletionType.Return, returnValue);
  127. }
  128. }
  129. break;
  130. case NodeType.BlockStatement:
  131. node.UserData = JintBlockStatement.BuildState((BlockStatement) node);
  132. break;
  133. }
  134. }
  135. }
  136. }
  137. internal sealed class CachedHoistingScope
  138. {
  139. public CachedHoistingScope(Program program)
  140. {
  141. Scope = HoistingScope.GetProgramLevelDeclarations(program);
  142. VarNames = [];
  143. GatherVarNames(Scope, VarNames);
  144. LexNames = DeclarationCacheBuilder.Build(Scope._lexicalDeclarations);
  145. }
  146. internal static void GatherVarNames(HoistingScope scope, List<Key> boundNames)
  147. {
  148. var varDeclarations = scope._variablesDeclarations;
  149. if (varDeclarations != null)
  150. {
  151. for (var i = 0; i < varDeclarations.Count; i++)
  152. {
  153. var d = varDeclarations[i];
  154. d.GetBoundNames(boundNames);
  155. }
  156. }
  157. }
  158. public HoistingScope Scope { get; }
  159. public List<Key> VarNames { get; }
  160. public DeclarationCache LexNames { get; }
  161. }
  162. internal static class AstPreparationExtensions
  163. {
  164. internal static HoistingScope GetHoistingScope(this Program program)
  165. {
  166. return program.UserData is CachedHoistingScope cached ? cached.Scope : HoistingScope.GetProgramLevelDeclarations(program);
  167. }
  168. internal static List<Key> GetVarNames(this Program program, HoistingScope hoistingScope)
  169. {
  170. List<Key> boundNames;
  171. if (program.UserData is CachedHoistingScope cached)
  172. {
  173. boundNames = cached.VarNames;
  174. }
  175. else
  176. {
  177. boundNames = [];
  178. CachedHoistingScope.GatherVarNames(hoistingScope, boundNames);
  179. }
  180. return boundNames;
  181. }
  182. internal static List<ScopedDeclaration> GetLexNames(this Program program, HoistingScope hoistingScope)
  183. {
  184. DeclarationCache cache;
  185. if (program.UserData is CachedHoistingScope cached)
  186. {
  187. cache = cached.LexNames;
  188. }
  189. else
  190. {
  191. cache = DeclarationCacheBuilder.Build(hoistingScope._lexicalDeclarations);
  192. }
  193. return cache.Declarations;
  194. }
  195. }