2025-01-22 16:18:30 +01:00

127 lines
3.7 KiB
Haxe

package aura.utils;
import haxe.Exception;
import haxe.PosInfos;
import haxe.exceptions.PosException;
import haxe.macro.Context;
import haxe.macro.Expr;
using haxe.macro.ExprTools;
class Assert {
/**
Checks whether the given expression evaluates to true. If this is not
the case, a `AuraAssertionException` with additional information is
thrown.
The assert level describes the severity of the assertion. If the
severity is lower than the level stored in the `AURA_ASSERT_LEVEL` flag,
the assertion is omitted from the code so that it doesn't decrease the
runtime performance.
@param level The severity of this assertion.
@param condition The conditional expression to test.
@param message Optional message to display when the assertion fails.
**/
public static macro function assert(level: ExprOf<AssertLevel>, condition: ExprOf<Bool>, ?message: ExprOf<String>): Expr {
final levelVal: AssertLevel = AssertLevel.fromExpr(level);
final assertThreshold = AssertLevel.fromString(Context.definedValue("AURA_ASSERT_LEVEL"));
if (levelVal < assertThreshold) {
return macro {};
}
return macro {
if (!$condition) {
#if AURA_ASSERT_QUIT kha.System.stop(); #end
@:pos(condition.pos)
final exception = new aura.utils.Assert.AuraAssertionException($v{condition.toString()}, ${message});
#if AURA_ASSERT_START_DEBUGGER
@:privateAccess aura.utils.Assert.logError(exception.details());
@:privateAccess aura.utils.Assert.logError("An assertion error was triggered, starting debugger...");
aura.utils.Debug.startDebugger();
#else
@:pos(condition.pos)
@:privateAccess aura.utils.Assert.throwAssertionError(exception);
#end
}
};
}
/**
Helper function to prevent Haxe "bug" that actually throws an error
even when using `macro throw` (inlining this method also does not work).
**/
static function throwAssertionError(exp: AuraAssertionException, ?pos: PosInfos) {
throw exp;
}
static function logError(str: String, ?infos: PosInfos) {
#if sys
Sys.stderr().writeString(str + "\n");
#elseif kha_krom
Krom.log(str + "\n");
#elseif kha_js
js.html.Console.error(str);
#else
haxe.Log.trace(str, infos);
#end
}
}
/**
Exception that is thrown when an assertion fails.
@see `Assert`
**/
class AuraAssertionException extends PosException {
/**
@param exprString The string representation of the failed assert condition.
@param message Custom error message, use `null` to omit printing the message.
**/
public function new(exprString: String, message: Null<String>, ?previous: Exception, ?pos: Null<PosInfos>) {
final optMsg = message != null ? '\n\tMessage: $message' : "";
super('\n[Aura] Failed assertion:$optMsg\n\tExpression: ($exprString)', previous, pos);
}
}
enum abstract AssertLevel(Int) from Int to Int {
var Debug: AssertLevel;
var Warning: AssertLevel;
var Error: AssertLevel;
var Critical: AssertLevel;
// Don't use this level in assert() calls!
var NoAssertions: AssertLevel;
public static function fromExpr(e: ExprOf<AssertLevel>): AssertLevel {
switch (e.expr) {
case EConst(CIdent(v)): return fromString(v);
default: throw new Exception('Unsupported expression: $e');
};
}
/**
Converts a string into an `AssertLevel`, the string must be spelled
exactly as the assert level. `null` defaults to `AssertLevel.Critical`.
**/
public static function fromString(s: String): AssertLevel {
return switch (s) {
case "Debug": Debug;
case "Warning": Warning;
case "Error": Error;
case "Critical" | null: Critical;
case "NoAssertions": NoAssertions;
default: throw 'Could not convert "$s" to AssertLevel';
}
}
@:op(A < B) static function lt(a:AssertLevel, b:AssertLevel):Bool;
@:op(A > B) static function gt(a:AssertLevel, b:AssertLevel):Bool;
}