From 3b25d3f075b686f8178aeb1631fdce30684f0f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 15 Apr 2026 22:13:30 +0200 Subject: [PATCH 1/6] Draft implementation of a Repr pass --- .../src/main/scala/effekt/core/Repr.scala | 345 ++++++++++++++++++ .../effekt/core/optimizer/Deadcode.scala | 4 +- .../effekt/generator/chez/ChezScheme.scala | 2 +- .../effekt/generator/chez/ChezSchemeCPS.scala | 2 +- .../effekt/generator/js/JavaScript.scala | 2 +- .../scala/effekt/generator/llvm/LLVM.scala | 2 +- .../main/scala/effekt/generator/vm/VM.scala | 2 +- examples/stdlib/repr.check | 17 + examples/stdlib/repr.effekt | 56 +++ libraries/common/array.effekt | 3 + libraries/common/bytearray.effekt | 29 ++ libraries/common/effekt.effekt | 44 +++ libraries/common/list.effekt | 3 + libraries/common/map.effekt | 12 + libraries/common/repr.effekt | 112 ++++++ libraries/common/set.effekt | 4 + 16 files changed, 632 insertions(+), 7 deletions(-) create mode 100644 effekt/shared/src/main/scala/effekt/core/Repr.scala create mode 100644 examples/stdlib/repr.check create mode 100644 examples/stdlib/repr.effekt create mode 100644 libraries/common/repr.effekt diff --git a/effekt/shared/src/main/scala/effekt/core/Repr.scala b/effekt/shared/src/main/scala/effekt/core/Repr.scala new file mode 100644 index 000000000..14f4bc5cf --- /dev/null +++ b/effekt/shared/src/main/scala/effekt/core/Repr.scala @@ -0,0 +1,345 @@ +package effekt +package core + +import scala.collection.mutable +import effekt.PhaseResult.CoreTransformed +import effekt.context.Context +import effekt.util.Trampoline +import effekt.util.messages.ErrorMessageReifier + +/** + * Synthesizes `repr: (A) {rose[Label, Unit]} => Unit` functions, one per distinct [[ValueType]] `A` encountered. + * + * Every call `repr[T](x)` in the program is rewritten to an invocation of a + * concrete, per-type function that performs `$cap.rose(label) { children }` calls directly in Core IR. + */ +object Repr extends Phase[CoreTransformed, CoreTransformed] { + override val phaseName: String = "repr" + + private val REPR = "repr" + private val REPR_BUILTIN = "reprBuiltin" + private val ROSE_INTERFACE = "rose" + private val ROSE_OPERATION = "labelled" + private val LABEL_TYPE = "Llabel" + + /** Names that DCE must preserve for the Repr pass to function. */ + def requiredNames(core: ModuleDecl)(using Context): Set[Id] = { + val dctx = DeclarationContext(core.declarations, core.externs) + + val roseIds = dctx.interfaces.values + .filter(_.id.name.name == ROSE_INTERFACE) + .flatMap(i => i.id +: i.properties.map(_.id)).toSet + + val labelIds = dctx.datas.values + .filter(_.id.name.name == LABEL_TYPE) + .flatMap(d => d.id +: d.constructors.map(_.id)).toSet + + val builtinIds = core.definitions.collect { + case Toplevel.Def(id, _) if id.name.name == REPR_BUILTIN => id + }.toSet + + roseIds ++ labelIds ++ builtinIds + } + + case class RoseMetadata( + operationId: Id, + interfaceType: BlockType.Interface, + operationType: BlockType, + capParam: BlockParam + ) { + def capVar: Block.BlockVar = + Block.BlockVar(capParam.id, interfaceType, Set.empty) + } + + object RoseMetadata { + def apply(using dctx: DeclarationContext)(using Context): RoseMetadata = { + val ifaceDecl = dctx.interfaces.values + .find(_.id.name.name == ROSE_INTERFACE) + .getOrElse(Context.abort( + pretty"Repr phase: cannot find '${ROSE_INTERFACE}' interface. Is the 'effekt.effekt' module imported?")) + + val op = ifaceDecl.properties + .find(_.id.name.name == ROSE_OPERATION) + .getOrElse(Context.abort( + pretty"Repr phase: '${ROSE_INTERFACE}' interface has no '${ROSE_OPERATION}' operation.")) + + val labelDecl = dctx.datas.values + .find(_.id.name.name == LABEL_TYPE) + .getOrElse(Context.abort( + pretty"Repr phase: cannot find '${LABEL_TYPE}' data type. Is the 'effekt.effekt' module imported?")) + + val labelTpe = ValueType.Data(labelDecl.id, Nil) + val ifaceTpe: BlockType.Interface = BlockType.Interface(ifaceDecl.id, List(labelTpe, Type.TUnit)) + + val tparamSubst: Map[Id, ValueType] = (ifaceDecl.tparams zip List(labelTpe, Type.TUnit)).toMap + val opTpe = Type.substitute(op.tpe, tparamSubst, Map.empty) + + val capId = Id("$rose") + val capParam = BlockParam(capId, ifaceTpe, Set.empty) + + RoseMetadata(op.id, ifaceTpe, opTpe, capParam) + } + } + + case class LabelMetadata( + decl: Declaration.Data, + dataTpe: ValueType.Data + ) { + private def ctor(name: String)(using Context): Constructor = + decl.constructors.find(_.id.name.name == name).getOrElse( + Context.abort(pretty"Repr phase: cannot find Label constructor '${name}'")) + + private def make(ctorName: String, fields: List[Expr])(using Context): Expr = + Expr.Make(dataTpe, ctor(ctorName).id, Nil, fields) + + inline def app(name: String)(using Context): Expr = + make("App", List(Expr.Literal(name, Type.TString))) + inline def prop(name: String)(using Context): Expr = + make("Prop", List(Expr.Literal(name, Type.TString))) + inline def opaque(name: String)(using Context): Expr = + make("Opaque", List(Expr.Literal(name, Type.TString))) + inline def literal(text: String)(using Context): Expr = + make("Literal", List(Expr.Literal(text, Type.TString))) + } + + object LabelMetadata { + def apply(using dctx: DeclarationContext)(using Context): LabelMetadata = { + val decl = dctx.datas.values + .find(_.id.name.name == LABEL_TYPE) + .getOrElse(Context.abort( + pretty"Repr phase: cannot find '${LABEL_TYPE}'. Is the 'effekt.effekt' module imported?")) + + LabelMetadata(decl, ValueType.Data(decl.id, Nil)) + } + } + + /** Small DSL for building the Core IR fragments used in generated repr code. */ + private object Emit { + inline def v(id: Id, tpe: ValueType): Expr = Expr.ValueVar(id, tpe) + inline def retUnit: Stmt = Stmt.Return(Expr.Literal((), Type.TUnit)) + + /** `$cap.rose(label) { () => childrenBody }` */ + inline def invokeRose(cap: Block.BlockVar, label: Expr)(inline childrenBody: => Stmt)(using ctx: ReprContext): Stmt = { + val childrenBlock = BlockLit(Nil, Nil, Nil, Nil, childrenBody) + Stmt.Invoke(cap, ctx.rose.operationId, ctx.rose.operationType, List(), List(label), List(childrenBlock)) + } + + /** `$cap.rose(label) { () }` (empty children). */ + inline def invokeRoseLeaf(cap: Block.BlockVar, label: Expr)(using ReprContext): Stmt = + invokeRose(cap, label) { retUnit } + + /** `reprFn(valueExpr) { cap }`, call a repr function with the rose capability. */ + inline def callRepr(reprFn: Block.BlockVar, valueExpr: Expr, cap: Block.BlockVar): Stmt = + Stmt.App(reprFn, Nil, List(valueExpr), List(cap)) + } + + /** Central context for the phase, contains metadata + a counter */ + class ReprContext( + val reprNames: mutable.Map[ValueType, Id] = mutable.Map.empty, + val reprDefns: mutable.Map[ValueType, Toplevel.Def] = mutable.Map.empty, + val tparamLookup: mutable.Map[Id, ValueType] = mutable.Map.empty, + val defsByName: Map[String, List[Toplevel]], + val rose: RoseMetadata, + val label: LabelMetadata, + private var _counter: Int = 0 + ) { + def generatedDefs: List[Toplevel.Def] = reprDefns.values.toList + def freshReprId: Id = { val n = _counter; _counter += 1; Id(s"repr$n") } + + /** `(vt) { rose[Label, Unit] } => Unit` — the type of every generated repr fn. */ + inline def reprFunctionType(vt: ValueType): BlockType = + BlockType.Function(Nil, List(rose.capParam.id), List(vt), List(rose.interfaceType), Type.TUnit) + } + + override def run(input: CoreTransformed)(using Context): Option[CoreTransformed] = input match { + case CoreTransformed(source, tree, mod, core) => + given dctx: DeclarationContext = DeclarationContext(core.declarations, core.externs) + given ctx: ReprContext = new ReprContext( + defsByName = core.definitions.groupBy(_.id.name.name), + rose = RoseMetadata(using dctx), + label = LabelMetadata(using dctx), + ) + val rewriter = new ReprRewrite() + val transformed = rewriter.rewrite(core) + Some(CoreTransformed(source, tree, mod, transformed)) + } + + private class ReprRewrite(using ctx: ReprContext, C: Context, dctx: DeclarationContext) + extends core.Tree.TrampolinedRewrite { + + override def rewrite(stmt: Stmt): Trampoline[Stmt] = stmt match { + // intercept `repr[T](vargs; bargs)` + case Stmt.App(Block.BlockVar(bid, _, _), List(targ), vargs, bargs) if bid.name.name == REPR => + for { + vargs2 <- all(vargs, rewrite) + bargs2 <- all(bargs, rewrite) + } yield Stmt.App(getReprBlockVar(targ), Nil, vargs2, bargs2) + + case other => super.rewrite(other) + } + + // Override ModuleDecl to append generated defs at the end + override def rewrite(m: ModuleDecl): ModuleDecl = { + val base = super.rewrite(m) + base.copy(definitions = base.definitions ++ ctx.generatedDefs) + } + } + + private def getReprBlockVar(vt: ValueType)(using ctx: ReprContext)(using Context, DeclarationContext): Block.BlockVar = { + val id = ctx.reprNames.getOrElse(vt, generateReprFor(vt)) + Block.BlockVar(id, ctx.reprFunctionType(vt), Set(ctx.rose.capParam.id)) + } + + /** + * Allocate a fresh id, build `def reprN(value: paramTpe) { $cap }: Unit`, + * and register it. The id is registered *before* evaluating [[body]] so that + * recursive types that call [[getReprBlockVar]] inside [[body]] find the id. + */ + private def makeReprDef(vt: ValueType, paramTpe: ValueType)(body: (Id, Block.BlockVar) => Stmt)(using ctx: ReprContext)(using Context): Toplevel.Def = { + val fid = ctx.freshReprId + ctx.reprNames += (vt -> fid) + val pId = Id("value") + val cap = ctx.rose.capParam + val defn: Toplevel.Def = Toplevel.Def(fid, + BlockLit(Nil, List(cap.id), List(ValueParam(pId, paramTpe)), List(cap), + body(pId, ctx.rose.capVar))) + ctx.reprDefns += (vt -> defn) + defn + } + + /** + * Generate (and register) a repr function for [[vt]], provided it doesn't exist already. + */ + private def generateReprFor(vt: ValueType)(using ctx: ReprContext, dctx: DeclarationContext)(using Context): Id = vt match { + case ValueType.Data(name, targs) => + val resolved = targs map lookupType + val dataTpe = ValueType.Data(name, resolved) + + findReprBuiltinFor(name, resolved) match { + // 1. Prefer an explicit 'reprBuiltin' for 'name' + case Some(bv) => + val reprBlocks: List[Block] = resolved.map(getReprBlockVar) + makeReprDef(vt, dataTpe) { (pId, cap) => + Stmt.App(bv, resolved, List(Emit.v(pId, dataTpe)), reprBlocks :+ cap) + }.id + + case None => + // 2. Otherwise, try a structural derivation from Declaration.Data + dctx.datas.get(name) match { + case Some(dataDecl) if dataDecl.constructors.nonEmpty => + ctx.tparamLookup ++= (dataDecl.tparams zip resolved) + // Register the id before building the body: recursive field + // types may call getReprBlockVar(dataTpe) during derivation. + val fid = ctx.freshReprId + ctx.reprNames += (vt -> fid) + val defn = deriveStructurally(dataDecl, fid, resolved, dataTpe) + ctx.reprDefns += (vt -> defn) + defn.id + + // 3. No data declaration and no 'reprBuiltin' ~> opaque fallback (for externs) + case _ => + makeReprDef(vt, dataTpe) { (_, cap) => + Emit.invokeRoseLeaf(cap, ctx.label.opaque(name.name.name)) + }.id + } + } + + case ValueType.Var(name) => + val concrete = ctx.tparamLookup.getOrElse(name, + Context.abort(pretty"Repr phase: unbound type variable '${name}'; too much type indirection?")) + + ctx.reprNames.get(concrete) match { + case Some(existingId) => + ctx.reprNames += (vt -> existingId) // alias only; no new def! + existingId + case None => + val inner = generateReprFor(concrete) + ctx.reprNames.get(concrete).foreach { id => ctx.reprNames += (vt -> id) } + inner + } + + // boxed ~> opaque + case ValueType.Boxed(_, _) => + makeReprDef(vt, vt) { (_, cap) => + Emit.invokeRoseLeaf(cap, ctx.label.opaque(s"box")) + }.id + } + + /** + * Build a repr function that matches on all constructors of [[decl]]. + * + * Generated structure for `type Foo { Bar(x: Int, y: Bool) ; Baz() }`: + * {{{ + * def repr0(value: Foo) { $cap: rose[Label, Unit] }: Unit = value match { + * case Bar(x, y) => + * $cap.rose(App("Bar")) { + * val _unit_1 = $cap.rose(Prop("x")) { repr1(x) { $cap } } + * val _unit_2 = $cap.rose(Prop("y")) { repr2(y) { $cap } } + * () + * } + * case Baz() => + * val _unit_3 = $cap.rose(App("Baz")) { () } + * () + * } + * }}} + */ + private def deriveStructurally(decl: Declaration.Data, fid: Id, targs: List[ValueType], dataTpe: ValueType)(using ctx: ReprContext, dctx: DeclarationContext)(using Context): Toplevel.Def = { + val valueId = Id("value") + val cap = ctx.rose.capParam + val capVar = ctx.rose.capVar + + val clauses: List[(Id, BlockLit)] = decl.constructors.map { constr => + val fieldParams = constr.fields.map { + case Field(fid, tpe) => ValueParam(fid, lookupType(tpe)) + } + constr.id -> BlockLit(Nil, Nil, fieldParams, Nil, + constructorBranch(constr.id.name.name, fieldParams, capVar)) + } + + Toplevel.Def(fid, + BlockLit(Nil, List(cap.id), List(ValueParam(valueId, dataTpe)), List(cap), + Stmt.Match(Emit.v(valueId, dataTpe), Type.TUnit, clauses, None))) + } + + /** + * Build one match branch body for a constructor. + * + * Uses `foldRight` so the last field is a tail [[Stmt.Invoke]], and all + * preceding fields are sequenced with [[Emit.seq]]. + * The whole children body is wrapped in `$cap.rose(App(name)) { … }`. + */ + private def constructorBranch(ctorName: String, fields: List[ValueParam], cap: Block.BlockVar)(using ctx: ReprContext, dctx: DeclarationContext)(using Context): Stmt = + Emit.invokeRose(cap, ctx.label.app(ctorName)) { + val bindings = fields.map { case ValueParam(fid, ftpe) => + val rhs = Emit.invokeRose(cap, ctx.label.prop(fid.name.name)) { + Emit.callRepr(getReprBlockVar(ftpe), Emit.v(fid, ftpe), cap) + } + Binding.Val(Id("_unit"), rhs) + } + Binding(bindings, Emit.retUnit) + } + + /** Find `def reprBuiltin[A,…](value: D[A,…]) { reprA } … { $cap }: Unit` for a data type. */ + private def findReprBuiltinFor(name: Id, targs: List[ValueType])(using ctx: ReprContext, dctx: DeclarationContext)(using Context): Option[Block.BlockVar] = { + val expectedBparams = targs.length + 1 // one per each targ + one extra for the capability + + def matches(vps: List[ValueParam], bps: List[BlockParam]): Boolean = + bps.length == expectedBparams && (vps match { + case List(ValueParam(_, ValueType.Data(n, _))) => n == name + case _ => false + }) + + ctx.defsByName.getOrElse(REPR_BUILTIN, Nil).collectFirst { + case Toplevel.Def(id, lit @ BlockLit(tps, cps, vps, bps, _)) if matches(vps, bps) => + Block.BlockVar(id, + BlockType.Function(tps, cps, vps.map(_.tpe), bps.map(_.tpe), Type.TUnit), cps.toSet) + } + } + + /** Recursively substitute type variables via the current `tparamLookup`. */ + private def lookupType(vt: ValueType)(using ctx: ReprContext): ValueType = vt match { + case ValueType.Data(name, targs) => ValueType.Data(name, targs map lookupType) + case ValueType.Var(name) => ctx.tparamLookup.getOrElse(name, vt) + case _ => vt + } +} \ No newline at end of file diff --git a/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala b/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala index 20a1e5f94..f5ec2d389 100644 --- a/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala +++ b/effekt/shared/src/main/scala/effekt/core/optimizer/Deadcode.scala @@ -79,9 +79,9 @@ object Deadcode extends Phase[CoreTransformed, CoreTransformed] { input match { case CoreTransformed(source, tree, mod, core) => val term = Context.ensureMainExists(mod) - // when ran "directly" (i.e., before the 'Show' pass), + // when ran "directly" (i.e., before the 'Show' and 'Repr' passes), // we add the functions required by subsequent passes - val required = Set(term) ++ Show.requiredNames(core) + val required = Set(term) ++ Show.requiredNames(core) ++ Repr.requiredNames(core) val dce = Context.timed("deadcode-elimination", source.name) { Deadcode.remove(required, core) } 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 32e3aa06a..91b372a62 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezScheme.scala @@ -55,7 +55,7 @@ trait ChezScheme extends Compiler[String] { // ------------------------ // Source => Core => Chez lazy val Compile = - allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen Optimizer andThen Chez map { case (main, expr) => + allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen core.Repr andThen Optimizer andThen Chez map { case (main, expr) => (Map(main -> pretty(expr).layout), main) } 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 a04a93a0a..582af9632 100644 --- a/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala +++ b/effekt/shared/src/main/scala/effekt/generator/chez/ChezSchemeCPS.scala @@ -37,7 +37,7 @@ class ChezSchemeCPS extends Compiler[String] { Frontend andThen Middleend } - lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen Optimizer map { + lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen core.Repr andThen Optimizer map { case input @ CoreTransformed(source, tree, mod, core) => val mainSymbol = Context.ensureMainExists(mod) val mainFile = path(mod) 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 81f2532e4..81b65b62f 100644 --- a/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala +++ b/effekt/shared/src/main/scala/effekt/generator/js/JavaScript.scala @@ -44,7 +44,7 @@ class JavaScript(additionalFeatureFlags: List[String] = Nil) extends Compiler[St Frontend andThen Middleend } - lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen Optimizer andThen DropBindings map { + lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen core.Repr andThen Optimizer andThen DropBindings map { case input @ CoreTransformed(source, tree, mod, core) => val mainSymbol = Context.ensureMainExists(mod) val mainFile = path(mod) 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 1620e3c61..f563eaa28 100644 --- a/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/llvm/LLVM.scala @@ -57,7 +57,7 @@ class LLVM extends Compiler[String] { // 1. Split off Deadcode from Optimizer // 2. Do Show (hope it still runs) // 3. Run Optimizer - val afterCore = allToCore(Core) andThen Aggregate andThen optimizer.Deadcode andThen core.Show andThen optimizer.Optimizer + val afterCore = allToCore(Core) andThen Aggregate andThen optimizer.Deadcode andThen core.Show andThen core.Repr andThen optimizer.Optimizer val afterMachine = afterCore andThen Machine map { case (mod, main, prog) => prog } val afterLLVM = afterMachine map { case machine.Program(decls, defns, entry) => diff --git a/effekt/shared/src/main/scala/effekt/generator/vm/VM.scala b/effekt/shared/src/main/scala/effekt/generator/vm/VM.scala index ab4521ac0..f9ca8535b 100644 --- a/effekt/shared/src/main/scala/effekt/generator/vm/VM.scala +++ b/effekt/shared/src/main/scala/effekt/generator/vm/VM.scala @@ -37,7 +37,7 @@ class VM extends Compiler[(Id, symbols.Module, ModuleDecl)] { Frontend andThen Middleend } - lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen core.optimizer.Optimizer map { + lazy val Optimized = allToCore(Core) andThen Aggregate andThen Deadcode andThen core.Show andThen core.Repr andThen core.optimizer.Optimizer map { case input @ CoreTransformed(source, tree, mod, core) => val mainSymbol = Context.ensureMainExists(mod) (mainSymbol, mod, core) diff --git a/examples/stdlib/repr.check b/examples/stdlib/repr.check new file mode 100644 index 000000000..6a69c0aac --- /dev/null +++ b/examples/stdlib/repr.check @@ -0,0 +1,17 @@ +x = 3 +A(x = 3, y = false) +A(x = 3, y = false) +A(x = 3, y = A(x = 4, y = A(x = 3, y = false))) +B(f = ) +C(array = ) +D(f = , list = [10, 15, 3], array = , )>, bytes = ) +Nil() +Cons(head = 3, tail = Cons(head = 4, tail = Nil())) +Cons(head = "hi", tail = Cons(head = "there", tail = Nil())) +Cons(head = (), tail = Cons(head = (), tail = Nil())) + + +E(there = "one", 2 !-> "two")>, back = 1, "two" !-> 2)>) +F(s = ) +A(x = 3, y = A(x = 4, y = A(x = 3, y = false))) +B(f = ) \ No newline at end of file diff --git a/examples/stdlib/repr.effekt b/examples/stdlib/repr.effekt new file mode 100644 index 000000000..64117cd47 --- /dev/null +++ b/examples/stdlib/repr.effekt @@ -0,0 +1,56 @@ +import repr +import map +import set + +extern type FooBarBaz + +extern def mkFooBarBaz(): FooBarBaz = + js "(42)" + +type MyType[T] { + A(x: Int, y: T) + B(f: Int => Int at {}) + C(array: Array[Int]) + D(f: Int => Int at {}, list: List[Int], array: Array[Array[Int]], bytes: ByteArray) + E(there: Map[Int, T], back: Map[T, Int]) + F(s: Set[MyType[T]]) +} + +type MyList[T] { + Nil() + Cons(head: T, tail: MyList[T]) +} + +def dump { rpr: () => Unit / rose[Llabel, Unit]}: Unit = println(toString { rpr() }) + +def main() = { + dump { do labelled(Llabel::Prop("x")) { do labelled(Llabel::Int(3)) { () } } } + + val fst = A(3, false); dump { repr(fst) } + + def reprrrr() = repr(fst); dump { reprrrr() } + + dump { repr(A(3, A(4, fst))) } + dump { repr(B[Nothing](box { n => n + 1 })) } + dump { repr(C[Nothing]([1, 2, 3, 4].fromList)) } + dump { repr(D[Nothing](box { n => n + 1 }, [10, 15, 3], [[1, 2].fromList, [3, 4].fromList].fromList, "ABC".fromString)) } + dump { repr(MyList::Nil[Int]()) } + dump { repr(MyList::Cons(3, MyList::Cons(4, MyList::Nil()))) } + dump { repr(MyList::Cons("hi", MyList::Cons("there", MyList::Nil()))) } + dump { MyList::Cons((), MyList::Cons((), MyList::Nil())).repr } + dump { mkFooBarBaz().repr } + + try { + dump { repr(box e) } + } with e: emit[Int] { v => resume(()) } + + dump { E([(1, "one"), (2, "two")].fromListGeneric, [("one", 1), ("two", 2)].fromListGeneric).repr } + dump { F([A(3, fst), A(4, fst)].fromListGeneric).repr } + + val (_, trees) = reify[Llabel, Unit] { + val _ = (repr(A(3, A(4, fst))), repr(B[Nothing](box { n => n + 1 }))) + } + trees.foreach { tree => + println(tree.toUniversalString) + } +} diff --git a/libraries/common/array.effekt b/libraries/common/array.effekt index caca559ec..d8d6407da 100644 --- a/libraries/common/array.effekt +++ b/libraries/common/array.effekt @@ -646,6 +646,9 @@ def println(l: Array[Double]): Unit = println(showBuiltin(l)) def println(l: Array[Bool]): Unit = println(showBuiltin(l)) def println(l: Array[String]): Unit = println(showBuiltin(l)) +def reprBuiltin[A](array: Array[A]) { reprA: A => Unit / rose[Llabel, Unit] }: Unit / rose[Llabel, Unit] + = do labelled[Llabel, Unit](Llabel::Opaque("Array")) { array.foreach { a => reprA(a) } } // TODO FIXME + // Streaming // --------- diff --git a/libraries/common/bytearray.effekt b/libraries/common/bytearray.effekt index a877f9377..f0bf756e1 100644 --- a/libraries/common/bytearray.effekt +++ b/libraries/common/bytearray.effekt @@ -4,6 +4,7 @@ import effekt import stream import control import array +import list /** * A memory managed, mutable, fixed-length array of bytes. @@ -266,6 +267,34 @@ def compareStringBytes(left: String, right: String): Ordering = { compareByteArray(l, r) } +// Conversion +// ---------- + +def toArray(bytes: ByteArray): Array[Byte] = + array::build(bytes.size) { i => + bytes.get(i) + } + +def fromArray(arr: Array[Byte]): ByteArray = + build(arr.size) { i => + arr.get(i) + } + +def toList(bytes: ByteArray): List[Byte] = + list::build(bytes.size) { i => + bytes.get(i) + } + +def fromList(lst: List[Byte]): ByteArray = { + val arr = allocate(lst.size) + lst.foreachIndex { (i, byte) => + arr.set(i, byte) + } + arr +} + +def reprBuiltin(bytes: ByteArray): Unit / rose[Llabel, Unit] = + do labelled[Llabel, Unit](Llabel::Opaque("ByteArray")) { bytes.foreach { b => reprBuiltin(b) } } // Streaming // --------- diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 5871f9d9c..f87adb41d 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -843,3 +843,47 @@ effect write(s: String): Unit effect splice[A](x: A): Unit +// Repr +// ==== + +interface rose[A, R] { + def labelled(node: A) { children: => R }: R +} + + +type Llabel { + // primitives + Int(n: Int) + Double(d: Double) + Bool(b: Bool) + Byte(b: Byte) + Char(c: Char) + String(s: String) + + // collections + List() + Assoc(name: String) + + // props + Prop(name: String) + AssocProp() + + // constructors + App(name: String) + Opaque(name: String) + Literal(name: String) + + // for pretty printing only + Omitted() +} + +def reprBuiltin(n: Int): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Int(n)) { () } +def reprBuiltin(d: Double): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Double(d)) { () } +def reprBuiltin(b: Bool): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Bool(b)) { () } +def reprBuiltin(c: Char): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Char(c)) { () } +def reprBuiltin(s: String): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::String(s)) { () } +def reprBuiltin(b: Byte): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Byte(b)) { () } +def reprBuiltin(u: Unit): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Literal("()")) { () } +def reprBuiltin(absurd: Nothing): Unit / rose[Llabel, Unit] = absurd match {} + +def repr[R](value: R): Unit / rose[Llabel, Unit] = <{ "to be replaced by the 'Repr' phase of the compiler" }> \ No newline at end of file diff --git a/libraries/common/list.effekt b/libraries/common/list.effekt index c323c949f..4c593acfe 100644 --- a/libraries/common/list.effekt +++ b/libraries/common/list.effekt @@ -974,6 +974,9 @@ def tails[A](list: List[A]): Unit / emit[List[A]] = case Cons(head, tail) => do emit(list); tails(tail) } +def reprBuiltin[A](list: List[A]) { reprA: A => Unit / rose[Llabel, Unit] }: Unit / rose[Llabel, Unit] + = do labelled[Llabel, Unit](Llabel::List()) { list.foreach { a => reprA(a) } } + /// Connects `list` as a producer to a given pull stream `driver` acting as a consumer. def feed[T, R](list: List[T]) { driver: () => R / next[T] }: R = { var l = list diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 7a5d27f83..0b356c3cb 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -993,6 +993,18 @@ namespace internal { } } +def reprBuiltin[K, V](m: Map[K, V]) + { reprK: K => Unit / rose[Llabel, Unit] } + { reprV: V => Unit / rose[Llabel, Unit] } +: Unit / rose[Llabel, Unit] = + do labelled[Llabel, Unit](Llabel::Assoc("Map")) { + m.foreach { (k, v) => + do labelled(Llabel::AssocProp()) { + reprK(k); reprV(v) + } + } + } + // Streaming // --------- diff --git a/libraries/common/repr.effekt b/libraries/common/repr.effekt new file mode 100644 index 000000000..5b2efe7fb --- /dev/null +++ b/libraries/common/repr.effekt @@ -0,0 +1,112 @@ +module repr + +import effekt +import list +import bytearray +import array +import stream + +/// Rose tree as a datatype +record Tree[A](node: A, children: List[Tree[A]]) + +def reflect[A](repr: Tree[A]): Unit / rose[A, Unit] = { + def go(r: Tree[A]): Unit / rose[A, Unit] = { + do labelled(r.node) { r.children.foreach { x => go(x) } } + } + go(repr) +} + +def reify[A, R] { program: () => R / rose[A, R] }: (R, List[Tree[A]]) = { + var acc: List[Tree[A]] = Nil() + + val res = try program() with rose[A, R] { + def labelled(label) = + resume { {children} => + val savedAcc = acc + acc = Nil() + val res = children() + val kids = acc.reverse + acc = Cons(Tree(label, kids), savedAcc) + res + } + } + + (res, acc.reverse) +} + +def reifySingle[A] { program: () => Unit / rose[A, Unit] }: Tree[A] = { + val (_, Cons(repr, Nil())) = program.reify[A, Unit] else panic("repr: expected exactly one repr") + repr +} + +namespace Repr { + type Repr = Tree[Llabel] + + def leaf(label: Llabel): Repr = Tree(label, []) + def int(n: Int): Repr = Int(n).leaf + def double(d: Double): Repr = Double(d).leaf + def bool(b: Bool): Repr = Bool(b).leaf + def string(s: String): Repr = String(s).leaf + def byte(b: Byte): Repr = Byte(b).leaf + def char(c: Char): Repr = Char(c).leaf + + def array(a: Array[Repr]): Repr = collection("Array", a.toList) + def list(l: List[Repr]): Repr = Tree(List(), l) + def byteArray(b: ByteArray): Repr = collection("ByteArray", b.toList.map {byte}) + + def constructorNamed(name: String, a: List[(String, Repr)]): Repr = + Tree(App(name), a.map { case (name, value) => Tree(Prop(name), [value]) }) + def constructor(name: String, args: List[Repr]): Repr = + Tree(App(name), args) + def opaque(name: String, child: Repr): Repr = + Tree(Opaque(name), [child]) + def opaque(name: String): Repr = + Tree(Opaque(name), []) + def opaqueLiteral(name: String, value: String): Repr = + Tree(Opaque(name), [Tree(Literal(value), [])]) + def collection(name: String, contents: List[Repr]): Repr = + opaque(name, list(contents)) + def assoc(name: String, contents: List[(Repr, Repr)]): Repr = + Tree(Assoc(name), contents.map { case (key, value) => Tree(AssocProp(), [key, value]) }) + + + def reflect(repr: Repr): Unit / rose[Llabel, Unit] = reflect[Llabel](repr) + + def reify[R] { program: () => R / rose[Llabel, R] }: (R, List[Tree[Llabel]]) = + reify[Llabel, R] { program() } + + def reifySingle { program: () => Unit / rose[Llabel, Unit] }: Tree[Llabel] = + reifySingle[Llabel] { program() } +} + +/// A representation of a value as a rose tree. +/// This is used for debugging, testing, and straightforward pretty-printing. +type Repr = Repr::Repr + +def toUniversalString(r: Repr): String = { + def commaSepChildren() = r.children.map { x => toUniversalString(x) }.join(", ") + + r.node match { + case Llabel::Int(n) => n.show + case Llabel::Double(d) => d.show + case Llabel::Bool(b) => b.show + case Llabel::Byte(b) => b.show + case Llabel::Char(c) => c.show + case Llabel::String(s) => "\"" ++ s ++ "\"" + case Llabel::List() => "[" ++ commaSepChildren() ++ "]" + case Llabel::App(name) => name ++ "(" ++ commaSepChildren() ++ ")" + case Llabel::Prop(name) and r.children is Cons(child, Nil()) => name ++ " = " ++ toUniversalString(child) + case Llabel::AssocProp() and r.children is Cons(key, Cons(value, Nil())) => toUniversalString(key) ++ " !-> " ++ toUniversalString(value) + case Llabel::Opaque(name) and r.children is Cons(_, _) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" + case Llabel::Opaque(name) => "<" ++ name ++ ">" + case Llabel::Assoc(name) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" + case Llabel::Literal(s) => s + case Llabel::Omitted() => "..." + case _ => panic("invalid Repr") + } +} + +def toString { reprey: () => Unit / rose[Llabel, Unit] }: String = { + val repr = reifySingle[Llabel] { reprey() } + toUniversalString(repr) +} diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 1c160d31b..9c1f22f18 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -235,6 +235,10 @@ def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = s1.intersection(s2, s1.compare) +def reprBuiltin[A](s: Set[A]) { reprA: A => Unit / rose[Llabel, Unit]}: Unit / rose[Llabel, Unit] = + do labelled[Llabel, Unit](Llabel::Opaque("Set")) { s.foreach { a => reprA(a) } } + + // Streaming // --------- From 9dc00386218691a29bc23b9deeebc82abc2a2fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 15 Apr 2026 22:18:18 +0200 Subject: [PATCH 2/6] Add new 'repr' module to 'acme' --- examples/stdlib/acme.effekt | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/stdlib/acme.effekt b/examples/stdlib/acme.effekt index 646569170..2c0d95d45 100644 --- a/examples/stdlib/acme.effekt +++ b/examples/stdlib/acme.effekt @@ -35,6 +35,7 @@ import queue import random import ref import regex +import repr import resizable_array import result import scanner From 2ea31839d102a044ec1bc5927996da983997a9cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 15 Apr 2026 22:31:05 +0200 Subject: [PATCH 3/6] Put label into its own namespace to prevent pollution --- examples/stdlib/repr.effekt | 6 +-- libraries/common/array.effekt | 4 +- libraries/common/bytearray.effekt | 4 +- libraries/common/effekt.effekt | 69 ++++++++++++++++--------------- libraries/common/list.effekt | 4 +- libraries/common/map.effekt | 10 ++--- libraries/common/repr.effekt | 63 ++++++++++++++-------------- libraries/common/set.effekt | 4 +- 8 files changed, 83 insertions(+), 81 deletions(-) diff --git a/examples/stdlib/repr.effekt b/examples/stdlib/repr.effekt index 64117cd47..6de3db894 100644 --- a/examples/stdlib/repr.effekt +++ b/examples/stdlib/repr.effekt @@ -21,10 +21,10 @@ type MyList[T] { Cons(head: T, tail: MyList[T]) } -def dump { rpr: () => Unit / rose[Llabel, Unit]}: Unit = println(toString { rpr() }) +def dump { rpr: () => Unit / rose[Repr::Llabel, Unit]}: Unit = println(toString { rpr() }) def main() = { - dump { do labelled(Llabel::Prop("x")) { do labelled(Llabel::Int(3)) { () } } } + dump { do labelled(Repr::Llabel::Prop("x")) { do labelled(Repr::Llabel::Int(3)) { () } } } val fst = A(3, false); dump { repr(fst) } @@ -47,7 +47,7 @@ def main() = { dump { E([(1, "one"), (2, "two")].fromListGeneric, [("one", 1), ("two", 2)].fromListGeneric).repr } dump { F([A(3, fst), A(4, fst)].fromListGeneric).repr } - val (_, trees) = reify[Llabel, Unit] { + val (_, trees) = Repr::reify[Unit] { val _ = (repr(A(3, A(4, fst))), repr(B[Nothing](box { n => n + 1 }))) } trees.foreach { tree => diff --git a/libraries/common/array.effekt b/libraries/common/array.effekt index d8d6407da..42f551541 100644 --- a/libraries/common/array.effekt +++ b/libraries/common/array.effekt @@ -646,8 +646,8 @@ def println(l: Array[Double]): Unit = println(showBuiltin(l)) def println(l: Array[Bool]): Unit = println(showBuiltin(l)) def println(l: Array[String]): Unit = println(showBuiltin(l)) -def reprBuiltin[A](array: Array[A]) { reprA: A => Unit / rose[Llabel, Unit] }: Unit / rose[Llabel, Unit] - = do labelled[Llabel, Unit](Llabel::Opaque("Array")) { array.foreach { a => reprA(a) } } // TODO FIXME +def reprBuiltin[A](array: Array[A]) { reprA: A => Unit / rose[Repr::Llabel, Unit] }: Unit / rose[Repr::Llabel, Unit] + = do labelled[Repr::Llabel, Unit](Repr::Llabel::Opaque("Array")) { array.foreach { a => reprA(a) } } // Streaming // --------- diff --git a/libraries/common/bytearray.effekt b/libraries/common/bytearray.effekt index f0bf756e1..d785979c0 100644 --- a/libraries/common/bytearray.effekt +++ b/libraries/common/bytearray.effekt @@ -293,8 +293,8 @@ def fromList(lst: List[Byte]): ByteArray = { arr } -def reprBuiltin(bytes: ByteArray): Unit / rose[Llabel, Unit] = - do labelled[Llabel, Unit](Llabel::Opaque("ByteArray")) { bytes.foreach { b => reprBuiltin(b) } } +def reprBuiltin(bytes: ByteArray): Unit / rose[Repr::Llabel, Unit] = + do labelled[Repr::Llabel, Unit](Repr::Llabel::Opaque("ByteArray")) { bytes.foreach { b => reprBuiltin(b) } } // Streaming // --------- diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index f87adb41d..fed6412ee 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -850,40 +850,41 @@ interface rose[A, R] { def labelled(node: A) { children: => R }: R } - -type Llabel { - // primitives - Int(n: Int) - Double(d: Double) - Bool(b: Bool) - Byte(b: Byte) - Char(c: Char) - String(s: String) - - // collections - List() - Assoc(name: String) - - // props - Prop(name: String) - AssocProp() - - // constructors - App(name: String) - Opaque(name: String) - Literal(name: String) - - // for pretty printing only - Omitted() +namespace Repr { + type Llabel { + // primitives + Int(n: Int) + Double(d: Double) + Bool(b: Bool) + Byte(b: Byte) + Char(c: Char) + String(s: String) + + // collections + List() + Assoc(name: String) + + // props + Prop(name: String) + AssocProp() + + // constructors + App(name: String) + Opaque(name: String) + Literal(name: String) + + // for pretty printing only + Omitted() + } } -def reprBuiltin(n: Int): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Int(n)) { () } -def reprBuiltin(d: Double): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Double(d)) { () } -def reprBuiltin(b: Bool): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Bool(b)) { () } -def reprBuiltin(c: Char): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Char(c)) { () } -def reprBuiltin(s: String): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::String(s)) { () } -def reprBuiltin(b: Byte): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Byte(b)) { () } -def reprBuiltin(u: Unit): Unit / rose[Llabel, Unit] = do labelled[Llabel, Unit](Llabel::Literal("()")) { () } -def reprBuiltin(absurd: Nothing): Unit / rose[Llabel, Unit] = absurd match {} +def reprBuiltin(n: Int): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Int(n)) { () } +def reprBuiltin(d: Double): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Double(d)) { () } +def reprBuiltin(b: Bool): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Bool(b)) { () } +def reprBuiltin(c: Char): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Char(c)) { () } +def reprBuiltin(s: String): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::String(s)) { () } +def reprBuiltin(b: Byte): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Byte(b)) { () } +def reprBuiltin(u: Unit): Unit / rose[Repr::Llabel, Unit] = do labelled[Repr::Llabel, Unit](Repr::Llabel::Literal("()")) { () } +def reprBuiltin(absurd: Nothing): Unit / rose[Repr::Llabel, Unit] = absurd match {} -def repr[R](value: R): Unit / rose[Llabel, Unit] = <{ "to be replaced by the 'Repr' phase of the compiler" }> \ No newline at end of file +def repr[R](value: R): Unit / rose[Repr::Llabel, Unit] = <{ "to be replaced by the 'Repr' phase of the compiler" }> \ No newline at end of file diff --git a/libraries/common/list.effekt b/libraries/common/list.effekt index 4c593acfe..993bb63bf 100644 --- a/libraries/common/list.effekt +++ b/libraries/common/list.effekt @@ -974,8 +974,8 @@ def tails[A](list: List[A]): Unit / emit[List[A]] = case Cons(head, tail) => do emit(list); tails(tail) } -def reprBuiltin[A](list: List[A]) { reprA: A => Unit / rose[Llabel, Unit] }: Unit / rose[Llabel, Unit] - = do labelled[Llabel, Unit](Llabel::List()) { list.foreach { a => reprA(a) } } +def reprBuiltin[A](list: List[A]) { reprA: A => Unit / rose[Repr::Llabel, Unit] }: Unit / rose[Repr::Llabel, Unit] + = do labelled[Repr::Llabel, Unit](Repr::Llabel::List()) { list.foreach { a => reprA(a) } } /// Connects `list` as a producer to a given pull stream `driver` acting as a consumer. def feed[T, R](list: List[T]) { driver: () => R / next[T] }: R = { diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 0b356c3cb..e72c6914c 100644 --- a/libraries/common/map.effekt +++ b/libraries/common/map.effekt @@ -994,12 +994,12 @@ namespace internal { } def reprBuiltin[K, V](m: Map[K, V]) - { reprK: K => Unit / rose[Llabel, Unit] } - { reprV: V => Unit / rose[Llabel, Unit] } -: Unit / rose[Llabel, Unit] = - do labelled[Llabel, Unit](Llabel::Assoc("Map")) { + { reprK: K => Unit / rose[Repr::Llabel, Unit] } + { reprV: V => Unit / rose[Repr::Llabel, Unit] } +: Unit / rose[Repr::Llabel, Unit] = + do labelled[Repr::Llabel, Unit](Repr::Llabel::Assoc("Map")) { m.foreach { (k, v) => - do labelled(Llabel::AssocProp()) { + do labelled(Repr::AssocProp()) { reprK(k); reprV(v) } } diff --git a/libraries/common/repr.effekt b/libraries/common/repr.effekt index 5b2efe7fb..86b0be84b 100644 --- a/libraries/common/repr.effekt +++ b/libraries/common/repr.effekt @@ -40,34 +40,35 @@ def reifySingle[A] { program: () => Unit / rose[A, Unit] }: Tree[A] = { } namespace Repr { - type Repr = Tree[Llabel] + type Llabel = Repr::Llabel + type Repr = Tree[Repr::Llabel] def leaf(label: Llabel): Repr = Tree(label, []) - def int(n: Int): Repr = Int(n).leaf - def double(d: Double): Repr = Double(d).leaf - def bool(b: Bool): Repr = Bool(b).leaf - def string(s: String): Repr = String(s).leaf - def byte(b: Byte): Repr = Byte(b).leaf - def char(c: Char): Repr = Char(c).leaf + def int(n: Int): Repr = Repr::Int(n).leaf + def double(d: Double): Repr = Repr::Double(d).leaf + def bool(b: Bool): Repr = Repr::Bool(b).leaf + def string(s: String): Repr = Repr::String(s).leaf + def byte(b: Byte): Repr = Repr::Byte(b).leaf + def char(c: Char): Repr = Repr::Char(c).leaf def array(a: Array[Repr]): Repr = collection("Array", a.toList) - def list(l: List[Repr]): Repr = Tree(List(), l) + def list(l: List[Repr]): Repr = Tree(Repr::List(), l) def byteArray(b: ByteArray): Repr = collection("ByteArray", b.toList.map {byte}) def constructorNamed(name: String, a: List[(String, Repr)]): Repr = - Tree(App(name), a.map { case (name, value) => Tree(Prop(name), [value]) }) + Tree(Repr::App(name), a.map { case (name, value) => Tree(Repr::Prop(name), [value]) }) def constructor(name: String, args: List[Repr]): Repr = - Tree(App(name), args) + Tree(Repr::App(name), args) def opaque(name: String, child: Repr): Repr = - Tree(Opaque(name), [child]) + Tree(Repr::Opaque(name), [child]) def opaque(name: String): Repr = - Tree(Opaque(name), []) + Tree(Repr::Opaque(name), []) def opaqueLiteral(name: String, value: String): Repr = - Tree(Opaque(name), [Tree(Literal(value), [])]) + Tree(Repr::Opaque(name), [Tree(Repr::Literal(value), [])]) def collection(name: String, contents: List[Repr]): Repr = opaque(name, list(contents)) def assoc(name: String, contents: List[(Repr, Repr)]): Repr = - Tree(Assoc(name), contents.map { case (key, value) => Tree(AssocProp(), [key, value]) }) + Tree(Repr::Assoc(name), contents.map { case (key, value) => Tree(Repr::AssocProp(), [key, value]) }) def reflect(repr: Repr): Unit / rose[Llabel, Unit] = reflect[Llabel](repr) @@ -87,26 +88,26 @@ def toUniversalString(r: Repr): String = { def commaSepChildren() = r.children.map { x => toUniversalString(x) }.join(", ") r.node match { - case Llabel::Int(n) => n.show - case Llabel::Double(d) => d.show - case Llabel::Bool(b) => b.show - case Llabel::Byte(b) => b.show - case Llabel::Char(c) => c.show - case Llabel::String(s) => "\"" ++ s ++ "\"" - case Llabel::List() => "[" ++ commaSepChildren() ++ "]" - case Llabel::App(name) => name ++ "(" ++ commaSepChildren() ++ ")" - case Llabel::Prop(name) and r.children is Cons(child, Nil()) => name ++ " = " ++ toUniversalString(child) - case Llabel::AssocProp() and r.children is Cons(key, Cons(value, Nil())) => toUniversalString(key) ++ " !-> " ++ toUniversalString(value) - case Llabel::Opaque(name) and r.children is Cons(_, _) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" - case Llabel::Opaque(name) => "<" ++ name ++ ">" - case Llabel::Assoc(name) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" - case Llabel::Literal(s) => s - case Llabel::Omitted() => "..." + case Repr::Int(n) => n.show + case Repr::Double(d) => d.show + case Repr::Bool(b) => b.show + case Repr::Byte(b) => b.show + case Repr::Char(c) => c.show + case Repr::String(s) => "\"" ++ s ++ "\"" + case Repr::List() => "[" ++ commaSepChildren() ++ "]" + case Repr::App(name) => name ++ "(" ++ commaSepChildren() ++ ")" + case Repr::Prop(name) and r.children is Cons(child, Nil()) => name ++ " = " ++ toUniversalString(child) + case Repr::AssocProp() and r.children is Cons(key, Cons(value, Nil())) => toUniversalString(key) ++ " !-> " ++ toUniversalString(value) + case Repr::Opaque(name) and r.children is Cons(_, _) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" + case Repr::Opaque(name) => "<" ++ name ++ ">" + case Repr::Assoc(name) => "<" ++ name ++ "(" ++ commaSepChildren() ++ ")" ++ ">" + case Repr::Literal(s) => s + case Repr::Omitted() => "..." case _ => panic("invalid Repr") } } -def toString { reprey: () => Unit / rose[Llabel, Unit] }: String = { - val repr = reifySingle[Llabel] { reprey() } +def toString { reprey: () => Unit / rose[Repr::Llabel, Unit] }: String = { + val repr = Repr::reifySingle { reprey() } toUniversalString(repr) } diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 9c1f22f18..0772cff88 100644 --- a/libraries/common/set.effekt +++ b/libraries/common/set.effekt @@ -235,8 +235,8 @@ def intersection[A](s1: Set[A], s2: Set[A]): Set[A] = s1.intersection(s2, s1.compare) -def reprBuiltin[A](s: Set[A]) { reprA: A => Unit / rose[Llabel, Unit]}: Unit / rose[Llabel, Unit] = - do labelled[Llabel, Unit](Llabel::Opaque("Set")) { s.foreach { a => reprA(a) } } +def reprBuiltin[A](s: Set[A]) { reprA: A => Unit / rose[Repr::Llabel, Unit]}: Unit / rose[Repr::Llabel, Unit] = + do labelled[Repr::Llabel, Unit](Repr::Llabel::Opaque("Set")) { s.foreach { a => reprA(a) } } // Streaming From 4aaf55e4ac08b188ef52c9f078cc20ce1c36c1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 15 Apr 2026 23:15:19 +0200 Subject: [PATCH 4/6] Remove extern example --- examples/stdlib/repr.check | 1 - examples/stdlib/repr.effekt | 6 ------ 2 files changed, 7 deletions(-) diff --git a/examples/stdlib/repr.check b/examples/stdlib/repr.check index 6a69c0aac..4c79e6fef 100644 --- a/examples/stdlib/repr.check +++ b/examples/stdlib/repr.check @@ -9,7 +9,6 @@ Nil() Cons(head = 3, tail = Cons(head = 4, tail = Nil())) Cons(head = "hi", tail = Cons(head = "there", tail = Nil())) Cons(head = (), tail = Cons(head = (), tail = Nil())) - E(there = "one", 2 !-> "two")>, back = 1, "two" !-> 2)>) F(s = ) diff --git a/examples/stdlib/repr.effekt b/examples/stdlib/repr.effekt index 6de3db894..1df378f49 100644 --- a/examples/stdlib/repr.effekt +++ b/examples/stdlib/repr.effekt @@ -2,11 +2,6 @@ import repr import map import set -extern type FooBarBaz - -extern def mkFooBarBaz(): FooBarBaz = - js "(42)" - type MyType[T] { A(x: Int, y: T) B(f: Int => Int at {}) @@ -38,7 +33,6 @@ def main() = { dump { repr(MyList::Cons(3, MyList::Cons(4, MyList::Nil()))) } dump { repr(MyList::Cons("hi", MyList::Cons("there", MyList::Nil()))) } dump { MyList::Cons((), MyList::Cons((), MyList::Nil())).repr } - dump { mkFooBarBaz().repr } try { dump { repr(box e) } From d86dc5feeae104506a3e2aa9a435f6644ad8b932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Wed, 15 Apr 2026 23:18:58 +0200 Subject: [PATCH 5/6] Add a few more examples --- examples/stdlib/repr.check | 12 +++++++----- examples/stdlib/repr.effekt | 16 ++++++++++------ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/examples/stdlib/repr.check b/examples/stdlib/repr.check index 4c79e6fef..4104468b9 100644 --- a/examples/stdlib/repr.check +++ b/examples/stdlib/repr.check @@ -5,12 +5,14 @@ A(x = 3, y = A(x = 4, y = A(x = 3, y = false))) B(f = ) C(array = ) D(f = , list = [10, 15, 3], array = , )>, bytes = ) -Nil() -Cons(head = 3, tail = Cons(head = 4, tail = Nil())) -Cons(head = "hi", tail = Cons(head = "there", tail = Nil())) -Cons(head = (), tail = Cons(head = (), tail = Nil())) +MyNil() +MyCons(head = 3, tail = MyCons(head = 4, tail = MyNil())) +MyCons(head = "hi", tail = MyCons(head = "there", tail = MyNil())) +MyCons(head = (), tail = MyCons(head = (), tail = MyNil())) E(there = "one", 2 !-> "two")>, back = 1, "two" !-> 2)>) F(s = ) A(x = 3, y = A(x = 4, y = A(x = 3, y = false))) -B(f = ) \ No newline at end of file +B(f = ) +[1, 2, 3, 4, 5] +Deep(size = 10, prefix = One(value = 1), middle = DeepFinger(size = 6, prefix = One(value = Leaf3(first = 2, second = 3, third = 4)), middle = NoFinger(), suffix = One(value = Leaf3(first = 5, second = 6, third = 7))), suffix = Three(first = 8, second = 9, third = 10)) \ No newline at end of file diff --git a/examples/stdlib/repr.effekt b/examples/stdlib/repr.effekt index 1df378f49..9052293fc 100644 --- a/examples/stdlib/repr.effekt +++ b/examples/stdlib/repr.effekt @@ -1,6 +1,7 @@ import repr import map import set +import seq type MyType[T] { A(x: Int, y: T) @@ -12,8 +13,8 @@ type MyType[T] { } type MyList[T] { - Nil() - Cons(head: T, tail: MyList[T]) + MyNil() + MyCons(head: T, tail: MyList[T]) } def dump { rpr: () => Unit / rose[Repr::Llabel, Unit]}: Unit = println(toString { rpr() }) @@ -29,10 +30,10 @@ def main() = { dump { repr(B[Nothing](box { n => n + 1 })) } dump { repr(C[Nothing]([1, 2, 3, 4].fromList)) } dump { repr(D[Nothing](box { n => n + 1 }, [10, 15, 3], [[1, 2].fromList, [3, 4].fromList].fromList, "ABC".fromString)) } - dump { repr(MyList::Nil[Int]()) } - dump { repr(MyList::Cons(3, MyList::Cons(4, MyList::Nil()))) } - dump { repr(MyList::Cons("hi", MyList::Cons("there", MyList::Nil()))) } - dump { MyList::Cons((), MyList::Cons((), MyList::Nil())).repr } + dump { repr(MyList::MyNil[Int]()) } + dump { repr(MyList::MyCons(3, MyList::MyCons(4, MyList::MyNil()))) } + dump { repr(MyList::MyCons("hi", MyList::MyCons("there", MyList::MyNil()))) } + dump { MyList::MyCons((), MyList::MyCons((), MyList::MyNil())).repr } try { dump { repr(box e) } @@ -47,4 +48,7 @@ def main() = { trees.foreach { tree => println(tree.toUniversalString) } + + dump { [1, 2, 3, 4, 5].repr } + dump { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].toSeq.repr } } From 02aa516feedeb32aaf77011dc8c912e9159f2e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bene=C5=A1?= Date: Thu, 16 Apr 2026 15:37:20 +0200 Subject: [PATCH 6/6] Fix tests on non-JS backends --- examples/stdlib/repr.check | 2 +- examples/stdlib/repr.effekt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/stdlib/repr.check b/examples/stdlib/repr.check index 4104468b9..b20dcc281 100644 --- a/examples/stdlib/repr.check +++ b/examples/stdlib/repr.check @@ -11,7 +11,7 @@ MyCons(head = "hi", tail = MyCons(head = "there", tail = MyNil())) MyCons(head = (), tail = MyCons(head = (), tail = MyNil())) E(there = "one", 2 !-> "two")>, back = 1, "two" !-> 2)>) -F(s = ) +F(s = ) A(x = 3, y = A(x = 4, y = A(x = 3, y = false))) B(f = ) [1, 2, 3, 4, 5] diff --git a/examples/stdlib/repr.effekt b/examples/stdlib/repr.effekt index 9052293fc..bf7e4ec1b 100644 --- a/examples/stdlib/repr.effekt +++ b/examples/stdlib/repr.effekt @@ -9,7 +9,7 @@ type MyType[T] { C(array: Array[Int]) D(f: Int => Int at {}, list: List[Int], array: Array[Array[Int]], bytes: ByteArray) E(there: Map[Int, T], back: Map[T, Int]) - F(s: Set[MyType[T]]) + F(s: Set[T]) } type MyList[T] { @@ -39,8 +39,8 @@ def main() = { dump { repr(box e) } } with e: emit[Int] { v => resume(()) } - dump { E([(1, "one"), (2, "two")].fromListGeneric, [("one", 1), ("two", 2)].fromListGeneric).repr } - dump { F([A(3, fst), A(4, fst)].fromListGeneric).repr } + dump { E([(1, "one"), (2, "two")].fromList(box compareInt), [("one", 1), ("two", 2)].fromList(box compareStringBytes)).repr } + dump { F([3, 4].fromList(box compareInt)).repr } val (_, trees) = Repr::reify[Unit] { val _ = (repr(A(3, A(4, fst))), repr(B[Nothing](box { n => n + 1 })))