using System.Runtime.CompilerServices;
using Acornima;
using Acornima.Ast;
using Jint;
using Jint.Native;
namespace XFESystem.Browser;
/// <summary>
/// Fully validates syntax, while leaving Jint's function/declaration analysis on
/// its normal first-use path. Large packages contain many unused function bodies.
/// The AST and analysis contain no realm; interpreter handlers remain engine owned.
/// </summary>
internal static class PackagedScriptParser
{
// Obtain the pinned engine's own grammar and regexp adapter. Prepared scripts
// otherwise default to compiled regex and a ten-second regexp timeout.
private static readonly ParserOptions options = Engine.PrepareScript(string.Empty,
options: new ScriptPreparationOptions
{
FoldConstants = false,
ParsingOptions = new() { CompileRegex = false, RegexTimeout = TimeSpan.FromMilliseconds(100) }
}).ParserOptions! with { OnNode = null };
internal static Prepared<Script> Parse(string source, string key, int maximumNodes, out int nodes)
{
var preparation = new SyntaxPreparation(maximumNodes);
var parser = new Parser(options with { OnNode = preparation.Visit });
var script = parser.ParseScript(source, sourceFile: key, strict: false);
nodes = preparation.Count;
return PreparedFactory<Script>.Create(script, options);
}
// Jint 4.11.0 exposes evaluation of Prepared<Script>, but its AST wrapper
// constructor is internal. This exact signature is pinned by Packages.lock;
// UnsafeAccessor emits a direct AOT call, with no reflection or layout cast.
private static class PreparedFactory<TProgram> where TProgram : Acornima.Ast.Program
{
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
internal static extern Prepared<TProgram> Create(TProgram program, ParserOptions parserOptions);
}
// The same immutable identifier memento used by Jint's AstAnalyzer. Only its
// name/key is shared; scope resolution and mutable inline caches remain on
// each engine's interpreter nodes. This signature also pins Jint 4.11.0.
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
[return: UnsafeAccessorType("Jint.Runtime.Environments.Environment+BindingName, Jint")]
private static extern object CreateBindingName(string name);
private sealed class SyntaxPreparation(int maximum)
{
private readonly Dictionary<string, object> bindingNames = new(StringComparer.Ordinal);
private readonly Dictionary<string, JsString> propertyNames = new(StringComparer.Ordinal);
internal int Count { get; private set; }
internal void Visit(Node node, in OnNodeContext context)
{
// An over-budget script is still parsed and executed, but not cached.
if (Count > maximum) return;
Count++;
if (node is Identifier identifier)
{
if (!bindingNames.TryGetValue(identifier.Name, out var binding))
bindingNames.Add(identifier.Name, binding = CreateBindingName(identifier.Name));
node.UserData = binding;
}
else if (node is MemberExpression { Computed: false, Property: Identifier property })
{
if (!propertyNames.TryGetValue(property.Name, out var key))
propertyNames.Add(property.Name, key = new JsString(property.Name));
node.UserData = key;
}
}
}
}
using System.Runtime.CompilerServices;
using Acornima;
using Acornima.Ast;
using Jint;
using Jint.Native;
namespace XFESystem.Browser;
/// <summary>
/// Fully validates syntax, while leaving Jint's function/declaration analysis on
/// its normal first-use path. Large packages contain many unused function bodies.
/// The AST and analysis contain no realm; interpreter handlers remain engine owned.
/// </summary>
internal static class PackagedScriptParser
{
// Obtain the pinned engine's own grammar and regexp adapter. Prepared scripts
// otherwise default to compiled regex and a ten-second regexp timeout.
private static readonly ParserOptions options = Engine.PrepareScript(string.Empty,
options: new ScriptPreparationOptions
{
FoldConstants = false,
ParsingOptions = new() { CompileRegex = false, RegexTimeout = TimeSpan.FromMilliseconds(100) }
}).ParserOptions! with { OnNode = null };
internal static Prepared<Script> Parse(string source, string key, int maximumNodes, out int nodes)
{
var preparation = new SyntaxPreparation(maximumNodes);
var parser = new Parser(options with { OnNode = preparation.Visit });
var script = parser.ParseScript(source, sourceFile: key, strict: false);
nodes = preparation.Count;
return PreparedFactory<Script>.Create(script, options);
}
// Jint 4.11.0 exposes evaluation of Prepared<Script>, but its AST wrapper
// constructor is internal. This exact signature is pinned by Packages.lock;
// UnsafeAccessor emits a direct AOT call, with no reflection or layout cast.
private static class PreparedFactory<TProgram> where TProgram : Acornima.Ast.Program
{
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
internal static extern Prepared<TProgram> Create(TProgram program, ParserOptions parserOptions);
}
// The same immutable identifier memento used by Jint's AstAnalyzer. Only its
// name/key is shared; scope resolution and mutable inline caches remain on
// each engine's interpreter nodes. This signature also pins Jint 4.11.0.
[UnsafeAccessor(UnsafeAccessorKind.Constructor)]
[return: UnsafeAccessorType("Jint.Runtime.Environments.Environment+BindingName, Jint")]
private static extern object CreateBindingName(string name);
private sealed class SyntaxPreparation(int maximum)
{
private readonly Dictionary<string, object> bindingNames = new(StringComparer.Ordinal);
private readonly Dictionary<string, JsString> propertyNames = new(StringComparer.Ordinal);
internal int Count { get; private set; }
internal void Visit(Node node, in OnNodeContext context)
{
// An over-budget script is still parsed and executed, but not cached.
if (Count > maximum) return;
Count++;
if (node is Identifier identifier)
{
if (!bindingNames.TryGetValue(identifier.Name, out var binding))
bindingNames.Add(identifier.Name, binding = CreateBindingName(identifier.Name));
node.UserData = binding;
}
else if (node is MemberExpression { Computed: false, Property: Identifier property })
{
if (!propertyNames.TryGetValue(property.Name, out var key))
propertyNames.Add(property.Name, key = new JsString(property.Name));
node.UserData = key;
}
}
}
}