Engine.Ast.cs 9.3 KB

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