Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Printer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import mlscript.utils._, shorthands._
import hkmc2._
import hkmc2.Message.MessageContext
import hkmc2.document._
import hkmc2.semantics._
import hkmc2.syntax._
import hkmc2.semantics.Elaborator.State
import hkmc2.utils.Scope

Expand Down Expand Up @@ -60,11 +62,38 @@ object Printer:
case End(msg) => doc"end ${msg}"
case _ => TODO(blk)

def mkDocument(c: ClsLikeBody)(using Raise, Scope): Document =
mkDocument(c.privateFields, c.publicFields, c.methods, N, c.ctor)

def mkDocument(
privateFields: List[TermSymbol],
publicFields: List[(BlockMemberSymbol, TermSymbol)],
methods: List[FunDefn],
preCtor: Opt[Block],
ctor: Block
)(using Raise, Scope): Document =
val privFields = privateFields.map(x => doc"let ${x.id.name} = ...").mkDocument(sep = doc" # ")
val pubFields = publicFields.map(x => doc"${x._1.nme}").mkDocument(sep = doc" # ")
val docPrivFlds = if privateFields.isEmpty then doc"" else doc" # ${privFields}"
val docPubFlds = if publicFields.isEmpty then doc"" else doc" # ${pubFields}"
val docPreCtor = preCtor match
case Some(End(_)) => doc""
case Some(value) => doc" # preCtor { #{ # ${mkDocument(value)} #} # }"
case None => doc""
val docCtor = ctor match
case End(_) => doc""
case _ => doc" # ctor { #{ # ${mkDocument(ctor)} #} # }"

val mtds = methods.map(mkDocument).mkDocument(sep = doc" # ")
val docMethods = if methods.isEmpty then doc"" else doc" # ${mtds}"
if publicFields.isEmpty && privateFields.isEmpty && methods.isEmpty then doc""
else doc" { #{ ${docPrivFlds}${docPubFlds}${docPreCtor}${docCtor}${docMethods} #} # }"

def mkDocument(defn: Defn)(using Raise, Scope): Document = defn match
case FunDefn(own, sym, dSym, params, body) =>
val docParams = doc"${own.fold("")(_.toString+"::")}${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkDocument("(", ", ", ")")).mkDocument("")}"
val docParams = doc"${own.fold("")(_.toString + "::")}${sym.nme}${params.map(_.params.map(x => summon[Scope].allocateName(x.sym)).mkDocument("(", ", ", ")")).mkDocument("")}"
val docBody = mkDocument(body)
doc"fun ${sym.nme}${docParams} { #{ # ${docBody} #} # }"
doc"fun ${docParams} { #{ # ${docBody} #} # }"
case ValDefn(tsym, sym, rhs) =>
doc"val ${tsym.nme} = ${mkDocument(rhs)}"
case ClsLikeDefn(own, isym, sym, ctorSym, k, paramsOpt, auxParams, parentSym, methods,
Expand All @@ -77,14 +106,24 @@ object Printer:
val clsParams = paramsOpt.fold(Nil)(_.paramSyms)
val auxClsParams = auxParams.flatMap(_.paramSyms)
val ctorParams = (clsParams ++ auxClsParams).map(p => summon[Scope].allocateName(p))
val privFields = privateFields.map(x => doc"let ${x.id.name} = ...").mkDocument(sep = doc" # ")
val pubFields = publicFields.map(x => doc"${x._1.nme}").mkDocument(sep = doc" # ")
val docPrivFlds = if privateFields.isEmpty then doc"" else doc" # ${privFields}"
val docPubFlds = if publicFields.isEmpty then doc"" else doc" # ${pubFields}"
val docBody = if publicFields.isEmpty && privateFields.isEmpty then doc"" else doc" { #{ ${docPrivFlds}${docPubFlds} #} # }"
val docCtorParams = if clsParams.isEmpty then doc"" else doc"(${ctorParams.mkDocument(", ")})"
val docStaged = if isym.defn.forall(_.hasStagedModifier.isEmpty) then doc"" else doc"staged "
doc"${docStaged}class ${own.fold("")(_.toString+"::")}${sym.nme}${docCtorParams}${docBody}"
val docBody = mkDocument(privateFields, publicFields, methods, S(preCtor), ctor)
val docPreCtor = mkDocument(preCtor)
val docCtor = mkDocument(ctor)
val clsType = k match
case Cls => "class"
case Pat => "pattern"
case Obj => "object"
case Mod => "module"
val docCls = doc"${docStaged}${clsType} ${own.fold("")(_.toString+"::")}${sym.nme}${docCtorParams}${docBody}"
val docModule = mod match
case Some(mod) =>
val docStaged = if mod.isym.defn.forall(_.hasStagedModifier.isEmpty) then doc"" else doc"staged "
val docBody = mkDocument(mod)
doc" with # ${docStaged}module ${own.fold("")(_.toString+"::")}${sym.nme}${docBody}"
case None => doc""
doc"${docCls}${docModule}"

def mkDocument(arg: Arg)(using Raise, Scope): Document =
val doc = mkDocument(arg.value)
Expand Down Expand Up @@ -118,6 +157,8 @@ object Printer:
doc"${if mut then "mut " else ""}{ ${
args.map(x => x.idx.fold(doc"...")(p => mkDocument(p) :: ": ") :: mkDocument(x.value)).mkDocument(", ")
} }"
case DynSelect(qual, fld, arrayIdx) =>
doc"${mkDocument(qual)}${if arrayIdx then "." else "!"}${mkDocument(fld)}"
case x: Path => mkDocument(x)

def mkDocument(prog: Program)(using Raise, Scope): Document = summon[Scope].nest.givenIn:
Expand Down
113 changes: 113 additions & 0 deletions hkmc2/shared/src/test/mlscript/codegen/BlockPrinter.mls
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,116 @@ let x = x + 1
//│ set block$res3 = undefined in
//│ end
//│ x = 1

:slot
module A with
val x = 2
let y = 3
class C(x) with
fun f(x) = 2
fun g(x) = 3
//│ Pretty Lowered:
//│
//│ define class A with
//│ module A {
//│ let y = ...
//│ x
//│ ctor {
//│ define val x = 2 in
//│ set y = 3 in
//│ define class module:A::C(x4) {
//│ let x = ...
//│ ctor {
//│ set x5 = x4 in
//│ end
//│ }
//│ fun class:C::f(x6) {
//│ return 2
//│ }
//│ } in
//│ end
//│ }
//│ fun module:A::g(x7) {
//│ return 3
//│ }
//│ } in
//│ set block$res4 = undefined in
//│ end

:slot
class A
class B(x) extends A with
val y = 2
//│ Pretty Lowered:
//│
//│ define class A in
//│ define class B(x4) {
//│ let x = ...
//│ y
//│ preCtor {
//│ return super()
//│ }
//│ ctor {
//│ set x5 = x4 in
//│ define val y = 2 in
//│ end
//│ }
//│ } in
//│ set block$res5 = undefined in
//│ end

:slot
pattern P = B
//│ Pretty Lowered:
//│
//│ define pattern P {
//│ fun unapply(input) {
//│ match input
//│ B =>
//│ set output = input in
//│ set tmp1 = { } in
//│ return new runtime.MatchSuccess(output, tmp1)
//│ else
//│ return new runtime.MatchFailure(null)
//│ in
//│ end
//│ }
//│ fun unapplyStringPrefix(input1) {
//│ return new runtime.MatchFailure(null)
//│ }
//│ } in
//│ set block$res6 = undefined in
//│ end

:slot
object A with
fun f = 42
//│ Pretty Lowered:
//│ define object A { fun object:A::f { return 42 } } in set block$res7 = undefined in end

:slot
let x = [1, 2, 3]
x.0
//│ Pretty Lowered:
//│
//│ set x4 = [1, 2, 3] in
//│ set selRes = x4.0 in
//│ set discarded = x4.0$__checkNotMethod in
//│ match selRes
//│ undefined =>
//│ throw new globalThis.Error("Access to required field '0' yielded 'undefined'")
//│ else
//│
//│ in
//│ set block$res8 = selRes in
//│ return undefined
//│ = 1
//│ x = [1, 2, 3]

:slot
let y = { x: 2 }
y!x
//│ Pretty Lowered:
//│ set x5 = 2 in set y = { "x": x5 } in set block$res9 = y!"x" in return undefined
//│ = 2
//│ y = {x: 2}
Loading