Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions Compilation/EmittedRuntime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
259 changes: 259 additions & 0 deletions Compilation/Emitters/Modules/AssertModuleEmitter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
using System.Reflection.Emit;
using SharpTS.Parsing;

namespace SharpTS.Compilation.Emitters.Modules;

/// <summary>
/// Emits IL code for the Node.js 'assert' module.
/// </summary>
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<string> GetExportedMembers() => _exportedMembers;

public bool TryEmitMethodCall(IEmitterContext emitter, string methodName, List<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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<Expr> 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);
}
}
}
Loading