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/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 diff --git a/examples/stdlib/repr.check b/examples/stdlib/repr.check new file mode 100644 index 000000000..b20dcc281 --- /dev/null +++ b/examples/stdlib/repr.check @@ -0,0 +1,18 @@ +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 = ) +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 = ) +[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 new file mode 100644 index 000000000..bf7e4ec1b --- /dev/null +++ b/examples/stdlib/repr.effekt @@ -0,0 +1,54 @@ +import repr +import map +import set +import seq + +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[T]) +} + +type MyList[T] { + MyNil() + MyCons(head: T, tail: MyList[T]) +} + +def dump { rpr: () => Unit / rose[Repr::Llabel, Unit]}: Unit = println(toString { rpr() }) + +def main() = { + dump { do labelled(Repr::Llabel::Prop("x")) { do labelled(Repr::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::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) } + } with e: emit[Int] { v => resume(()) } + + 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 }))) + } + 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 } +} diff --git a/libraries/common/array.effekt b/libraries/common/array.effekt index caca559ec..42f551541 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[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 a877f9377..d785979c0 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[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 5871f9d9c..fed6412ee 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -843,3 +843,48 @@ effect write(s: String): Unit effect splice[A](x: A): Unit +// Repr +// ==== + +interface rose[A, R] { + def labelled(node: A) { children: => R }: R +} + +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[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[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 c323c949f..993bb63bf 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[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 = { var l = list diff --git a/libraries/common/map.effekt b/libraries/common/map.effekt index 7a5d27f83..e72c6914c 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[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(Repr::AssocProp()) { + reprK(k); reprV(v) + } + } + } + // Streaming // --------- diff --git a/libraries/common/repr.effekt b/libraries/common/repr.effekt new file mode 100644 index 000000000..86b0be84b --- /dev/null +++ b/libraries/common/repr.effekt @@ -0,0 +1,113 @@ +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 Llabel = Repr::Llabel + type Repr = Tree[Repr::Llabel] + + def leaf(label: Llabel): Repr = Tree(label, []) + 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(Repr::List(), l) + def byteArray(b: ByteArray): Repr = collection("ByteArray", b.toList.map {byte}) + + def constructorNamed(name: String, a: List[(String, Repr)]): Repr = + Tree(Repr::App(name), a.map { case (name, value) => Tree(Repr::Prop(name), [value]) }) + def constructor(name: String, args: List[Repr]): Repr = + Tree(Repr::App(name), args) + def opaque(name: String, child: Repr): Repr = + Tree(Repr::Opaque(name), [child]) + def opaque(name: String): Repr = + Tree(Repr::Opaque(name), []) + def opaqueLiteral(name: String, value: String): Repr = + 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(Repr::Assoc(name), contents.map { case (key, value) => Tree(Repr::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 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[Repr::Llabel, Unit] }: String = { + val repr = Repr::reifySingle { reprey() } + toUniversalString(repr) +} diff --git a/libraries/common/set.effekt b/libraries/common/set.effekt index 1c160d31b..0772cff88 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[Repr::Llabel, Unit]}: Unit / rose[Repr::Llabel, Unit] = + do labelled[Repr::Llabel, Unit](Repr::Llabel::Opaque("Set")) { s.foreach { a => reprA(a) } } + + // Streaming // ---------