diff --git a/Compilation/EmittedRuntime.cs b/Compilation/EmittedRuntime.cs
index c75dfd6..8a75e98 100644
--- a/Compilation/EmittedRuntime.cs
+++ b/Compilation/EmittedRuntime.cs
@@ -401,6 +401,30 @@ public class EmittedRuntime
// Process module methods
public MethodBuilder ProcessGetEnv { get; set; } = null!;
public MethodBuilder ProcessGetArgv { get; set; } = null!;
+ public MethodBuilder ProcessHrtime { get; set; } = null!;
+ public MethodBuilder ProcessUptime { get; set; } = null!;
+ public MethodBuilder ProcessMemoryUsage { get; set; } = null!;
+
+ // Querystring module methods
+ public MethodBuilder QuerystringParse { get; set; } = null!;
+ public MethodBuilder QuerystringStringify { get; set; } = null!;
+
+ // Assert module methods
+ public MethodBuilder AssertOk { get; set; } = null!;
+ public MethodBuilder AssertStrictEqual { get; set; } = null!;
+ public MethodBuilder AssertNotStrictEqual { get; set; } = null!;
+ public MethodBuilder AssertDeepStrictEqual { get; set; } = null!;
+ public MethodBuilder AssertNotDeepStrictEqual { get; set; } = null!;
+ public MethodBuilder AssertThrows { get; set; } = null!;
+ public MethodBuilder AssertDoesNotThrow { get; set; } = null!;
+ public MethodBuilder AssertFail { get; set; } = null!;
+ public MethodBuilder AssertEqual { get; set; } = null!;
+ public MethodBuilder AssertNotEqual { get; set; } = null!;
+
+ // URL module methods
+ public MethodBuilder UrlParse { get; set; } = null!;
+ public MethodBuilder UrlFormat { get; set; } = null!;
+ public MethodBuilder UrlResolve { get; set; } = null!;
// Built-in module methods (module name -> method name -> MethodBuilder)
// Used for creating TSFunction wrappers when importing named exports
diff --git a/Compilation/Emitters/Modules/AssertModuleEmitter.cs b/Compilation/Emitters/Modules/AssertModuleEmitter.cs
new file mode 100644
index 0000000..8e85a03
--- /dev/null
+++ b/Compilation/Emitters/Modules/AssertModuleEmitter.cs
@@ -0,0 +1,259 @@
+using System.Reflection.Emit;
+using SharpTS.Parsing;
+
+namespace SharpTS.Compilation.Emitters.Modules;
+
+///
+/// Emits IL code for the Node.js 'assert' module.
+///
+public sealed class AssertModuleEmitter : IBuiltInModuleEmitter
+{
+ public string ModuleName => "assert";
+
+ private static readonly string[] _exportedMembers =
+ [
+ "ok", "strictEqual", "notStrictEqual", "deepStrictEqual", "notDeepStrictEqual",
+ "throws", "doesNotThrow", "fail", "equal", "notEqual"
+ ];
+
+ public IReadOnlyList GetExportedMembers() => _exportedMembers;
+
+ public bool TryEmitMethodCall(IEmitterContext emitter, string methodName, List arguments)
+ {
+ return methodName switch
+ {
+ "ok" => EmitOk(emitter, arguments),
+ "strictEqual" => EmitStrictEqual(emitter, arguments),
+ "notStrictEqual" => EmitNotStrictEqual(emitter, arguments),
+ "deepStrictEqual" => EmitDeepStrictEqual(emitter, arguments),
+ "notDeepStrictEqual" => EmitNotDeepStrictEqual(emitter, arguments),
+ "throws" => EmitThrows(emitter, arguments),
+ "doesNotThrow" => EmitDoesNotThrow(emitter, arguments),
+ "fail" => EmitFail(emitter, arguments),
+ "equal" => EmitEqual(emitter, arguments),
+ "notEqual" => EmitNotEqual(emitter, arguments),
+ _ => false
+ };
+ }
+
+ public bool TryEmitPropertyGet(IEmitterContext emitter, string propertyName)
+ {
+ // assert has no properties
+ return false;
+ }
+
+ private static bool EmitOk(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: AssertOk(object? value, object? message)
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 1)
+ {
+ emitter.EmitExpression(arguments[1]);
+ emitter.EmitBoxIfNeeded(arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertOk);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitStrictEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: AssertStrictEqual(object? actual, object? expected, object? message)
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertStrictEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitNotStrictEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertNotStrictEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitDeepStrictEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertDeepStrictEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitNotDeepStrictEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertNotDeepStrictEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitThrows(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: AssertThrows(object? fn, object? message)
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 1)
+ {
+ emitter.EmitExpression(arguments[1]);
+ emitter.EmitBoxIfNeeded(arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertThrows);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitDoesNotThrow(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 1)
+ {
+ emitter.EmitExpression(arguments[1]);
+ emitter.EmitBoxIfNeeded(arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertDoesNotThrow);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitFail(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: AssertFail(object? message)
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertFail);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static bool EmitNotEqual(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ EmitThreeArgs(emitter, arguments);
+ il.Emit(OpCodes.Call, ctx.Runtime!.AssertNotEqual);
+ il.Emit(OpCodes.Ldnull); // Push null for expression statement Pop
+ return true;
+ }
+
+ private static void EmitThreeArgs(IEmitterContext emitter, List arguments)
+ {
+ var il = emitter.Context.IL;
+
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 1)
+ {
+ emitter.EmitExpression(arguments[1]);
+ emitter.EmitBoxIfNeeded(arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 2)
+ {
+ emitter.EmitExpression(arguments[2]);
+ emitter.EmitBoxIfNeeded(arguments[2]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+ }
+}
diff --git a/Compilation/Emitters/Modules/QuerystringModuleEmitter.cs b/Compilation/Emitters/Modules/QuerystringModuleEmitter.cs
new file mode 100644
index 0000000..be8c652
--- /dev/null
+++ b/Compilation/Emitters/Modules/QuerystringModuleEmitter.cs
@@ -0,0 +1,171 @@
+using System.Reflection.Emit;
+using SharpTS.Parsing;
+
+namespace SharpTS.Compilation.Emitters.Modules;
+
+///
+/// Emits IL code for the Node.js 'querystring' module.
+///
+public sealed class QuerystringModuleEmitter : IBuiltInModuleEmitter
+{
+ public string ModuleName => "querystring";
+
+ private static readonly string[] _exportedMembers =
+ [
+ "parse", "stringify", "escape", "unescape", "decode", "encode"
+ ];
+
+ public IReadOnlyList GetExportedMembers() => _exportedMembers;
+
+ public bool TryEmitMethodCall(IEmitterContext emitter, string methodName, List arguments)
+ {
+ return methodName switch
+ {
+ "parse" or "decode" => EmitParse(emitter, arguments),
+ "stringify" or "encode" => EmitStringify(emitter, arguments),
+ "escape" => EmitEscape(emitter, arguments),
+ "unescape" => EmitUnescape(emitter, arguments),
+ _ => false
+ };
+ }
+
+ public bool TryEmitPropertyGet(IEmitterContext emitter, string propertyName)
+ {
+ // querystring has no properties
+ return false;
+ }
+
+ private static bool EmitParse(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: QuerystringParse(string str, string? sep, string? eq)
+ // Argument 0: str
+ if (arguments.Count > 0)
+ {
+ EmitToString(emitter, arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "");
+ }
+
+ // Argument 1: sep (default "&")
+ if (arguments.Count > 1)
+ {
+ EmitToString(emitter, arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "&");
+ }
+
+ // Argument 2: eq (default "=")
+ if (arguments.Count > 2)
+ {
+ EmitToString(emitter, arguments[2]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "=");
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.QuerystringParse);
+ return true;
+ }
+
+ private static bool EmitStringify(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: QuerystringStringify(object? obj, string sep, string eq)
+ // Argument 0: obj
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ // Argument 1: sep (default "&")
+ if (arguments.Count > 1)
+ {
+ EmitToString(emitter, arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "&");
+ }
+
+ // Argument 2: eq (default "=")
+ if (arguments.Count > 2)
+ {
+ EmitToString(emitter, arguments[2]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "=");
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.QuerystringStringify);
+ return true;
+ }
+
+ private static bool EmitEscape(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Uri.EscapeDataString(str)
+ if (arguments.Count > 0)
+ {
+ EmitToString(emitter, arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "");
+ }
+
+ il.Emit(OpCodes.Call, typeof(Uri).GetMethod("EscapeDataString", [typeof(string)])!);
+ return true;
+ }
+
+ private static bool EmitUnescape(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Replace '+' with ' ' then Uri.UnescapeDataString(str)
+ if (arguments.Count > 0)
+ {
+ EmitToString(emitter, arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldstr, "");
+ }
+
+ // Call string.Replace('+', ' ')
+ il.Emit(OpCodes.Ldc_I4, '+');
+ il.Emit(OpCodes.Ldc_I4, ' ');
+ il.Emit(OpCodes.Callvirt, ctx.Types.GetMethod(ctx.Types.String, "Replace", ctx.Types.Char, ctx.Types.Char));
+
+ // Call Uri.UnescapeDataString
+ il.Emit(OpCodes.Call, typeof(Uri).GetMethod("UnescapeDataString", [typeof(string)])!);
+ return true;
+ }
+
+ private static void EmitToString(IEmitterContext emitter, Expr expr)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ emitter.EmitExpression(expr);
+ emitter.EmitBoxIfNeeded(expr);
+ il.Emit(OpCodes.Callvirt, ctx.Types.GetMethod(ctx.Types.Object, "ToString"));
+ }
+}
diff --git a/Compilation/Emitters/Modules/UrlModuleEmitter.cs b/Compilation/Emitters/Modules/UrlModuleEmitter.cs
new file mode 100644
index 0000000..2d30b13
--- /dev/null
+++ b/Compilation/Emitters/Modules/UrlModuleEmitter.cs
@@ -0,0 +1,106 @@
+using System.Reflection.Emit;
+using SharpTS.Parsing;
+
+namespace SharpTS.Compilation.Emitters.Modules;
+
+///
+/// Emits IL code for the Node.js 'url' module.
+///
+public sealed class UrlModuleEmitter : IBuiltInModuleEmitter
+{
+ public string ModuleName => "url";
+
+ private static readonly string[] _exportedMembers =
+ [
+ "URL", "URLSearchParams", "parse", "format", "resolve"
+ ];
+
+ public IReadOnlyList GetExportedMembers() => _exportedMembers;
+
+ public bool TryEmitMethodCall(IEmitterContext emitter, string methodName, List arguments)
+ {
+ return methodName switch
+ {
+ "parse" => EmitParse(emitter, arguments),
+ "format" => EmitFormat(emitter, arguments),
+ "resolve" => EmitResolve(emitter, arguments),
+ _ => false
+ };
+ }
+
+ public bool TryEmitPropertyGet(IEmitterContext emitter, string propertyName)
+ {
+ // URL and URLSearchParams are handled as class constructors, not properties
+ // They require special handling in the compiler
+ return false;
+ }
+
+ private static bool EmitParse(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: UrlParse(object? url)
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.UrlParse);
+ return true;
+ }
+
+ private static bool EmitFormat(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.UrlFormat);
+ return true;
+ }
+
+ private static bool EmitResolve(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper: UrlResolve(object? from, object? to)
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ emitter.EmitBoxIfNeeded(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ if (arguments.Count > 1)
+ {
+ emitter.EmitExpression(arguments[1]);
+ emitter.EmitBoxIfNeeded(arguments[1]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+
+ il.Emit(OpCodes.Call, ctx.Runtime!.UrlResolve);
+ return true;
+ }
+}
diff --git a/Compilation/Emitters/ProcessStaticEmitter.cs b/Compilation/Emitters/ProcessStaticEmitter.cs
index a83f62f..55fa3b0 100644
--- a/Compilation/Emitters/ProcessStaticEmitter.cs
+++ b/Compilation/Emitters/ProcessStaticEmitter.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using SharpTS.Parsing;
@@ -41,6 +42,18 @@ public bool TryEmitStaticCall(IEmitterContext emitter, string methodName, List
+ /// Emits IL for process.hrtime(prev?).
+ /// Returns a [seconds, nanoseconds] array.
+ ///
+ private static void EmitHrtime(IEmitterContext emitter, List arguments)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper that handles hrtime logic
+ // The helper takes an optional previous time array
+ if (arguments.Count > 0)
+ {
+ emitter.EmitExpression(arguments[0]);
+ }
+ else
+ {
+ il.Emit(OpCodes.Ldnull);
+ }
+ il.Emit(OpCodes.Call, ctx.Runtime!.ProcessHrtime);
+ }
+
+ ///
+ /// Emits IL for process.uptime().
+ /// Returns the number of seconds the process has been running.
+ ///
+ private static void EmitUptime(IEmitterContext emitter)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper
+ il.Emit(OpCodes.Call, ctx.Runtime!.ProcessUptime);
+ il.Emit(OpCodes.Box, ctx.Types.Double);
+ }
+
+ ///
+ /// Emits IL for process.memoryUsage().
+ /// Returns an object with memory usage information.
+ ///
+ private static void EmitMemoryUsage(IEmitterContext emitter)
+ {
+ var ctx = emitter.Context;
+ var il = ctx.IL;
+
+ // Call runtime helper
+ il.Emit(OpCodes.Call, ctx.Runtime!.ProcessMemoryUsage);
+ }
}
diff --git a/Compilation/ILCompiler.cs b/Compilation/ILCompiler.cs
index 517b752..6444cc3 100644
--- a/Compilation/ILCompiler.cs
+++ b/Compilation/ILCompiler.cs
@@ -457,6 +457,9 @@ private void InitializeTypeEmitterRegistries()
_builtInModuleEmitterRegistry.Register(new PathModuleEmitter());
_builtInModuleEmitterRegistry.Register(new OsModuleEmitter());
_builtInModuleEmitterRegistry.Register(new FsModuleEmitter());
+ _builtInModuleEmitterRegistry.Register(new QuerystringModuleEmitter());
+ _builtInModuleEmitterRegistry.Register(new AssertModuleEmitter());
+ _builtInModuleEmitterRegistry.Register(new UrlModuleEmitter());
}
#endregion
diff --git a/Compilation/RuntimeEmitter.AssertHelpers.cs b/Compilation/RuntimeEmitter.AssertHelpers.cs
new file mode 100644
index 0000000..1c48e54
--- /dev/null
+++ b/Compilation/RuntimeEmitter.AssertHelpers.cs
@@ -0,0 +1,635 @@
+using System.Reflection;
+using System.Reflection.Emit;
+using SharpTS.Runtime.BuiltIns.Modules.Interpreter;
+
+namespace SharpTS.Compilation;
+
+public partial class RuntimeEmitter
+{
+ ///
+ /// Emits assert module helper methods.
+ ///
+ private void EmitAssertMethods(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ EmitAssertOk(typeBuilder, runtime);
+ EmitAssertStrictEqual(typeBuilder, runtime);
+ EmitAssertNotStrictEqual(typeBuilder, runtime);
+ EmitAssertDeepStrictEqual(typeBuilder, runtime);
+ EmitAssertNotDeepStrictEqual(typeBuilder, runtime);
+ EmitAssertThrows(typeBuilder, runtime);
+ EmitAssertDoesNotThrow(typeBuilder, runtime);
+ EmitAssertFail(typeBuilder, runtime);
+ EmitAssertEqual(typeBuilder, runtime);
+ EmitAssertNotEqual(typeBuilder, runtime);
+ EmitAssertMethodWrappers(typeBuilder, runtime);
+ }
+
+ ///
+ /// Emits wrapper methods for assert functions that can be used as first-class values.
+ ///
+ private void EmitAssertMethodWrappers(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ // ok(value, message?) - 2 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "ok", 2, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, runtime.AssertOk);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // strictEqual(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "strictEqual", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertStrictEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // notStrictEqual(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "notStrictEqual", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertNotStrictEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // deepStrictEqual(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "deepStrictEqual", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertDeepStrictEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // notDeepStrictEqual(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "notDeepStrictEqual", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertNotDeepStrictEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // throws(fn, message?) - 2 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "throws", 2, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, runtime.AssertThrows);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // doesNotThrow(fn, message?) - 2 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "doesNotThrow", 2, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, runtime.AssertDoesNotThrow);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // fail(message?) - 1 param
+ EmitAssertWrapperSimple(typeBuilder, runtime, "fail", 1, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Call, runtime.AssertFail);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // equal(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "equal", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+
+ // notEqual(actual, expected, message?) - 3 params
+ EmitAssertWrapperSimple(typeBuilder, runtime, "notEqual", 3, il =>
+ {
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, runtime.AssertNotEqual);
+ il.Emit(OpCodes.Ldnull);
+ });
+ }
+
+ private void EmitAssertWrapperSimple(
+ TypeBuilder typeBuilder,
+ EmittedRuntime runtime,
+ string methodName,
+ int paramCount,
+ Action emitCall)
+ {
+ var paramTypes = new Type[paramCount];
+ for (int i = 0; i < paramCount; i++)
+ paramTypes[i] = _types.Object;
+
+ var method = typeBuilder.DefineMethod(
+ $"Assert_{methodName}_Wrapper",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Object,
+ paramTypes
+ );
+
+ var il = method.GetILGenerator();
+ emitCall(il);
+ il.Emit(OpCodes.Ret);
+
+ runtime.RegisterBuiltInModuleMethod("assert", methodName, method);
+ }
+
+ ///
+ /// Emits: public static void AssertOk(object? value, object? message)
+ ///
+ private void EmitAssertOk(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertOk",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object]
+ );
+ runtime.AssertOk = method;
+
+ var il = method.GetILGenerator();
+
+ // Call AssertHelpers.Ok(value, message) which handles the message correctly
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("Ok", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertStrictEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertStrictEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertStrictEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertStrictEqual = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("StrictEqual", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertNotStrictEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertNotStrictEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertNotStrictEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertNotStrictEqual = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("NotStrictEqual", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertDeepStrictEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertDeepStrictEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertDeepStrictEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertDeepStrictEqual = method;
+
+ var il = method.GetILGenerator();
+
+ // For now, call the interpreter version via reflection
+ // This is a placeholder - full deep equality comparison would be complex to emit in IL
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("DeepStrictEqual", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertNotDeepStrictEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertNotDeepStrictEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertNotDeepStrictEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertNotDeepStrictEqual = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("NotDeepStrictEqual", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertThrows(object? fn, object? message)
+ ///
+ private void EmitAssertThrows(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertThrows",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object]
+ );
+ runtime.AssertThrows = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("Throws", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertDoesNotThrow(object? fn, object? message)
+ ///
+ private void EmitAssertDoesNotThrow(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertDoesNotThrow",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object]
+ );
+ runtime.AssertDoesNotThrow = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("DoesNotThrow", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertFail(object? message)
+ ///
+ private void EmitAssertFail(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertFail",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object]
+ );
+ runtime.AssertFail = method;
+
+ var il = method.GetILGenerator();
+
+ // Get message or default
+ var notNull = il.DefineLabel();
+ var afterMsg = il.DefineLabel();
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Brtrue, notNull);
+ il.Emit(OpCodes.Ldstr, "Failed");
+ il.Emit(OpCodes.Br, afterMsg);
+ il.MarkLabel(notNull);
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Callvirt, _types.GetMethodNoParams(_types.Object, "ToString"));
+ il.MarkLabel(afterMsg);
+
+ // throw new AssertionError(message)
+ il.Emit(OpCodes.Ldnull); // actual
+ il.Emit(OpCodes.Ldnull); // expected
+ il.Emit(OpCodes.Ldstr, "fail"); // operator
+ il.Emit(OpCodes.Newobj, typeof(AssertionError).GetConstructor([typeof(string), typeof(object), typeof(object), typeof(string)])!);
+ il.Emit(OpCodes.Throw);
+ }
+
+ ///
+ /// Emits: public static void AssertEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertEqual = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("Equal", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ ///
+ /// Emits: public static void AssertNotEqual(object? actual, object? expected, object? message)
+ ///
+ private void EmitAssertNotEqual(TypeBuilder typeBuilder, EmittedRuntime runtime)
+ {
+ var method = typeBuilder.DefineMethod(
+ "AssertNotEqual",
+ MethodAttributes.Public | MethodAttributes.Static,
+ _types.Void,
+ [_types.Object, _types.Object, _types.Object]
+ );
+ runtime.AssertNotEqual = method;
+
+ var il = method.GetILGenerator();
+
+ il.Emit(OpCodes.Ldarg_0);
+ il.Emit(OpCodes.Ldarg_1);
+ il.Emit(OpCodes.Ldarg_2);
+ il.Emit(OpCodes.Call, typeof(AssertHelpers).GetMethod("NotEqual", BindingFlags.Public | BindingFlags.Static)!);
+ il.Emit(OpCodes.Ret);
+ }
+
+ private void EmitThrowAssertionError(ILGenerator il, string defaultMessage, string @operator)
+ {
+ // Check if custom message provided (arg1 or arg2 depending on method)
+ il.Emit(OpCodes.Ldstr, defaultMessage);
+ il.Emit(OpCodes.Ldnull); // actual
+ il.Emit(OpCodes.Ldnull); // expected
+ il.Emit(OpCodes.Ldstr, @operator);
+ il.Emit(OpCodes.Newobj, typeof(AssertionError).GetConstructor([typeof(string), typeof(object), typeof(object), typeof(string)])!);
+ il.Emit(OpCodes.Throw);
+ }
+}
+
+///
+/// Static helper methods for assert module in compiled mode.
+/// These are called from emitted IL for complex assertion logic.
+///
+public static class AssertHelpers
+{
+ public static void Ok(object? value, object? message)
+ {
+ if (!IsTruthy(value))
+ {
+ var msg = message?.ToString() ?? "The expression evaluated to a falsy value";
+ throw new AssertionError(msg, value, true, "ok");
+ }
+ }
+
+ public static void StrictEqual(object? actual, object? expected, object? message)
+ {
+ if (!StrictEquals(actual, expected))
+ {
+ var msg = message?.ToString() ?? "Expected values to be strictly equal";
+ throw new AssertionError(msg, actual, expected, "strictEqual");
+ }
+ }
+
+ public static void NotStrictEqual(object? actual, object? expected, object? message)
+ {
+ if (StrictEquals(actual, expected))
+ {
+ var msg = message?.ToString() ?? "Expected values not to be strictly equal";
+ throw new AssertionError(msg, actual, expected, "notStrictEqual");
+ }
+ }
+
+ public static void DeepStrictEqual(object? actual, object? expected, object? message)
+ {
+ if (!DeepEquals(actual, expected))
+ {
+ var msg = message?.ToString() ?? $"Expected values to be deeply equal";
+ throw new AssertionError(msg, actual, expected, "deepStrictEqual");
+ }
+ }
+
+ public static void NotDeepStrictEqual(object? actual, object? expected, object? message)
+ {
+ if (DeepEquals(actual, expected))
+ {
+ var msg = message?.ToString() ?? $"Expected values not to be deeply equal";
+ throw new AssertionError(msg, actual, expected, "notDeepStrictEqual");
+ }
+ }
+
+ public static void Throws(object? fn, object? message)
+ {
+ if (fn == null)
+ {
+ throw new AssertionError(message?.ToString() ?? "Missing function to test", null, null, "throws");
+ }
+
+ bool threw = false;
+ try
+ {
+ // Try to invoke the function
+ if (fn is Delegate del)
+ {
+ del.DynamicInvoke([]);
+ }
+ else
+ {
+ // Try via reflection for TSFunction
+ var invokeMethod = fn.GetType().GetMethod("Invoke");
+ if (invokeMethod != null)
+ {
+ invokeMethod.Invoke(fn, [Array.Empty