Skip to content

Commit d427826

Browse files
Add code generation for bbml (#245)
1 parent e3f9e89 commit d427826

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1217
-525
lines changed

hkmc2/jvm/src/test/scala/hkmc2/BbmlDiffMaker.scala

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,33 @@ import hkmc2.bbml.*
99
abstract class BbmlDiffMaker extends JSBackendDiffMaker:
1010

1111
val bbPreludeFile = file / os.up / os.RelPath("bbPrelude.mls")
12+
val bbPredefFile = file / os.up / os.up / os.up /"mlscript-compile"/"bbml"/"Predef.mls"
1213

1314
val bbmlOpt = new NullaryCommand("bbml"):
1415
override def onSet(): Unit =
1516
super.onSet()
1617
if isGlobal then typeCheck.disable.isGlobal = true
1718
typeCheck.disable.setCurrentValue(())
1819
if file =/= bbPreludeFile then
20+
curCtx = Elaborator.State.init
1921
importFile(bbPreludeFile, verbose = false)
22+
curCtx = curCtx.nest(N)
2023

2124

25+
override def init(): Unit =
26+
if bbmlOpt.isSet then
27+
import syntax.*
28+
import Tree.*
29+
import Keyword.*
30+
given raise: Raise = d =>
31+
output(s"Error: $d")
32+
()
33+
processTrees(
34+
Modified(`import`, N, StrLit(bbPredefFile.toString))
35+
:: Open(Ident("Predef"))
36+
:: Nil)
37+
super.init()
38+
2239
lazy val bbCtx =
2340
given Elaborator.Ctx = curCtx
2441
bbml.BbCtx.init(_ => die)

hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ object BbCtx:
4545
def numTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Num").get, Nil)
4646
def strTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Str").get, Nil)
4747
def boolTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Bool").get, Nil)
48+
def errTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Error").get, Nil)
4849
private def codeBaseTy(ct: TypeArg, cr: TypeArg, isVar: TypeArg)(using ctx: BbCtx): Type =
4950
ClassLikeType(ctx.getCls("CodeBase").get, ct :: cr :: isVar :: Nil)
5051
def codeTy(ct: Type, cr: Type)(using ctx: BbCtx): Type =
@@ -57,6 +58,8 @@ object BbCtx:
5758
ClassLikeType(ctx.getCls("Ref").get, Wildcard(ct, ct) :: Wildcard.out(sk) :: Nil)
5859
def init(raise: Raise)(using Elaborator.State, Elaborator.Ctx): BbCtx =
5960
new BbCtx(raise, summon, None, 1, HashMap.empty)
61+
62+
val builtinOps = Set("+", "-", "*", "/", "<", ">", "<=", ">=", "==", "!=", "&&", "||")
6063
end BbCtx
6164

6265

@@ -183,6 +186,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
183186
case _: UnitLit => Top
184187
case _: BoolLit => BbCtx.boolTy), Bot, Bot)
185188
case Ref(sym: Symbol) if sym.nme === "error" => (Bot, Bot, Bot)
189+
case Ref(sym: Symbol) if BbCtx.builtinOps(sym.nme) => ctx.get(sym) match
190+
case S(ty) => (tryMkMono(ty, code), Bot, Bot)
191+
case N =>
192+
(error(msg"Cannot quote operator ${sym.nme}" -> code.toLoc :: Nil), Bot, Bot)
186193
case Lam(PlainParamList(params), body) =>
187194
val nestCtx = ctx.nextLevel
188195
given BbCtx = nestCtx
@@ -257,7 +264,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
257264
split match
258265
case Split.Cons(Branch(scrutinee, Pattern.ClassLike(sym, _, _, _), cons), alts) =>
259266
// * Pattern matching for classes
260-
val (clsTy, tv, emptyTy) = ctx.getCls(sym.nme).flatMap(_.defn) match
267+
val (clsTy, tv, emptyTy) = sym.asCls.flatMap(_.defn) match
261268
case S(cls) =>
262269
(ClassLikeType(sym, cls.tparams.map(_ => freshWildcard(N))), (freshVar(N)), ClassLikeType(sym, cls.tparams.map(_ => Wildcard.empty)))
263270
case _ =>
@@ -304,7 +311,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
304311
case Split.Else(alts) => sign match
305312
case S(sign) => ascribe(alts, sign)
306313
case _ => typeCheck(alts)
307-
case Split.End => ???
314+
case Split.End => (Bot, Bot)
308315

309316
// * Note: currently, the returned type is not used or useful, but it could be in the future
310317
private def ascribe(lhs: Term, rhs: GeneralType)(using ctx: BbCtx): (GeneralType, Type) =
@@ -424,6 +431,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
424431
goStats(stats)
425432
case (clsDef: ClassDef) :: stats =>
426433
goStats(stats)
434+
case (modDef: ModuleDef) :: stats =>
435+
goStats(stats)
436+
case Import(sym, pth) :: stats =>
437+
goStats(stats) // TODO:
427438
goStats(stats)
428439
val (ty, eff) = typeCheck(res)
429440
(ty, effBuff.foldLeft(eff)((res, e) => res | e))
@@ -464,7 +475,9 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
464475
case _ => (error(msg"${field.name} is not a valid member in class ${clsSym.nme}" -> t.toLoc :: Nil), Bot)
465476
case N =>
466477
(error(msg"Not a valid class: ${cls.describe}" -> cls.toLoc :: Nil), Bot)
467-
case Term.App(lhs, Term.Tup(rhs)) =>
478+
case Term.App(lhs: Term.SynthSel, Term.Tup(Nil)) if lhs.sym.exists(_.isGetter) =>
479+
typeCheck(lhs) // * Getter access will be elaborated to applications. But they cannot be typed as normal applications.
480+
case t @ Term.App(lhs, Term.Tup(rhs)) =>
468481
val (funTy, lhsEff) = typeCheck(lhs)
469482
app((funTy, lhsEff), rhs, t)
470483
case Term.New(cls, args) =>
@@ -517,7 +530,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
517530
val sk = freshVar(N)
518531
constrain(tryMkMono(regTy, reg), BbCtx.regionTy(sk))
519532
(BbCtx.refTy(tryMkMono(valTy, value), sk), sk | (regEff | valEff))
520-
case Term.Assgn(lhs, rhs) =>
533+
case Term.SetRef(lhs, rhs) =>
521534
val (lhsTy, lhsEff) = typeCheck(lhs)
522535
val (rhsTy, rhsEff) = typeCheck(rhs)
523536
val sk = freshVar(N)
@@ -536,6 +549,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
536549
(BbCtx.codeTy(ty, ctxTy), eff)
537550
case _: Term.Unquoted =>
538551
(error(msg"Unquote should nest in quasiquote" -> t.toLoc :: Nil), Bot)
552+
case Throw(e) =>
553+
val (ty, eff) = typeCheck(e)
554+
constrain(tryMkMono(ty, e), BbCtx.errTy)
555+
(Bot, eff)
539556
case Term.Error =>
540557
(Bot, Bot) // TODO: error type?
541558
case _ =>

hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,26 @@ class Lowering(using TL, Raise, Elaborator.State):
305305
k(Value.Ref(l))
306306
)
307307

308+
// * BbML-specific cases: t.Cls#field and mutable operations
309+
case SelProj(prefix, _, proj) =>
310+
setupSelection(prefix, proj, N)(k)
311+
case Region(reg, body) =>
312+
Assign(reg, Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Region"))(N), Nil), term(body)(k))
313+
case RegRef(reg, value) =>
314+
def rec(as: Ls[st], asr: Ls[Path]): Block = as match
315+
case Nil => k(Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Ref"))(N), asr.reverse))
316+
case a :: as =>
317+
subTerm(a): ar =>
318+
rec(as, ar :: asr)
319+
rec(reg :: value :: Nil, Nil)
320+
case Deref(ref) =>
321+
subTerm(ref): r =>
322+
k(Select(r, Tree.Ident("value"))(N))
323+
case SetRef(lhs, rhs) =>
324+
subTerm(lhs): ref =>
325+
subTerm(rhs): value =>
326+
AssignField(ref, Tree.Ident("value"), value, k(value))(N)
327+
308328
case Error => End("error")
309329

310330
// case _ =>

hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
9393
case Call(Value.Ref(l: BuiltinSymbol), args) =>
9494
err(msg"Illeal arity for builtin symbol '${l.nme}'")
9595

96+
case Call(s @ Select(_, id), lhs :: rhs :: Nil) =>
97+
Elaborator.ctx.Builtins.getBuiltinOp(id.name) match
98+
case S(jsOp) =>
99+
val res = doc"${result(lhs)} ${jsOp} ${result(rhs)}"
100+
if needsParens(jsOp) then doc"(${res})" else res
101+
case N => doc"${result(s)}(${(result(lhs) :: result(rhs) :: Nil).mkDocument(", ")})"
96102
case c @ Call(fun, args) =>
97103
val base = fun match
98104
case _: Value.Lam => doc"(${result(fun)})"
@@ -148,7 +154,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
148154
S(defn.sym).collectFirst{ case s: InnerSymbol => s }):
149155
defn match
150156
case FunDefn(sym, Nil, body) =>
151-
TODO("getters")
157+
doc"function ${sym.nme}() { #{ # ${this.body(body)} #} # }"
152158
case FunDefn(sym, ps :: pss, bod) =>
153159
val result = pss.foldRight(bod):
154160
case (ps, block) =>
@@ -179,6 +185,10 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
179185
doc" # ${td.sym.nme}($params) { #{ # ${
180186
bodyDoc
181187
} #} # }"
188+
case td @ FunDefn(_, Nil, bod) =>
189+
doc" # get ${td.sym.nme}() { #{ # ${
190+
this.body(bod)
191+
} #} # }"
182192
.mkDocument(" ")
183193
}${
184194
if mtds.exists(_.sym.nme == "toString")

hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ object Elaborator:
2929
";" -> ",",
3030
"+." -> "+",
3131
"-." -> "-",
32-
"*." -> "*")
32+
"*." -> "*",
33+
"/." -> "/")
34+
private val builtinBinOps = aliasOps ++ (binaryOps.map: op =>
35+
op -> op).toMap
3336

3437
val reservedNames = binaryOps.toSet ++ aliasOps.keySet + "NaN" + "Infinity"
3538

@@ -43,11 +46,15 @@ object Elaborator:
4346

4447
def withMembers(members: Iterable[Str -> MemberSymbol[?]], out: Opt[Symbol] = N): Ctx =
4548
copy(env = env ++ members.map:
46-
case (nme, sym) => nme -> (
47-
out orElse outer match
48-
case S(outer) => Ctx.SelElem(outer, sym.nme, S(sym))
49-
case N => sym: Ctx.Elem
50-
)
49+
case (nme, sym) =>
50+
val elem = out orElse outer match
51+
case S(outer) => Ctx.SelElem(outer, sym.nme, S(sym))
52+
case N => sym: Ctx.Elem
53+
if sym.isGetter && !(out.exists:
54+
case _: ClassSymbol | _: ModuleSymbol => true // * A getter inside class/module can be invoked directly
55+
case _: BlockMemberSymbol | _: TermSymbol | _: TypeAliasSymbol | _: PatternSymbol | _: TopLevelSymbol => false)
56+
then nme -> Ctx.GetElem(elem)
57+
else nme -> elem
5158
)
5259

5360
def nest(outer: Opt[InnerSymbol]): Ctx = Ctx(outer, Some(this), Map.empty)
@@ -78,25 +85,32 @@ object Elaborator:
7885
val Num = assumeBuiltinCls("Num")
7986
val Str = assumeBuiltinCls("Str")
8087
val Predef = assumeBuiltinMod("Predef")
88+
def getBuiltinOp(op: Str): Opt[Str] = if getBuiltin(op).isDefined then builtinBinOps.get(op) else N
8189

8290
object Ctx:
8391
abstract class Elem:
8492
def nme: Str
85-
def ref(id: Tree.Ident): Term
93+
def ref(id: Tree.Ident)(using Elaborator.State): Term
8694
def symbol: Opt[Symbol]
8795
final case class RefElem(val sym: Symbol) extends Elem:
8896
val nme = sym.nme
89-
def ref(id: Tree.Ident): Term =
97+
def ref(id: Tree.Ident)(using Elaborator.State): Term =
9098
require(id.name == nme)
9199
Term.Ref(sym)(id, 666) // TODO 666
92100
def symbol = S(sym)
93101
final case class SelElem(val base: Elem, val nme: Str, val symOpt: Opt[FieldSymbol]) extends Elem:
94-
def ref(id: Tree.Ident): Term =
102+
def ref(id: Tree.Ident)(using Elaborator.State): Term =
95103
// * Note: due to symbolic ops, we may have `id.name =/= nme`;
96104
// * e.g., we can have `id.name = "|>"` and `nme = "pipe"`.
97105
Term.SynthSel(base.ref(Ident(base.nme)),
98106
new Tree.Ident(nme).withLocOf(id))(symOpt)
99107
def symbol = symOpt
108+
final case class GetElem(val base: Elem) extends Elem:
109+
def nme: Str = base.nme
110+
def ref(id: Tree.Ident)(using Elaborator.State): Term =
111+
val emptyTup: Tree.Tup = Tree.Tup(Nil)
112+
Term.App(base.ref(id), Term.Tup(Nil)(emptyTup))(Tree.App(id, emptyTup), FlowSymbol("‹get-res›"))
113+
def symbol: Opt[Symbol] = base.symbol
100114
given Conversion[Symbol, Elem] = RefElem(_)
101115
val empty: Ctx = Ctx(N, N, Map.empty)
102116

@@ -273,7 +287,7 @@ extends Importer:
273287
case App(Ident("&"), Tree.Tup(lhs :: rhs :: Nil)) =>
274288
Term.CompType(term(lhs), term(rhs), false)
275289
case App(Ident(":="), Tree.Tup(lhs :: rhs :: Nil)) =>
276-
Term.Assgn(term(lhs), term(rhs))
290+
Term.SetRef(term(lhs), term(rhs))
277291
case App(Ident("#"), Tree.Tup(SynthSel(pre, idn: Ident) :: (idp: Ident) :: Nil)) =>
278292
Term.SelProj(term(pre), term(idn), idp)
279293
case App(Ident("#"), Tree.Tup(SynthSel(pre, Ident(name)) :: App(Ident(proj), args) :: Nil)) =>

hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,16 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State)
116116
override def toString: Str =
117117
s"member:$nme${State.dbgUid(uid)}"
118118

119+
override val isGetter: Bool = // TODO: this should be checked based on a special syntax for getter
120+
trmImplTree.exists(t => t.k === Fun && t.paramLists.isEmpty)
121+
119122
end BlockMemberSymbol
120123

121124

122125
sealed abstract class MemberSymbol[Defn <: Definition](using State) extends Symbol:
123126
def nme: Str
124127
var defn: Opt[Defn] = N
128+
val isGetter: Bool = false
125129

126130

127131
class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State)

hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ enum Term extends Statement:
3535
case RegRef(reg: Term, value: Term)
3636
case Assgn(lhs: Term, rhs: Term)
3737
case Deref(ref: Term)
38+
case SetRef(ref: Term, value: Term)
3839
case Ret(result: Term)
3940
case Throw(result: Term)
4041
case Try(body: Term, finallyDo: Term)
@@ -71,7 +72,9 @@ enum Term extends Statement:
7172
case Region(name, body) => "region expression"
7273
case RegRef(reg, value) => "reference creation"
7374
case Assgn(lhs, rhs) => "assignment"
75+
case SetRef(ref, value) => "mutable reference assignment"
7476
case Deref(ref) => "dereference"
77+
case Throw(e) => "throw"
7578
end Term
7679

7780
import Term.*
@@ -111,6 +114,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
111114
case Region(_, body) => body :: Nil
112115
case RegRef(reg, value) => reg :: value :: Nil
113116
case Assgn(lhs, rhs) => lhs :: rhs :: Nil
117+
case SetRef(lhs, rhs) => lhs :: rhs :: Nil
114118
case Deref(term) => term :: Nil
115119
case TermDefinition(_, k, _, ps, sign, body, res, _) =>
116120
ps.toList.flatMap(_.subTerms) ::: sign.toList ::: body.toList
@@ -180,6 +184,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
180184
case Region(name, body) => s"region ${name.nme} in ${body.showDbg}"
181185
case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}"
182186
case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}"
187+
case SetRef(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}"
183188
case Deref(term) => s"!$term"
184189
case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}"
185190
case Error => "<error>"

hkmc2/shared/src/test/mlscript-compile/Predef.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ const Predef$class = class Predef {
5050
tmp = "| ".repeat(this.indentLvl) ?? null;
5151
tmp1 = " ".repeat(this.indentLvl) ?? null;
5252
tmp2 = "\n" + tmp1;
53-
tmp3 = msg.replaceAll("\n", tmp2) ?? null;
53+
tmp3 = msg.replaceAll("\n", tmp2);
5454
tmp4 = tmp + tmp3;
5555
return console.log(tmp4) ?? null;
5656
} else {
@@ -88,7 +88,7 @@ const Predef$class = class Predef {
8888
}
8989
call(receiver1, f2) {
9090
return (...args) => {
91-
return f2.call(receiver1, ...args) ?? null;
91+
return f2.call(receiver1, ...args);
9292
};
9393
}
9494
print(x3) {
@@ -102,7 +102,7 @@ const Predef$class = class Predef {
102102
return globalThis.Array.prototype.slice.call(xs, i, tmp) ?? null;
103103
}
104104
tupleGet(xs1, i1) {
105-
return globalThis.Array.prototype.at.call(xs1, i1) ?? null;
105+
return globalThis.Array.prototype.at.call(xs1, i1);
106106
}
107107
stringStartsWith(string, prefix) {
108108
return string.startsWith(prefix) ?? null;

hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ class Accounting {
6363
constructor(fileName) {
6464
this.fileName = fileName;
6565
let tmp;
66-
tmp = fs.writeFileSync(this.fileName, "# Accounting\n") ?? null;
66+
tmp = fs.writeFileSync(this.fileName, "# Accounting\n");
6767
}
6868
w(txt) {
69-
return fs.appendFileSync(this.fileName, txt) ?? null;
69+
return fs.appendFileSync(this.fileName, txt);
7070
}
7171
wln(txt1) {
7272
let tmp;
7373
tmp = Str.concat(txt1, "\n");
74-
return fs.appendFileSync(this.fileName, tmp) ?? null;
74+
return fs.appendFileSync(this.fileName, tmp);
7575
}
7676
init() {
7777
let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13;
@@ -134,7 +134,7 @@ class Accounting {
134134
}) ?? null;
135135
tmp14 = tmp13.reduce((a, b) => {
136136
return a + b;
137-
}, 0) ?? null;
137+
}, 0);
138138
tmp15 = this$Accounting.display(tmp14);
139139
tmp16 = Str.concat(tmp11, tmp15);
140140
tmp17 = Str.concat(tmp16, "|");
@@ -149,7 +149,7 @@ class Accounting {
149149
}) ?? null;
150150
tmp23 = tmp22.reduce((a, b) => {
151151
return a + b;
152-
}, 0) ?? null;
152+
}, 0);
153153
tmp24 = this$Accounting.display(tmp23);
154154
tmp25 = Str.concat(tmp20, tmp24);
155155
tmp26 = Str.concat(tmp25, "|");

0 commit comments

Comments
 (0)