/* * Copyright 2024 Magnus Madsen * * Use of this source code is governed by the Apache 2.0 license * that can be found in the LICENSE.md file. */////// Effect for aborting a computation with an error message, and its functions.///pubmod Abort {import java.lang.Threadimport java.lang.StackTraceElementimport java.lang.System////// Effect for aborting a computation with an error message.///pubeffAbort {////// Immediately aborts the current computation with the given error message `m`.////// The computation cannot be resumed.///defabort(m: RichString): Void }////// Aborts the current computation with the given error message `m` enriched with a JVM stack trace.///pubdefabortWithTrace(m: String): Void \ Abort =lettrace = unsafeIO {letelems = Thread.currentThread().getStackTrace();formatStackTrace(Array.toList(elems), 3) };letnl = RichString.fromString(String.lineSeparator());letheader = RichString.white("Stack Trace:");Abort.abort(RichString.bold(RichString.red(RichString.fromString(m))) +nl+header+nl+trace)////// Handles the `Abort` effect of the given function `f` by printing the error message and exiting.////// In other words, re-interprets the `Abort` effect using the `IO` effect.////// Note: The returned function can still return normally if `abort` is never called by `f`.///pubdefhandle(f: a -> b \ ef): a -> b \ (ef - Abort) + IO = x ->run {f(x) } withhandlerAbort {defabort(m, _k) = { System.out.println(RichString.toString(m));unchecked_cast(System.exit(1) asb) } }////// Handles the `Abort` effect of the given function `f` by converting aborts to Result values.////// Returns a function that////// Returns `Ok(f())` if the computation completes successfully without aborting./// Returns `Err(m)` if the computation aborts with error message `m`.///pubdefhandleWithResult(f: a -> b \ ef): a -> Result[RichString, b] \ (ef - Abort) = x ->run { Ok(f(x)) } withhandlerAbort {defabort(m, _k) = Result.Err(m) }////// Runs the `Abort` effect of the given function `f`.////// In other words, re-interprets the `Abort` effect using the `IO` effect.////// Note: This function can still return normally if `abort` is never called by `f`.///@DefaultHandlerpubdefrunWithIO(f: Unit -> a \ ef): a \ (ef - Abort) + IO = handle(f)()////// Runs the `Abort` effect of the given function `f`.////// Returns `Ok(f())` if the computation completes successfully without aborting./// Returns `Err(m)` if the computation aborts with error message `m`.///pubdefrunWithResult(f: Unit -> a \ ef): Result[RichString, a] \ (ef - Abort) = handleWithResult(f)()////// Formats a JVM stack trace as a gray RichString.///defformatStackTrace(trace: List[StackTraceElement], skip: Int32): RichString \ IO =use RichString.{fromString, joinWith, cyan, gray, bold, yellow};letnl = fromString(String.lineSeparator());letframes = List.drop(skip, trace) |> List.zipWithIndex;letmaxIdx = List.length(frames) -1;letwidth = String.length("${maxIdx}");joinWith(match (idx, elem) -> {letclassName = elem.getClassName();letmethodName = elem.getMethodName();letfileName = Object.toOption(elem.getFileName()) |> Option.getWithDefault("Unknown Source");letlineNumber = elem.getLineNumber();letindex = cyan("#${String.padLeft(width, ' ', "${idx}")}");letlocation = gray("(${fileName}:${lineNumber})");if (isUserFrame(className))let (classPrefix, funcName) = splitClassName(className);fromString(" ") +index+fromString(" ")+fromString(classPrefix)+bold(yellow(funcName))+fromString(".${methodName}")+locationelsefromString(" ") +index+fromString(" ")+gray("${className}.${methodName}")+location },nl,frames )////// Splits a user-frame class name into the prefix (up to and including `Def$`)/// and the Flix function name (after `Def$`).///defsplitClassName(className: String): (String, String) =letprefix = "Def$";matchString.indexOfRight(substr = prefix, className) {case Some(idx) =>letsplitAt = idx+String.length(prefix); (String.sliceLeft(end = splitAt, className), String.sliceRight(start = splitAt, className))case None => ("", className) }////// Returns `true` if the given `className` represents a user-defined Flix function.////// Flix compiles user functions to JVM classes named `Def$functionName` or `Namespace.Def$functionName`.///defisUserFrame(className: String): Bool =String.startsWith(prefix = "Def$", className)orString.contains(substr = ".Def$", className)}