flix

0.78.0

Debug.flix

/*
 * Copyright 2025 Magnus Madsen
 *
 * Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE.md file.
 */

///
/// An effect used for print debugging.
///
eff Debug

///
/// Functions for print debugging.
///
pub mod Debug {

    import java.lang.Byte
    import java.lang.Double
    import java.lang.Float
    import java.lang.Integer
    import java.lang.Long
    import java.lang.Object
    import java.lang.Short
    import java.lang.System
    import java.lang.{Class => JClass}
    import java.lang.{String => JString}
    import java.math.{BigDecimal => JBigDecimal}
    import java.math.{BigInteger => JBigInteger}
    import java.util.Arrays
    import java.util.Objects

    ///
    /// Prints `x` to the standard out.
    ///
    pub def dprint(x: a): Unit \ (Debug + Formattable.Aef[a]) with Formattable[a] =
        let rs = Formattable.format(x);
        let s = RichString.toString(rs);
        unsafe IO as Debug { System.out.print(s) }

    ///
    /// Prints `x` to the standard out followed by a new line.
    ///
    pub def dprintln(x: a): Unit \ (Debug + Formattable.Aef[a]) with Formattable[a] =
        let rs = Formattable.format(x);
        let s = RichString.toString(rs);
        unsafe IO as Debug { System.out.println(s) }

    ///
    /// Escapes the given string per Flix's escaping rules.
    ///
    def escape(s: String): String = {
        def replace(src: String, dst: String, subject: String): String = subject.replace(src, dst);
        s
            // NB: \\ must come first to avoid clobbering other cases
            |> replace("\\", "\\\\")
            |> replace("\n", "\\n")
            |> replace("\r", "\\r")
            |> replace("\"", "\\\"")
            |> replace("\'", "\\\'")
            |> replace("\t", "\\t")
    }

    ///
    /// Returns an automatic string representation of `x`.
    ///
    pub def stringify(x: a): String = unsafe IO {
        use Reflect.JvmValue;
        match Reflect.reflectValue(x) {
            case JvmValue.JvmBool(b) => if (b) "true" else "false"
            case JvmValue.JvmChar(c) => "'" + escape("${(c: Char)}") + "'"
            case JvmValue.JvmFloat32(y) => Float.toString(y) + "f32"
            case JvmValue.JvmFloat64(y) => Double.toString(y)
            case JvmValue.JvmInt8(y) => Byte.toString(y) + "i8"
            case JvmValue.JvmInt16(y) => Short.toString(y) + "i16"
            case JvmValue.JvmInt32(y) => Integer.toString(y)
            case JvmValue.JvmInt64(y) => Long.toString(y) + "i64"
            case JvmValue.JvmObject(obj) =>
                if (Objects.isNull(obj)) {
                    "null"
                } else if (obj instanceof JString) {
                    "\"" + escape(unchecked_cast(obj as String)) + "\""
                } else if (obj instanceof JBigInteger) {
                    unchecked_cast(obj as JBigInteger).toString() + "ii"
                } else if (obj instanceof JBigDecimal) {
                    unchecked_cast(obj as JBigDecimal).toString() + "ff"
                } else {
                    objectToString(obj)
                }
        }
    }

    ///
    /// Returns a string representation of the given object,
    /// with special handling for arrays.
    ///
    def objectToString(obj: Object): String \ IO = {
        let clazz = obj.getClass();
        if (clazz.isArray()) {
            let componentType = clazz.getComponentType();
            if (componentType.isPrimitive()) {
                let name = componentType.getName();
                match name {
                    case "boolean" => Arrays.toString(unchecked_cast(obj as Array[Bool, Static]))
                    case "char"    => Arrays.toString(unchecked_cast(obj as Array[Char, Static]))
                    case "float"   => Arrays.toString(unchecked_cast(obj as Array[Float32, Static]))
                    case "double"  => Arrays.toString(unchecked_cast(obj as Array[Float64, Static]))
                    case "byte"    => Arrays.toString(unchecked_cast(obj as Array[Int8, Static]))
                    case "short"   => Arrays.toString(unchecked_cast(obj as Array[Int16, Static]))
                    case "int"     => Arrays.toString(unchecked_cast(obj as Array[Int32, Static]))
                    case "long"    => Arrays.toString(unchecked_cast(obj as Array[Int64, Static]))
                    case _         => Objects.toString(obj)
                }
            } else {
                Arrays.deepToString(unchecked_cast(obj as Array[Object, Static]))
            }
        } else {
            Objects.toString(obj)
        }
    }

}