From 10168d8ee4678478adaf653d771afcf7f2ef31a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Mon, 1 Dec 2025 13:25:08 +0100 Subject: [PATCH 01/11] Use TestRenamer before showing core to humans --- .../test/scala/effekt/core/ReparseTests.scala | 2 +- .../scala/effekt/core/TestRenamerTests.scala | 62 +++++++++---------- .../scala/effekt/core/PrettyPrinter.scala | 5 ++ .../main}/scala/effekt/core/TestRenamer.scala | 14 ++++- .../effekt/generator/chez/ChezScheme.scala | 2 +- .../effekt/generator/chez/ChezSchemeCPS.scala | 4 +- .../effekt/generator/js/JavaScript.scala | 4 +- .../scala/effekt/generator/llvm/LLVM.scala | 2 +- 8 files changed, 54 insertions(+), 41 deletions(-) rename effekt/{jvm/src/test => shared/src/main}/scala/effekt/core/TestRenamer.scala (95%) diff --git a/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala b/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala index a7dea5dbaa..44edb7972e 100644 --- a/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala @@ -87,7 +87,7 @@ class ReparseTests extends CoreTests { } val reparsedRenamed = renamer(reparsed) val reparsedPrinted = core.PrettyPrinter.format(reparsedRenamed).layout - val expectedPrinted = core.PrettyPrinter.format(expectedRenamed).layout + val expectedPrinted = printed assertEquals(reparsedPrinted, expectedPrinted) } diff --git a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala index d867a14f5f..8df6d4c907 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala @@ -12,7 +12,7 @@ class TestRenamerTests extends CoreTests { names: Names = Names(defaultNames))(using munit.Location) = { val pInput = parse(input, "input", names) val pExpected = parse(renamed, "expected", names) - val renamer = new TestRenamer(names, "renamed") // use "renamed" as prefix so we can refer to it + val renamer = new TestRenamer(names, "_renamed_") // use "renamed" as prefix so we can refer to it val obtained = renamer(pInput) val obtainedPrinted = effekt.core.PrettyPrinter.format(obtained).layout val expectedPrinted = effekt.core.PrettyPrinter.format(pExpected).layout @@ -31,7 +31,7 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0() = { + |def _renamed_0() = { | return (bar: (Int) => Int @ {})(baz: Int) |} |""".stripMargin @@ -51,11 +51,11 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0() = { - | val renamed1: Int = { - | renamed0: (Int) => Int @ {}(4) + |def foo_renamed_0() = { + | val x_renamed_1: Int = { + | foo_renamed_0: (Int) => Int @ {}(4) | }; - | return renamed1: Int + | return x_renamed_1: Int |} |""".stripMargin assertRenamedTo(input, expected) @@ -73,9 +73,9 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0() = { - | var renamed1 @ global = (renamed0: (Int) => Int @ {})(4); - | return renamed1: Int + |def foo_renamed_0() = { + | var x_renamed_1 @ global = (foo_renamed_0: (Int) => Int @ {})(4); + | return x_renamed_1: Int |} |""".stripMargin assertRenamedTo(input, expected) @@ -85,15 +85,15 @@ class TestRenamerTests extends CoreTests { val input = """module main | - |def renamed0(renamed1: Int) = { - | return renamed1: Int + |def f(x: Int) = { + | return x: Int |} |""".stripMargin val expected = """module main | - |def renamed0(renamed1: Int) = { - | return renamed1: Int + |def f_renamed_0(x_renamed_1: Int) = { + | return x_renamed_1: Int |} |""".stripMargin assertRenamedTo(input, expected) @@ -113,14 +113,14 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |type renamed1 { - | renamed0(a: Int, b: Int) + |type Data_renamed_1 { + | X_renamed_0(a: Int, b: Int) |} | - |def renamed2() = { - | 12 match [Int] { - | X : { (renamed3: Int, renamed4: Int) => - | return renamed3: Int + |def foo_renamed_2() = { + | 12 match { + | X : { (aa_renamed_3: Int, bb_renamed_4: Int) => + | return aa_renamed_3: Int | } | } |} @@ -139,8 +139,8 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0['renamed1](renamed2: Identity[renamed1]) = { - | return renamed2: Identity[renamed1] + |def foo_renamed_0['A_renamed_1](a_renamed_2: A_renamed_1) = { + | return a_renamed_2: Identity[A_renamed_1] |} |""".stripMargin assertRenamedTo(input, expected) @@ -161,17 +161,17 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0() = { + |def bar_renamed_0() = { | return 1 |} - |def renamed1() = { - | def renamed2() = { - | renamed0: () => Unit @ {}() + |def main_renamed_1() = { + | def foo_renamed_2() = { + | bar_renamed_0: () => Unit @ {}() | } - | def renamed3() = { + | def bar_renamed_3() = { | return 2 | } - | renamed2: () => Unit @ {}() + | foo_renamed_2: () => Unit @ {}() |} |""".stripMargin @@ -191,10 +191,10 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def renamed0() = { - | let renamed1 = 1 - | let renamed2 = 2 - | return renamed2: Int + |def main_renamed_0() = { + | let x_renamed_1 = 1 + | let x_renamed_2 = 2 + | return x_renamed_2: Int |} |""".stripMargin diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index c8e560af2e..7ce072ff02 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -14,6 +14,11 @@ object PrettyPrinter extends ParenPrettyPrinter { override val defaultIndent = 2 + def formatHumanReadable(t: ModuleDecl): Document = { + val renamer = effekt.core.TestRenamer(Names(builtins.coreBuiltins)) + format(renamer(t)) + } + def format(t: ModuleDecl): Document = pretty(toDoc(t), 4) diff --git a/effekt/jvm/src/test/scala/effekt/core/TestRenamer.scala b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala similarity index 95% rename from effekt/jvm/src/test/scala/effekt/core/TestRenamer.scala rename to effekt/shared/src/main/scala/effekt/core/TestRenamer.scala index ca70ff1551..77b29e6ebe 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TestRenamer.scala +++ b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala @@ -1,7 +1,7 @@ package effekt.core -import effekt.{Template, core, symbols} import effekt.symbols.builtins +import effekt.{Template, core, symbols} /** * Freshens bound names in a given term for tests. @@ -14,7 +14,7 @@ import effekt.symbols.builtins * * @param C the context is used to copy annotations from old symbols to fresh symbols */ -class TestRenamer(names: Names = Names(Map.empty), prefix: String = "_") extends core.Tree.Rewrite { +class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$") extends core.Tree.Rewrite { // list of scopes that map bound symbols to their renamed variants. private var scopes: List[Map[Id, Id]] = List.empty @@ -31,6 +31,14 @@ class TestRenamer(names: Names = Names(Map.empty), prefix: String = "_") extends if (names.isKnown(id)) { return names.getKnown(id).get } + // To generate human-readable names, we include the user-provided string. + // To ensure idempotent printing, we strip any existing prefix from the name. + // This assumes that user-provided names to not include `prefix`. + val userName = if (id.name.name.contains(prefix)) { + id.name.name.substring(0, id.name.name.lastIndexOf(prefix)) + } else { + id.name.name + } // HACK: This is an unfortunate hack. // TestRenamer is often used to check for alpha-equivalence by renaming both sides of a comparison. // However, Effekt requires globally unique Barendregt indices for all symbols, so just creating fresh @@ -38,7 +46,7 @@ class TestRenamer(names: Names = Names(Map.empty), prefix: String = "_") extends // comparison get the same fresh Id for a given original Id. // This is achieved by generating a deterministic string `uniqueName` on both sides, and looking it up in `names`, // which generates a unique Id for it once and reuses it on subsequent lookups. - val uniqueName = prefix + suffix.toString + val uniqueName = userName + prefix + suffix.toString suffix = suffix + 1 names.idFor(uniqueName) diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala index 991b6a5408..7ded90297d 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -36,7 +36,7 @@ trait ChezScheme extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez") override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => Core(source).map { res => core.PrettyPrinter.format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } case Stage.CPS => None case Stage.Machine => None case Stage.Target => Separate(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala index f7506b52cb..d3c6ed494f 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -14,8 +14,8 @@ class ChezSchemeCPS extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez", "chezCPS") override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter.format(res.core) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.formatHumanReadable(res) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } case Stage.Machine => None case Stage.Target => LSP(source) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index b10848ac42..5024883b61 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -18,9 +18,9 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerCps.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.format(res) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.formatHumanReadable(res) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter.format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } case Stage.Machine => None case Stage.Target => CompileLSP(source).map { pretty } } diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala index 11d05e0ea1..0973152ced 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -18,7 +18,7 @@ class LLVM extends Compiler[String] { override def supportedFeatureFlags: List[String] = Transformer.llvmFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter.format(res.core) } + case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } case Stage.CPS => None case Stage.Machine => steps.afterMachine(source).map { res => machine.PrettyPrinter.format(res.program) } case Stage.Target => steps.afterLLVM(source).map { res => pretty(res) } From 6f1e18ecff271b70ae93e1ab39c584a47340e797 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Mon, 1 Dec 2025 13:54:32 +0100 Subject: [PATCH 02/11] Elide some details when printing core for human inspection --- .../effekt/core/DeclarationContext.scala | 2 +- .../scala/effekt/core/PrettyPrinter.scala | 35 +++++++++++-------- .../effekt/generator/chez/ChezScheme.scala | 2 +- .../effekt/generator/chez/ChezSchemeCPS.scala | 4 +-- .../effekt/generator/js/JavaScript.scala | 4 +-- .../scala/effekt/generator/llvm/LLVM.scala | 2 +- .../src/main/scala/effekt/util/Debug.scala | 2 +- 7 files changed, 29 insertions(+), 22 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/DeclarationContext.scala b/effekt/shared/src/main/scala/effekt/core/DeclarationContext.scala index a4c53987c2..2627395377 100644 --- a/effekt/shared/src/main/scala/effekt/core/DeclarationContext.scala +++ b/effekt/shared/src/main/scala/effekt/core/DeclarationContext.scala @@ -1,7 +1,7 @@ package effekt.core import effekt.util.messages.ErrorReporter -import PrettyPrinter.* +import HumanReadablePrettyPrinter.* /** * Context for transformations of a [[core.ModuleDecl]] that provides the declarations for this module. diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index 7ce072ff02..e84b97b982 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -4,21 +4,14 @@ package core import effekt.core.Type.{ PromptSymbol, ResumeSymbol, TChar } import effekt.source.FeatureFlag import kiama.output.ParenPrettyPrinter +import kiama.output.PrettyPrinterTypes.Document import scala.language.implicitConversions import effekt.symbols.{ Name, Wildcard, builtins } -object PrettyPrinter extends ParenPrettyPrinter { - - import kiama.output.PrettyPrinterTypes.Document - +class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { override val defaultIndent = 2 - def formatHumanReadable(t: ModuleDecl): Document = { - val renamer = effekt.core.TestRenamer(Names(builtins.coreBuiltins)) - format(renamer(t)) - } - def format(t: ModuleDecl): Document = pretty(toDoc(t), 4) @@ -61,9 +54,9 @@ object PrettyPrinter extends ParenPrettyPrinter { // The order of toplevel items must match the parser (where the order is currently fixed). val includes = vsep(m.includes.map { im => "import" <+> im }) val decls = vsep(m.declarations.map(toDoc)) - val externs = vsep(m.externs.map(toDoc)) + val externs = if humanReadable then emptyDoc else vsep(m.externs.map(toDoc)) val defs = toDoc(m.definitions) - val exports = vsep(m.exports.map { id => "export" <+> toDoc(id) }) + val exports = if humanReadable then emptyDoc else vsep(m.exports.map { id => "export" <+> toDoc(id) }) "module" <+> m.path <> emptyline <> @@ -106,7 +99,7 @@ object PrettyPrinter extends ParenPrettyPrinter { def toDoc(b: Block, preventBraces: Boolean = false): Doc = b match { case BlockVar(id, tpe, capt) => - toDoc(id) <> ":" <+> toDoc(tpe) <+> "@" <+> toDoc(capt) + toDoc(id) <> (if humanReadable then emptyDoc else ":" <+> toDoc(tpe) <+> "@" <+> toDoc(capt)) case BlockLit(tps, cps, vps, bps, body) => val doc = space <> paramsToDoc(tps, cps, vps, bps) <+> "=>" <+> nest(line <> toDocStmts(body)) <> line if preventBraces then doc else braces { doc } @@ -129,8 +122,7 @@ object PrettyPrinter extends ParenPrettyPrinter { case Literal(n, Type.TChar) => s"'\\${n.toString}'" case Literal(s: String, _) => stringLiteral(s) case Literal(value, _) => value.toString - case ValueVar(id, tpe) => toDoc(id) <> ":" <+> toDoc(tpe) - + case ValueVar(id, tpe) => toDoc(id) <> (if humanReadable then emptyDoc else ":" <+> toDoc(tpe)) case PureApp(b, targs, vargs) => parens(toDoc(b)) <> argsToDoc(targs, vargs, Nil) case Make(data, tag, targs, vargs) => "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) @@ -351,3 +343,18 @@ object PrettyPrinter extends ParenPrettyPrinter { multi <> s <> multi } } + +/** + * Instance of PrettyPrinter that produces output that can be parsed back by the core parser. + */ +object ReparsablePrettyPrinter extends PrettyPrinter(false) {} + +/** + * Instance of PrettyPrinter that produces less verbose, more human-readable output. + */ +object HumanReadablePrettyPrinter extends PrettyPrinter(true) { + def renameAndFormat(t: ModuleDecl): Document = { + val renamer = effekt.core.TestRenamer(Names(builtins.coreBuiltins)) + format(renamer(t)) + } +} \ No newline at end of file diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala index 7ded90297d..83b929a7bd 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -36,7 +36,7 @@ trait ChezScheme extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez") override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } case Stage.CPS => None case Stage.Machine => None case Stage.Target => Separate(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala index d3c6ed494f..bac68bb59f 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -14,8 +14,8 @@ class ChezSchemeCPS extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez", "chezCPS") override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.formatHumanReadable(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.renameAndFormat(res) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } case Stage.Machine => None case Stage.Target => LSP(source) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index 5024883b61..2f7a7d6ea9 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -18,9 +18,9 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerCps.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter.formatHumanReadable(res) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.renameAndFormat(res) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } case Stage.Machine => None case Stage.Target => CompileLSP(source).map { pretty } } diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala index 0973152ced..d01393f56b 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -18,7 +18,7 @@ class LLVM extends Compiler[String] { override def supportedFeatureFlags: List[String] = Transformer.llvmFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter.formatHumanReadable(res.core) } + case Stage.Core => steps.afterCore(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } case Stage.CPS => None case Stage.Machine => steps.afterMachine(source).map { res => machine.PrettyPrinter.format(res.program) } case Stage.Target => steps.afterLLVM(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/util/Debug.scala b/effekt/shared/src/main/scala/effekt/util/Debug.scala index 8efbe62edd..0e71f9a3d4 100644 --- a/effekt/shared/src/main/scala/effekt/util/Debug.scala +++ b/effekt/shared/src/main/scala/effekt/util/Debug.scala @@ -14,7 +14,7 @@ val showGeneric: PartialFunction[Any, String] = { val show: PartialFunction[Any, String] = TypePrinter.show orElse - core.PrettyPrinter.show orElse + core.HumanReadablePrettyPrinter.show orElse generator.js.PrettyPrinter.show orElse cps.PrettyPrinter.show orElse machine.PrettyPrinter.show orElse From 88d2650b65d8fc199c47e6ed97e3c5093e5aa09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Mon, 1 Dec 2025 13:59:05 +0100 Subject: [PATCH 03/11] Fix remaining uses of PrettyPrinter.format --- .../jvm/src/test/scala/effekt/core/CoreTests.scala | 12 ++++++------ .../src/test/scala/effekt/core/ReparseTests.scala | 4 ++-- .../test/scala/effekt/core/TestRenamerTests.scala | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala index 144223bcd0..d79a82517c 100644 --- a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala @@ -19,11 +19,11 @@ trait CoreTests extends munit.FunSuite { |===================== |Got: |---- - |${effekt.core.PrettyPrinter.format(obtained).layout} + |${effekt.core.ReparsablePrettyPrinter.format(obtained).layout} | |Expected: |--------- - |${effekt.core.PrettyPrinter.format(expected).layout} + |${effekt.core.ReparsablePrettyPrinter.format(expected).layout} | |""".stripMargin }) @@ -34,11 +34,11 @@ trait CoreTests extends munit.FunSuite { |===================== |Got: |---- - |${effekt.core.PrettyPrinter.format(obtained)} + |${effekt.core.ReparsablePrettyPrinter.format(obtained)} | |Expected: |--------- - |${effekt.core.PrettyPrinter.format(expected)} + |${effekt.core.ReparsablePrettyPrinter.format(expected)} | |""".stripMargin }) @@ -50,8 +50,8 @@ trait CoreTests extends munit.FunSuite { val renamer = TestRenamer(names) val obtainedRenamed = renamer(obtained) val expectedRenamed = renamer(expected) - val obtainedPrinted = effekt.core.PrettyPrinter.format(obtainedRenamed).layout - val expectedPrinted = effekt.core.PrettyPrinter.format(expectedRenamed).layout + val obtainedPrinted = effekt.core.ReparsablePrettyPrinter.format(obtainedRenamed).layout + val expectedPrinted = effekt.core.ReparsablePrettyPrinter.format(expectedRenamed).layout assertEquals(obtainedPrinted, expectedPrinted) shouldBeEqual(obtainedRenamed, expectedRenamed, clue) } diff --git a/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala b/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala index 44edb7972e..4c00ba20d9 100644 --- a/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/ReparseTests.scala @@ -75,7 +75,7 @@ class ReparseTests extends CoreTests { } val renamer = TestRenamer(Names(defaultNames)) val expectedRenamed = renamer(coreMod) - val printed = core.PrettyPrinter.format(expectedRenamed).layout + val printed = core.ReparsablePrettyPrinter.format(expectedRenamed).layout val reparsed: ModuleDecl = try { parse(printed)(using Location.empty) } catch { case e @ TypeError(msg, context) => println(e.toString) @@ -86,7 +86,7 @@ class ReparseTests extends CoreTests { throw e } val reparsedRenamed = renamer(reparsed) - val reparsedPrinted = core.PrettyPrinter.format(reparsedRenamed).layout + val reparsedPrinted = core.ReparsablePrettyPrinter.format(reparsedRenamed).layout val expectedPrinted = printed assertEquals(reparsedPrinted, expectedPrinted) } diff --git a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala index 8df6d4c907..9b0357c7f5 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala @@ -14,8 +14,8 @@ class TestRenamerTests extends CoreTests { val pExpected = parse(renamed, "expected", names) val renamer = new TestRenamer(names, "_renamed_") // use "renamed" as prefix so we can refer to it val obtained = renamer(pInput) - val obtainedPrinted = effekt.core.PrettyPrinter.format(obtained).layout - val expectedPrinted = effekt.core.PrettyPrinter.format(pExpected).layout + val obtainedPrinted = effekt.core.ReparsablePrettyPrinter.format(obtained).layout + val expectedPrinted = effekt.core.ReparsablePrettyPrinter.format(pExpected).layout assertEquals(obtainedPrinted, expectedPrinted) shouldBeEqual(obtained, pExpected, clue) } From 889b4d186b4c51d47643286c5fcc489762529b9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Mon, 1 Dec 2025 14:08:40 +0100 Subject: [PATCH 04/11] Do not include user-annotated name when checking for alpha conversion --- effekt/jvm/src/test/scala/effekt/core/CoreTests.scala | 4 ++-- .../shared/src/main/scala/effekt/core/TestRenamer.scala | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala index d79a82517c..3525a22fed 100644 --- a/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/CoreTests.scala @@ -47,7 +47,7 @@ trait CoreTests extends munit.FunSuite { expected: ModuleDecl, clue: => Any = "values are not alpha-equivalent", names: Names = Names(defaultNames))(using Location): Unit = { - val renamer = TestRenamer(names) + val renamer = TestRenamer(names, preserveUserAnnotatedPrefix=false) val obtainedRenamed = renamer(obtained) val expectedRenamed = renamer(expected) val obtainedPrinted = effekt.core.ReparsablePrettyPrinter.format(obtainedRenamed).layout @@ -59,7 +59,7 @@ trait CoreTests extends munit.FunSuite { expected: Stmt, clue: => Any = "values are not alpha-equivalent", names: Names = Names(defaultNames))(using Location): Unit = { - val renamer = TestRenamer(names) + val renamer = TestRenamer(names, preserveUserAnnotatedPrefix=false) shouldBeEqual(renamer(obtained), renamer(expected), clue) } def parse(input: String, diff --git a/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala index 77b29e6ebe..a7893a7cff 100644 --- a/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala +++ b/effekt/shared/src/main/scala/effekt/core/TestRenamer.scala @@ -14,7 +14,7 @@ import effekt.{Template, core, symbols} * * @param C the context is used to copy annotations from old symbols to fresh symbols */ -class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$") extends core.Tree.Rewrite { +class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$", preserveUserAnnotatedPrefix: Boolean = true) extends core.Tree.Rewrite { // list of scopes that map bound symbols to their renamed variants. private var scopes: List[Map[Id, Id]] = List.empty @@ -39,6 +39,10 @@ class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$") extends } else { id.name.name } + // For many purposes, we would like to include the user-annotated name in the fresh id, + // so that printed terms are more readable. + // However, we do not want to include it when checking for alpha-equivalence in tests. + val userPart = if preserveUserAnnotatedPrefix then userName else "" // HACK: This is an unfortunate hack. // TestRenamer is often used to check for alpha-equivalence by renaming both sides of a comparison. // However, Effekt requires globally unique Barendregt indices for all symbols, so just creating fresh @@ -46,7 +50,7 @@ class TestRenamer(names: Names = Names(Map.empty), prefix: String = "$") extends // comparison get the same fresh Id for a given original Id. // This is achieved by generating a deterministic string `uniqueName` on both sides, and looking it up in `names`, // which generates a unique Id for it once and reuses it on subsequent lookups. - val uniqueName = userName + prefix + suffix.toString + val uniqueName = userPart + prefix + suffix.toString suffix = suffix + 1 names.idFor(uniqueName) From a8996d8a4238de5d10d6fc2f9a28e57efa134f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Wed, 3 Dec 2025 09:58:42 +0100 Subject: [PATCH 05/11] Print full Barendregt id --- .../src/main/scala/effekt/core/PrettyPrinter.scala | 10 ++++++++-- .../main/scala/effekt/generator/chez/ChezScheme.scala | 2 +- .../scala/effekt/generator/chez/ChezSchemeCPS.scala | 4 ++-- .../main/scala/effekt/generator/js/JavaScript.scala | 4 ++-- .../src/main/scala/effekt/generator/llvm/LLVM.scala | 2 +- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index e84b97b982..c9fb1efea1 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -113,8 +113,14 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { //def toDoc(n: Name): Doc = n.toString - def toDoc(s: symbols.Symbol): Doc = - builtins.coreBuiltinSymbolToString(s).getOrElse(s.name.name) + def toDoc(s: symbols.Symbol): Doc = { + // In human-readable mode, we show the name together with the actual Barendregt id. + // This allows the user to connect the symbol to the internal representation when debugging. + // In reparsable mode, we just show the string part, which should be freshened by the TestRenamer before printing. + // The TestRenamer does not rename the Barendregt id because that would violate the internal invariant of having + // just a single global Barendregt namespace. + builtins.coreBuiltinSymbolToString(s).getOrElse(if humanReadable then s.show else s.name.name) + } def toDoc(e: Expr): Doc = e match { case Literal((), _) => "()" diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala index 83b929a7bd..473d6ffdc8 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -36,7 +36,7 @@ trait ChezScheme extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez") override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } case Stage.CPS => None case Stage.Machine => None case Stage.Target => Separate(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala index bac68bb59f..8fe0379fa4 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -14,8 +14,8 @@ class ChezSchemeCPS extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez", "chezCPS") override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.renameAndFormat(res) } - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.format(res) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } case Stage.Machine => None case Stage.Target => LSP(source) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index 2f7a7d6ea9..dac4bcd83d 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -18,9 +18,9 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerCps.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.renameAndFormat(res) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.format(res) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } + case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } case Stage.Machine => None case Stage.Target => CompileLSP(source).map { pretty } } diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala index d01393f56b..c373916f76 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -18,7 +18,7 @@ class LLVM extends Compiler[String] { override def supportedFeatureFlags: List[String] = Transformer.llvmFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => steps.afterCore(source).map { res => core.HumanReadablePrettyPrinter.renameAndFormat(res.core) } + case Stage.Core => steps.afterCore(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } case Stage.CPS => None case Stage.Machine => steps.afterMachine(source).map { res => machine.PrettyPrinter.format(res.program) } case Stage.Target => steps.afterLLVM(source).map { res => pretty(res) } From f83842292510270244e9cbb549f9a4107c540ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Wed, 3 Dec 2025 10:15:38 +0100 Subject: [PATCH 06/11] Hide more annotations in human-readable mode --- .../scala/effekt/core/PrettyPrinter.scala | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index c9fb1efea1..f16afc78d3 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -129,9 +129,12 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { case Literal(s: String, _) => stringLiteral(s) case Literal(value, _) => value.toString case ValueVar(id, tpe) => toDoc(id) <> (if humanReadable then emptyDoc else ":" <+> toDoc(tpe)) - case PureApp(b, targs, vargs) => parens(toDoc(b)) <> argsToDoc(targs, vargs, Nil) - case Make(data, tag, targs, vargs) => "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) - + case PureApp(b, targs, vargs) => (if humanReadable then toDoc(b) else parens(toDoc(b))) <> argsToDoc(targs, vargs, Nil) + case Make(data, tag, targs, vargs) => + if humanReadable then + "make" <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) + else + "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) case Box(b, capt) => "box" <+> toDoc(capt) <+> toDoc(b) } @@ -238,7 +241,8 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { toDoc(b) <> argsToDoc(targs, vargs, bargs) case Invoke(b, method, methodTpe, targs, vargs, bargs) => - toDoc(b) <> "." <> toDoc(method) <> ":" <+> toDoc(methodTpe) <> argsToDoc(targs, vargs, bargs) + val pTpe = if humanReadable then emptyDoc else ":" <+> toDoc(methodTpe) + toDoc(b) <> "." <> toDoc(method) <> pTpe <> argsToDoc(targs, vargs, bargs) case If(cond, thn, els) => "if" <+> parens(toDoc(cond)) <+> block(toDocStmts(thn)) <+> "else" <+> block(toDocStmts(els)) @@ -358,9 +362,4 @@ object ReparsablePrettyPrinter extends PrettyPrinter(false) {} /** * Instance of PrettyPrinter that produces less verbose, more human-readable output. */ -object HumanReadablePrettyPrinter extends PrettyPrinter(true) { - def renameAndFormat(t: ModuleDecl): Document = { - val renamer = effekt.core.TestRenamer(Names(builtins.coreBuiltins)) - format(renamer(t)) - } -} \ No newline at end of file +object HumanReadablePrettyPrinter extends PrettyPrinter(true) {} From e75099935817b70fddc99c1bbeea8dce41873301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Wed, 3 Dec 2025 11:18:52 +0100 Subject: [PATCH 07/11] Add --detailed-ir flag --- .../src/main/scala/effekt/EffektConfig.scala | 2 ++ .../src/main/scala/effekt/EffektConfig.scala | 11 ++++++++ .../scala/effekt/core/PrettyPrinter.scala | 28 ++++++++++--------- .../effekt/generator/chez/ChezScheme.scala | 2 +- .../effekt/generator/chez/ChezSchemeCPS.scala | 4 +-- .../effekt/generator/js/JavaScript.scala | 4 +-- .../scala/effekt/generator/llvm/LLVM.scala | 2 +- 7 files changed, 34 insertions(+), 19 deletions(-) diff --git a/effekt/js/src/main/scala/effekt/EffektConfig.scala b/effekt/js/src/main/scala/effekt/EffektConfig.scala index 8b50b1eabf..44b6e88daf 100644 --- a/effekt/js/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/js/src/main/scala/effekt/EffektConfig.scala @@ -39,4 +39,6 @@ trait EffektConfig { def timed() = false def debug() = false + + def printDetailedIR(): Boolean = false } diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 154b499749..0760892e32 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -164,6 +164,15 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- group = debugging ) + val detailedIR: ScallopOption[Boolean] = toggle( + "detailed-ir", + descrYes = "Print detailed IR with all annotations", + default = Some(false), + noshort = true, + prefix = "no-", + group = debugging + ) + val showDocumentation: ScallopOption[Boolean] = toggle( "show-documentation", descrYes = "Show all documented statements as a JSON", @@ -263,6 +272,8 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- def timed(): Boolean = time.isSupplied && !server() + def printDetailedIR(): Boolean = detailedIR() + validateFilesIsDirectory(includePath) // force some other configs manually to initialize them when compiling with native-image diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index f16afc78d3..803d44be3d 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -9,7 +9,7 @@ import kiama.output.PrettyPrinterTypes.Document import scala.language.implicitConversions import effekt.symbols.{ Name, Wildcard, builtins } -class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { +class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { override val defaultIndent = 2 def format(t: ModuleDecl): Document = @@ -54,9 +54,9 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { // The order of toplevel items must match the parser (where the order is currently fixed). val includes = vsep(m.includes.map { im => "import" <+> im }) val decls = vsep(m.declarations.map(toDoc)) - val externs = if humanReadable then emptyDoc else vsep(m.externs.map(toDoc)) + val externs = if printDetails then vsep(m.externs.map(toDoc)) else emptyDoc val defs = toDoc(m.definitions) - val exports = if humanReadable then emptyDoc else vsep(m.exports.map { id => "export" <+> toDoc(id) }) + val exports = if printDetails then vsep(m.exports.map { id => "export" <+> toDoc(id) }) else emptyDoc "module" <+> m.path <> emptyline <> @@ -99,7 +99,7 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { def toDoc(b: Block, preventBraces: Boolean = false): Doc = b match { case BlockVar(id, tpe, capt) => - toDoc(id) <> (if humanReadable then emptyDoc else ":" <+> toDoc(tpe) <+> "@" <+> toDoc(capt)) + toDoc(id) <> (if printDetails then ":" <+> toDoc(tpe) <+> "@" <+> toDoc(capt) else emptyDoc) case BlockLit(tps, cps, vps, bps, body) => val doc = space <> paramsToDoc(tps, cps, vps, bps) <+> "=>" <+> nest(line <> toDocStmts(body)) <> line if preventBraces then doc else braces { doc } @@ -119,7 +119,7 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { // In reparsable mode, we just show the string part, which should be freshened by the TestRenamer before printing. // The TestRenamer does not rename the Barendregt id because that would violate the internal invariant of having // just a single global Barendregt namespace. - builtins.coreBuiltinSymbolToString(s).getOrElse(if humanReadable then s.show else s.name.name) + builtins.coreBuiltinSymbolToString(s).getOrElse(if printDetails then s.name.name else s.show) } def toDoc(e: Expr): Doc = e match { @@ -128,13 +128,13 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { case Literal(n, Type.TChar) => s"'\\${n.toString}'" case Literal(s: String, _) => stringLiteral(s) case Literal(value, _) => value.toString - case ValueVar(id, tpe) => toDoc(id) <> (if humanReadable then emptyDoc else ":" <+> toDoc(tpe)) - case PureApp(b, targs, vargs) => (if humanReadable then toDoc(b) else parens(toDoc(b))) <> argsToDoc(targs, vargs, Nil) + case ValueVar(id, tpe) => toDoc(id) <> (if printDetails then ":" <+> toDoc(tpe) else emptyDoc) + case PureApp(b, targs, vargs) => (if printDetails then parens(toDoc(b)) else toDoc(b)) <> argsToDoc(targs, vargs, Nil) case Make(data, tag, targs, vargs) => - if humanReadable then - "make" <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) - else + if printDetails then "make" <+> toDoc(data) <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) + else + "make" <+> toDoc(tag) <> argsToDoc(targs, vargs, Nil) case Box(b, capt) => "box" <+> toDoc(capt) <+> toDoc(b) } @@ -241,7 +241,7 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { toDoc(b) <> argsToDoc(targs, vargs, bargs) case Invoke(b, method, methodTpe, targs, vargs, bargs) => - val pTpe = if humanReadable then emptyDoc else ":" <+> toDoc(methodTpe) + val pTpe = if printDetails then ":" <+> toDoc(methodTpe) else emptyDoc toDoc(b) <> "." <> toDoc(method) <> pTpe <> argsToDoc(targs, vargs, bargs) case If(cond, thn, els) => @@ -356,10 +356,12 @@ class PrettyPrinter(humanReadable: Boolean) extends ParenPrettyPrinter { /** * Instance of PrettyPrinter that produces output that can be parsed back by the core parser. + * This corresponds to the `--detailed-ir` command line flag. */ -object ReparsablePrettyPrinter extends PrettyPrinter(false) {} +object ReparsablePrettyPrinter extends PrettyPrinter(true) {} /** * Instance of PrettyPrinter that produces less verbose, more human-readable output. + * This is the default behavior for the `--ir-write-all` and `--ir-show` command line flags. */ -object HumanReadablePrettyPrinter extends PrettyPrinter(true) {} +object HumanReadablePrettyPrinter extends PrettyPrinter(false) {} diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala index 473d6ffdc8..ca97dd519d 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -36,7 +36,7 @@ trait ChezScheme extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez") override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } case Stage.CPS => None case Stage.Machine => None case Stage.Target => Separate(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala index 8fe0379fa4..33d1d0351f 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -14,8 +14,8 @@ class ChezSchemeCPS extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez", "chezCPS") override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.printDetailedIR()).format(res) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } case Stage.Machine => None case Stage.Target => LSP(source) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index dac4bcd83d..d4990cef0a 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -18,9 +18,9 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerCps.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.HumanReadablePrettyPrinter.format(res) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.printDetailedIR()).format(res) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } case Stage.Machine => None case Stage.Target => CompileLSP(source).map { pretty } } diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala index c373916f76..55fa9024ed 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -18,7 +18,7 @@ class LLVM extends Compiler[String] { override def supportedFeatureFlags: List[String] = Transformer.llvmFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => steps.afterCore(source).map { res => core.HumanReadablePrettyPrinter.format(res.core) } + case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } case Stage.CPS => None case Stage.Machine => steps.afterMachine(source).map { res => machine.PrettyPrinter.format(res.program) } case Stage.Target => steps.afterLLVM(source).map { res => pretty(res) } From ee9c7ce501790742f0f8a879ec35d2685594014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Wed, 10 Dec 2025 13:57:27 +0100 Subject: [PATCH 08/11] Hide more details when printing core by default --- .../scala/effekt/core/PrettyPrinter.scala | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index 803d44be3d..cfd2b8f287 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -109,7 +109,12 @@ class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { def toDoc(p: ValueParam): Doc = toDoc(p.id) <> ":" <+> toDoc(p.tpe) def toDoc(p: BlockParam): Doc = braces(toDoc(p.id) <> ":" <+> toDoc(p.tpe)) - def toDoc(cparam: Id, bparam: BlockParam): Doc = braces(toDoc(bparam.id) <+> "@" <+> toDoc(cparam) <> ":" <+> toDoc(bparam.tpe)) + def toDoc(cparam: Id, bparam: BlockParam): Doc = { + if printDetails then + braces(toDoc(bparam.id) <+> "@" <+> toDoc(cparam) <> ":" <+> toDoc(bparam.tpe)) + else + braces(toDoc(bparam.id) <> ":" <+> toDoc(bparam.tpe)) + } //def toDoc(n: Name): Doc = n.toString @@ -261,16 +266,25 @@ class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { toDocStmts(body) case Var(ref, init, cap, body) => - "var" <+> toDoc(ref) <+> "@" <+> toDoc(cap) <+> "=" <+> toDoc(init) <> ";" <> line <> - toDocStmts(body) + if printDetails then + "var" <+> toDoc(ref) <+> "@" <+> toDoc(cap) <+> "=" <+> toDoc(init) <> ";" <> line <> toDocStmts(body) + else + "var" <+> toDoc(ref) <+> "=" <+> toDoc(init) <> ";" <> line <> toDocStmts(body) case Get(id, tpe, ref, capt, body) => - "get" <+> toDoc(id) <+> ":" <+> toDoc(tpe) <+> "=" <+> "!" <+> toDoc(ref) <+> "@" <+> toDocSingleCapture(capt) <> ";" <> line <> - toDocStmts(body) + if printDetails then + "get" <+> toDoc(id) <+> ":" <+> toDoc(tpe) <+> "=" <+> "!" <+> toDoc(ref) <+> "@" <+> toDocSingleCapture(capt) <> ";" <> line <> + toDocStmts(body) + else + "get" <+> toDoc(id) <+> "=" <+> "!" <+> toDoc(ref) <> ";" <> line <> toDocStmts(body) case Put(ref, capt, value, body) => - "put" <+> toDoc(ref) <+> "@" <+> toDocSingleCapture(capt) <+> "=" <+> toDoc(value) <> ";" <> line <> - toDocStmts(body) + if printDetails then + "put" <+> toDoc(ref) <+> "@" <+> toDocSingleCapture(capt) <+> "=" <+> toDoc(value) <> ";" <> line <> + toDocStmts(body) + else + "put" <+> toDoc(ref) <+> "=" <+> toDoc(value) <> ";" <> line <> + toDocStmts(body) case Region(body) => "region" <+> toDoc(body) From 6250f8ab98b031e9a3a427719759a4189ad7ac7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Wed, 10 Dec 2025 14:01:06 +0100 Subject: [PATCH 09/11] Replace --detailed-ir by --debug flag --- effekt/js/src/main/scala/effekt/EffektConfig.scala | 2 -- effekt/jvm/src/main/scala/effekt/EffektConfig.scala | 11 ----------- .../src/main/scala/effekt/core/PrettyPrinter.scala | 2 +- .../main/scala/effekt/generator/chez/ChezScheme.scala | 2 +- .../scala/effekt/generator/chez/ChezSchemeCPS.scala | 4 ++-- .../main/scala/effekt/generator/js/JavaScript.scala | 4 ++-- .../src/main/scala/effekt/generator/llvm/LLVM.scala | 2 +- 7 files changed, 7 insertions(+), 20 deletions(-) diff --git a/effekt/js/src/main/scala/effekt/EffektConfig.scala b/effekt/js/src/main/scala/effekt/EffektConfig.scala index 44b6e88daf..8b50b1eabf 100644 --- a/effekt/js/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/js/src/main/scala/effekt/EffektConfig.scala @@ -39,6 +39,4 @@ trait EffektConfig { def timed() = false def debug() = false - - def printDetailedIR(): Boolean = false } diff --git a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala index 0760892e32..154b499749 100644 --- a/effekt/jvm/src/main/scala/effekt/EffektConfig.scala +++ b/effekt/jvm/src/main/scala/effekt/EffektConfig.scala @@ -164,15 +164,6 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- group = debugging ) - val detailedIR: ScallopOption[Boolean] = toggle( - "detailed-ir", - descrYes = "Print detailed IR with all annotations", - default = Some(false), - noshort = true, - prefix = "no-", - group = debugging - ) - val showDocumentation: ScallopOption[Boolean] = toggle( "show-documentation", descrYes = "Show all documented statements as a JSON", @@ -272,8 +263,6 @@ class EffektConfig(args: Seq[String]) extends REPLConfig(args.takeWhile(_ != "-- def timed(): Boolean = time.isSupplied && !server() - def printDetailedIR(): Boolean = detailedIR() - validateFilesIsDirectory(includePath) // force some other configs manually to initialize them when compiling with native-image diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index cfd2b8f287..b740574c14 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -370,7 +370,7 @@ class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { /** * Instance of PrettyPrinter that produces output that can be parsed back by the core parser. - * This corresponds to the `--detailed-ir` command line flag. + * This can be enabled using the `--debug` command line flag. */ object ReparsablePrettyPrinter extends PrettyPrinter(true) {} diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala index ca97dd519d..08700a6eb7 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -36,7 +36,7 @@ trait ChezScheme extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez") override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.debug()).format(res.core) } case Stage.CPS => None case Stage.Machine => None case Stage.Target => Separate(source).map { res => pretty(res) } diff --git a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala index 33d1d0351f..1260bc4625 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -14,8 +14,8 @@ class ChezSchemeCPS extends Compiler[String] { override def supportedFeatureFlags: List[String] = List("chez", "chezCPS") override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.printDetailedIR()).format(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.debug()).format(res) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.debug()).format(res.core) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } case Stage.Machine => None case Stage.Target => LSP(source) diff --git a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala index d4990cef0a..1c860b065d 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -18,9 +18,9 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St override def supportedFeatureFlags: List[String] = additionalFeatureFlags ++ TransformerCps.jsFeatureFlags override def prettyIR(source: Source, stage: Stage)(using C: Context): Option[Document] = stage match { - case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.printDetailedIR()).format(res) } + case Stage.Core if C.config.optimize() => Optimized(source).map { (_, _, res) => core.PrettyPrinter(Context.config.debug()).format(res) } case Stage.CPS => CPSTransformed(source).map { (_, _, _, res) => cps.PrettyPrinter.format(res) } - case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } + case Stage.Core => Core(source).map { res => core.PrettyPrinter(Context.config.debug()).format(res.core) } case Stage.Machine => None case Stage.Target => CompileLSP(source).map { pretty } } diff --git a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala index 55fa9024ed..79c58984ba 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -18,7 +18,7 @@ class LLVM extends Compiler[String] { override def supportedFeatureFlags: List[String] = Transformer.llvmFeatureFlags override def prettyIR(source: Source, stage: Stage)(using Context): Option[Document] = stage match { - case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter(Context.config.printDetailedIR()).format(res.core) } + case Stage.Core => steps.afterCore(source).map { res => core.PrettyPrinter(Context.config.debug()).format(res.core) } case Stage.CPS => None case Stage.Machine => steps.afterMachine(source).map { res => machine.PrettyPrinter.format(res.program) } case Stage.Target => steps.afterLLVM(source).map { res => pretty(res) } From 8630bc9ec0e7bd7e804b459fde43001af2298bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Mon, 15 Dec 2025 13:45:45 +0100 Subject: [PATCH 10/11] Fix TestRenamerTests --- .../src/test/scala/effekt/core/TestRenamerTests.scala | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala index 9b0357c7f5..0f5b459b33 100644 --- a/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala +++ b/effekt/jvm/src/test/scala/effekt/core/TestRenamerTests.scala @@ -31,7 +31,7 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def _renamed_0() = { + |def foo_renamed_0() = { | return (bar: (Int) => Int @ {})(baz: Int) |} |""".stripMargin @@ -118,13 +118,12 @@ class TestRenamerTests extends CoreTests { |} | |def foo_renamed_2() = { - | 12 match { + | 12 match[Int] { | X : { (aa_renamed_3: Int, bb_renamed_4: Int) => | return aa_renamed_3: Int | } | } - |} - |""".stripMargin + |}""".stripMargin assertRenamedTo(input, expected) } @@ -139,7 +138,7 @@ class TestRenamerTests extends CoreTests { val expected = """module main | - |def foo_renamed_0['A_renamed_1](a_renamed_2: A_renamed_1) = { + |def foo_renamed_0['A_renamed_1](a_renamed_2: Identity[A_renamed_1]) = { | return a_renamed_2: Identity[A_renamed_1] |} |""".stripMargin From 4f5d6b6674c6cdebb7af6b1c7da5144066a6f9e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20S=C3=BCberkr=C3=BCb?= Date: Tue, 16 Dec 2025 16:34:54 +0100 Subject: [PATCH 11/11] Always print internal Barendregt id --- .../shared/src/main/scala/effekt/core/PrettyPrinter.scala | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala index b740574c14..1f3f9c82b4 100644 --- a/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala +++ b/effekt/shared/src/main/scala/effekt/core/PrettyPrinter.scala @@ -9,7 +9,7 @@ import kiama.output.PrettyPrinterTypes.Document import scala.language.implicitConversions import effekt.symbols.{ Name, Wildcard, builtins } -class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { +class PrettyPrinter(printDetails: Boolean, printInternalIds: Boolean = true) extends ParenPrettyPrinter { override val defaultIndent = 2 def format(t: ModuleDecl): Document = @@ -124,7 +124,7 @@ class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { // In reparsable mode, we just show the string part, which should be freshened by the TestRenamer before printing. // The TestRenamer does not rename the Barendregt id because that would violate the internal invariant of having // just a single global Barendregt namespace. - builtins.coreBuiltinSymbolToString(s).getOrElse(if printDetails then s.name.name else s.show) + builtins.coreBuiltinSymbolToString(s).getOrElse(if printInternalIds then s.show else s.name.name) } def toDoc(e: Expr): Doc = e match { @@ -370,9 +370,8 @@ class PrettyPrinter(printDetails: Boolean) extends ParenPrettyPrinter { /** * Instance of PrettyPrinter that produces output that can be parsed back by the core parser. - * This can be enabled using the `--debug` command line flag. */ -object ReparsablePrettyPrinter extends PrettyPrinter(true) {} +object ReparsablePrettyPrinter extends PrettyPrinter(true, false) {} /** * Instance of PrettyPrinter that produces less verbose, more human-readable output.