/* * Copyright 2025 Magnus Madsen * Copyright 2025 Daniel Neo López Martínez * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// Effect for performing assertions during program execution, and its functions.///pubmod Assert {use Sys.Consoleuse RichString.{blue, red, text, yellow}import java.lang.AssertionError////// Effect for performing assertions during program execution.///pubeffAssert {////// Asserts that the given condition `cond` is `true` with a message `msg`.///defassert(cond: Bool, msg: RichString): Unit }////// Asserts that the given condition `cond` is `true`.///pubdefassertTrue(cond: Bool): Unit \ Assert =letm = red("Assertion failed: ") +text("expected ") +blue("true");Assert.assert(cond, m)////// Asserts that the given condition `cond` is `true` with the given message `msg`.///pubdefassertTrueWithMsg(cond: Bool, msg: String): Unit \ Assert =Assert.assert(cond, text(msg))////// Asserts that the given condition `cond` is `false`.///pubdefassertFalse(cond: Bool): Unit \ Assert =letm = red("Assertion failed: ") +text("expected ") +blue("false");Assert.assert(notcond, m)////// Asserts that the given condition `cond` is `false` with the given message `msg`.///pubdefassertFalseWithMsg(cond: Bool, msg: String): Unit \ Assert =Assert.assert(notcond, text(msg))////// Asserts that the given values `expected` and `actual` are equal.///pubdefassertEq(expected: {expected = a}, actual: a): Unit \ AssertwithEq[a], ToString[a] =letc = actual==expected#expected;letm = red("Assertion failed: ") +text("expected ") +blue("`${expected#expected}`") +text(", but got ") +yellow("`${actual}`");Assert.assert(c, m)////// Asserts that the given values `expected` and `actual` are equal with the given message `msg`.///pubdefassertEqWithMsg(expected: {expected = a}, actual: a, msg: String): Unit \ AssertwithEq[a] =letc = actual==expected#expected;letm = text(msg);Assert.assert(c, m)////// Asserts that the given values `unexpected` and `actual` are not equal.///pubdefassertNeq(unexpected: {unexpected = a}, actual: a): Unit \ AssertwithEq[a], ToString[a] =letc = actual!=unexpected#unexpected;letm = red("Assertion failed: ") +text("expected values to be different, but both were ") +yellow("`${actual}`");Assert.assert(c, m)////// Asserts that the given values `unexpected` and `actual` are not equal with the given message `msg`.///pubdefassertNeqWithMsg(unexpected: {unexpected = a}, actual: a, msg: String): Unit \ AssertwithEq[a] =letc = actual!=unexpected#unexpected;letm = text(msg);Assert.assert(c, m)////// Asserts that the given Option `o` is `Some(v)`.///pubdefassertSome(o: Option[a]): Unit \ Assert =letc = Option.nonEmpty(o);letm = red("Assertion failed: ") +text("expected ") +blue("Some(_)") +text(", but got ") +yellow("None");Assert.assert(c, m)////// Asserts that the given Option `o` is `None`.///pubdefassertNone(o: Option[a]): Unit \ Assert =letc = Option.isEmpty(o);letm = red("Assertion failed: ") +text("expected ") +blue("None") +text(", but got ") +yellow("Some(_)");Assert.assert(c, m)////// Asserts that the given Result `r` is `Ok(v)`.///pubdefassertOk(r: Result[e, a]): Unit \ Assert =letc = Result.isOk(r);letm = red("Assertion failed: ") +text("expected ") +blue("Ok(_)") +text(", but got ") +yellow("Err(_)");Assert.assert(c, m)////// Asserts that the given Result `r` is `Err(e)`.///pubdefassertErr(r: Result[e, a]): Unit \ Assert =letc = Result.isErr(r);letm = red("Assertion failed: ") +text("expected ") +blue("Err(_)") +text(", but got ") +yellow("Ok(_)");Assert.assert(c, m)////// Asserts that the given foldable `ma` is empty.///pubdefassertEmpty(ma: m[a]): Unit \ (Assert + Foldable.Aef[m]) withFoldable[m], ToString[m[a]] =letc = Foldable.isEmpty(ma);letm = red("Assertion failed: ") +text("expected ") +blue("empty collection") +text(", but got ") +yellow("${ma}");Assert.assert(c, m)////// Asserts that the given foldable `ma` is non-empty.///pubdefassertNonEmpty(ma: m[a]): Unit \ (Assert + Foldable.Aef[m]) withFoldable[m], ToString[m[a]] =letc = Foldable.nonEmpty(ma);letm = red("Assertion failed: ") +text("expected ") +blue("non-empty collection") +text(", but got ") +yellow("${ma}");Assert.assert(c, m)////// Asserts that the given element `x` is a member of the given foldable `ma`.///pubdefassertMemberOf(x: a, ma: m[a]): Unit \ (Assert + Foldable.Aef[m]) withFoldable[m], Eq[a], ToString[a], ToString[m[a]] =letc = Foldable.memberOf(x, ma);letm = red("Assertion failed: ") +text("expected ") +blue("`${x}`") +text(" to be a member of ") +yellow("${ma}");Assert.assert(c, m)////// Unconditionally succeeds with the given message `msg`.///pubdefsuccess(msg: String): Unit \ Assert =Assert.assert(true, text(msg))////// Unconditionally fails with the given message `msg`.///pubdeffail(msg: String): Unit \ Assert =Assert.assert(false, text(msg))////// Handles the `Assert` effect of the given function `f` by throwing exceptions on assertion failures.////// In other words, re-interprets the `Assert` effect using the `IO` effect.///pubdefhandle(f: a -> b \ ef): (a -> b \ (ef - Assert) + IO) = x ->run {f(x) } withhandlerAssert {defassert(cond, msg, resume) = {if (cond)resume()else {println(RichString.toString(msg));thrownew AssertionError(RichString.toString(msg)) } } }////// Runs the `Assert` effect of the given function `f` by throwing exceptions on assertion failures.////// In other words, re-interprets the `Assert` effect using the `IO` effect.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - Assert) + IO = handle(f)()////// Handles the `Assert` effect by ignoring all assertions (no-op).///pubdefhandleWithNoOp(f: a -> b \ ef): (a -> b \ (ef - Assert)) = x ->run {f(x) } withhandlerAssert {defassert(_cond, _msg, resume) = resume() }////// Runs the `Assert` effect of the given function `f` by ignoring all assertions (no-op).///pubdefrunWithNoOp(f: Unit -> a \ ef): a \ (ef - Assert) = handleWithNoOp(f)()////// Handles the `Assert` effect by logging assertion failures using the `Logger` effect.////// Assertions continue execution after logging the assertion failure.///pubdefhandleWithLogger(f: a -> b \ ef): (a -> b \ (ef - Assert) + Logger) = x ->run {f(x) } withhandlerAssert {defassert(cond, msg, resume) = {if (cond)resume()else {Logger.warn(msg);resume() } } }////// Runs the `Assert` effect of the given function `f` by logging assertion failures using the `Logger` effect.////// Assertions continue execution after logging the assertion failure.///pubdefrunWithLogger(f: Unit -> a \ ef): a \ (ef - Assert) + Logger = handleWithLogger(f)()////// Handles the `Assert` effect by printing assertion failures to stdout.////// Assertions continue execution after printing the assertion failure.///pubdefhandleWithStdOut(f: a -> b \ ef): (a -> b \ (ef - Assert) + IO) = x ->run {f(x) } withhandlerAssert {defassert(cond, msg, resume) = {if (cond)resume()else {println(RichString.toString(msg));resume() } } }////// Runs the `Assert` effect of the given function `f` by printing assertion failures to stdout.////// Assertions continue execution after printing the assertion failure.///pubdefrunWithStdOut(f: Unit -> a \ ef): a \ (ef - Assert) + IO = handleWithStdOut(f)()////// Handles the `Assert` effect by printing assertion failures to stderr.////// Assertions continue execution after printing the assertion failure.///pubdefhandleWithStdErr(f: a -> b \ ef): (a -> b \ (ef - Assert) + IO) = x ->run {f(x) } withhandlerAssert {defassert(cond, msg, resume) = {if (cond)resume()else {use Console.eprintln;run {eprintln(RichString.toString(msg)) } withConsole.runWithIO;resume() } } }////// Runs the `Assert` effect of the given function `f` by printing assertion failures to stderr.////// Assertions continue execution after printing the assertion failure.///pubdefrunWithStdErr(f: Unit -> a \ ef): a \ (ef - Assert) + IO = handleWithStdErr(f)()////// Handles the `Assert` effect by prompting the user whether to continue execution.////// When an assertion fails, prints the assertion failure and asks the user if they want to continue./// If the user chooses not to continue, the program throws an exception.///pubdefhandleWithPrompt(f: a -> b \ ef): (a -> b \ (ef - Assert) + IO) = x ->run {f(x) } withhandlerAssert {defassert(cond, msg, resume) = {if (cond)resume()else {use Console.{print, readln};run {println("");println(RichString.toString(msg));println("");print(RichString.toString(yellow("Continue execution? (Y/n): ")));matchreadln() {case None =>thrownew AssertionError("Execution aborted (no interactive console) after the following assertion failure: ${RichString.toString(msg)}")case Some(input) =>lettrimmed = String.toLowerCase(String.trim(input));if (trimmed=="n"ortrimmed=="no")thrownew AssertionError("Execution aborted by user after the following assertion failure: ${RichString.toString(msg)}")elseresume() } } withConsole.runWithIO } } }////// Runs the `Assert` effect of the given function `f` by prompting the user whether to continue execution.////// When an assertion fails, prints the assertion failure and asks the user if they want to continue./// If the user chooses not to continue, the program throws an exception.///pubdefrunWithPrompt(f: Unit -> a \ ef): a \ (ef - Assert) + IO = handleWithPrompt(f)()}