Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add code generation for bbml #245

Merged
merged 36 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f654551
Map builtin operators
NeilKleistGao Nov 26, 2024
270ff3e
WIP: Fix num and sel code gen & add predef
NeilKleistGao Nov 26, 2024
6896564
WIP: Add throw
NeilKleistGao Nov 26, 2024
c25ebd5
WIP: Fix predef
NeilKleistGao Nov 26, 2024
b9e3782
Fix getter generation & split.end typing
NeilKleistGao Nov 27, 2024
dc58521
Generate code for region & ref
NeilKleistGao Nov 27, 2024
7c56b26
WIP: Add tests
NeilKleistGao Nov 29, 2024
a40e1cc
Merge from main branch
NeilKleistGao Nov 29, 2024
c2deec1
Fix class matching typing
NeilKleistGao Nov 30, 2024
ddeb04a
Merge from main branch
NeilKleistGao Nov 30, 2024
a81aae2
Fix num ops code gen
NeilKleistGao Nov 30, 2024
dae1d36
Fix getter generation
NeilKleistGao Dec 1, 2024
4afe46f
Clean
NeilKleistGao Dec 1, 2024
b0c3f38
Merge from mlscript
NeilKleistGao Dec 1, 2024
a683de6
Update bbml predef
NeilKleistGao Dec 1, 2024
af42e40
Minor
NeilKleistGao Dec 1, 2024
182237f
Reuse getBuiltin
NeilKleistGao Dec 4, 2024
5443f1f
Fix GetElem implementation
NeilKleistGao Dec 4, 2024
89f5c6c
Merge branch 'hkmc2' of https://github.com/hkust-taco/mlscript into b…
NeilKleistGao Dec 5, 2024
ebe9103
Rerun tests
NeilKleistGao Dec 5, 2024
8e63fae
Move defn check to symbol
NeilKleistGao Dec 6, 2024
0ff406f
Fix module getter generation and ctx.get use
NeilKleistGao Dec 6, 2024
ce6f2cf
Refactor getter typing
NeilKleistGao Dec 6, 2024
e0a22ad
Update hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
NeilKleistGao Dec 8, 2024
313d680
Merge from main branch
NeilKleistGao Dec 8, 2024
0bb46f1
WIP: Fix shadowing
NeilKleistGao Dec 8, 2024
5557984
Forbid getters from being defined in non-module and non-function scopes
NeilKleistGao Dec 8, 2024
52e2bd2
Update hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala
NeilKleistGao Dec 12, 2024
ae7bfd5
Remove top-level getter selection check
NeilKleistGao Dec 12, 2024
bbc8f9f
Add empty lines
NeilKleistGao Dec 12, 2024
710831f
Fix diff
NeilKleistGao Dec 12, 2024
6e7a96a
Fix diff
NeilKleistGao Dec 12, 2024
7ca79b9
Fix getter logic
NeilKleistGao Dec 12, 2024
634748d
Merge from main branch
NeilKleistGao Dec 12, 2024
e0c2e5e
Minor changes
NeilKleistGao Dec 12, 2024
ea0c9c8
Remove the unused import and update the comment
NeilKleistGao Dec 12, 2024
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
17 changes: 17 additions & 0 deletions hkmc2/jvm/src/test/scala/hkmc2/BbmlDiffMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,33 @@ import hkmc2.bbml.*
abstract class BbmlDiffMaker extends JSBackendDiffMaker:

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

val bbmlOpt = new NullaryCommand("bbml"):
override def onSet(): Unit =
super.onSet()
if isGlobal then typeCheck.disable.isGlobal = true
typeCheck.disable.setCurrentValue(())
if file =/= bbPreludeFile then
curCtx = Elaborator.State.init
importFile(bbPreludeFile, verbose = false)
curCtx = curCtx.nest(N)


override def init(): Unit =
if bbmlOpt.isSet then
import syntax.*
import Tree.*
import Keyword.*
given raise: Raise = d =>
output(s"Error: $d")
()
processTrees(
Modified(`import`, N, StrLit(bbPredefFile.toString))
:: Open(Ident("Predef"))
:: Nil)
super.init()

lazy val bbCtx =
given Elaborator.Ctx = curCtx
bbml.BbCtx.init(_ => die)
Expand Down
25 changes: 21 additions & 4 deletions hkmc2/shared/src/main/scala/hkmc2/bbml/bbML.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ object BbCtx:
def numTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Num").get, Nil)
def strTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Str").get, Nil)
def boolTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Bool").get, Nil)
def errTy(using ctx: BbCtx): Type = ClassLikeType(ctx.getCls("Error").get, Nil)
private def codeBaseTy(ct: TypeArg, cr: TypeArg, isVar: TypeArg)(using ctx: BbCtx): Type =
ClassLikeType(ctx.getCls("CodeBase").get, ct :: cr :: isVar :: Nil)
def codeTy(ct: Type, cr: Type)(using ctx: BbCtx): Type =
Expand All @@ -57,6 +58,8 @@ object BbCtx:
ClassLikeType(ctx.getCls("Ref").get, Wildcard(ct, ct) :: Wildcard.out(sk) :: Nil)
def init(raise: Raise)(using Elaborator.State, Elaborator.Ctx): BbCtx =
new BbCtx(raise, summon, None, 1, HashMap.empty)

val builtinOps = Set("+", "-", "*", "/", "<", ">", "<=", ">=", "==", "!=", "&&", "||")
end BbCtx


Expand Down Expand Up @@ -183,6 +186,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
case _: UnitLit => Top
case _: BoolLit => BbCtx.boolTy), Bot, Bot)
case Ref(sym: Symbol) if sym.nme === "error" => (Bot, Bot, Bot)
case Ref(sym: Symbol) if BbCtx.builtinOps(sym.nme) => ctx.get(sym) match
case S(ty) => (tryMkMono(ty, code), Bot, Bot)
case N =>
(error(msg"Cannot quote operator ${sym.nme}" -> code.toLoc :: Nil), Bot, Bot)
case Lam(PlainParamList(params), body) =>
val nestCtx = ctx.nextLevel
given BbCtx = nestCtx
Expand Down Expand Up @@ -257,7 +264,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
split match
case Split.Cons(Branch(scrutinee, Pattern.ClassLike(sym, _, _, _), cons), alts) =>
// * Pattern matching for classes
val (clsTy, tv, emptyTy) = ctx.getCls(sym.nme).flatMap(_.defn) match
val (clsTy, tv, emptyTy) = sym.asCls.flatMap(_.defn) match
case S(cls) =>
(ClassLikeType(sym, cls.tparams.map(_ => freshWildcard(N))), (freshVar(N)), ClassLikeType(sym, cls.tparams.map(_ => Wildcard.empty)))
case _ =>
Expand Down Expand Up @@ -304,7 +311,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
case Split.Else(alts) => sign match
case S(sign) => ascribe(alts, sign)
case _ => typeCheck(alts)
case Split.End => ???
case Split.End => (Bot, Bot)

// * Note: currently, the returned type is not used or useful, but it could be in the future
private def ascribe(lhs: Term, rhs: GeneralType)(using ctx: BbCtx): (GeneralType, Type) =
Expand Down Expand Up @@ -424,6 +431,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
goStats(stats)
case (clsDef: ClassDef) :: stats =>
goStats(stats)
case (modDef: ModuleDef) :: stats =>
goStats(stats)
case Import(sym, pth) :: stats =>
goStats(stats) // TODO:
goStats(stats)
val (ty, eff) = typeCheck(res)
(ty, effBuff.foldLeft(eff)((res, e) => res | e))
Expand Down Expand Up @@ -464,7 +475,9 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
case _ => (error(msg"${field.name} is not a valid member in class ${clsSym.nme}" -> t.toLoc :: Nil), Bot)
case N =>
(error(msg"Not a valid class: ${cls.describe}" -> cls.toLoc :: Nil), Bot)
case Term.App(lhs, Term.Tup(rhs)) =>
case Term.App(lhs: Term.SynthSel, Term.Tup(Nil)) if lhs.sym.exists(_.isGetter) =>
typeCheck(lhs) // * Getter access will be elaborated to applications. But they cannot be typed as normal applications.
case t @ Term.App(lhs, Term.Tup(rhs)) =>
val (funTy, lhsEff) = typeCheck(lhs)
app((funTy, lhsEff), rhs, t)
case Term.New(cls, args) =>
Expand Down Expand Up @@ -517,7 +530,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
val sk = freshVar(N)
constrain(tryMkMono(regTy, reg), BbCtx.regionTy(sk))
(BbCtx.refTy(tryMkMono(valTy, value), sk), sk | (regEff | valEff))
case Term.Assgn(lhs, rhs) =>
case Term.SetRef(lhs, rhs) =>
val (lhsTy, lhsEff) = typeCheck(lhs)
val (rhsTy, rhsEff) = typeCheck(rhs)
val sk = freshVar(N)
Expand All @@ -536,6 +549,10 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
(BbCtx.codeTy(ty, ctxTy), eff)
case _: Term.Unquoted =>
(error(msg"Unquote should nest in quasiquote" -> t.toLoc :: Nil), Bot)
case Throw(e) =>
val (ty, eff) = typeCheck(e)
constrain(tryMkMono(ty, e), BbCtx.errTy)
(Bot, eff)
case Term.Error =>
(Bot, Bot) // TODO: error type?
case _ =>
Expand Down
20 changes: 20 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,26 @@ class Lowering(using TL, Raise, Elaborator.State):
k(Value.Ref(l))
)

NeilKleistGao marked this conversation as resolved.
Show resolved Hide resolved
// * BbML-specific cases: t.Cls#field and mutable operations
case SelProj(prefix, _, proj) =>
setupSelection(prefix, proj, N)(k)
case Region(reg, body) =>
Assign(reg, Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Region"))(N), Nil), term(body)(k))
case RegRef(reg, value) =>
def rec(as: Ls[st], asr: Ls[Path]): Block = as match
case Nil => k(Instantiate(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Ref"))(N), asr.reverse))
case a :: as =>
subTerm(a): ar =>
rec(as, ar :: asr)
rec(reg :: value :: Nil, Nil)
case Deref(ref) =>
subTerm(ref): r =>
k(Select(r, Tree.Ident("value"))(N))
case SetRef(lhs, rhs) =>
subTerm(lhs): ref =>
subTerm(rhs): value =>
AssignField(ref, Tree.Ident("value"), value, k(value))(N)

case Error => End("error")

// case _ =>
Expand Down
12 changes: 11 additions & 1 deletion hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
case Call(Value.Ref(l: BuiltinSymbol), args) =>
err(msg"Illeal arity for builtin symbol '${l.nme}'")

NeilKleistGao marked this conversation as resolved.
Show resolved Hide resolved
case Call(s @ Select(_, id), lhs :: rhs :: Nil) =>
Elaborator.ctx.Builtins.getBuiltinOp(id.name) match
case S(jsOp) =>
val res = doc"${result(lhs)} ${jsOp} ${result(rhs)}"
if needsParens(jsOp) then doc"(${res})" else res
case N => doc"${result(s)}(${(result(lhs) :: result(rhs) :: Nil).mkDocument(", ")})"
case c @ Call(fun, args) =>
val base = fun match
case _: Value.Lam => doc"(${result(fun)})"
Expand Down Expand Up @@ -148,7 +154,7 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
S(defn.sym).collectFirst{ case s: InnerSymbol => s }):
defn match
case FunDefn(sym, Nil, body) =>
TODO("getters")
doc"function ${sym.nme}() { #{ # ${this.body(body)} #} # }"
case FunDefn(sym, ps :: pss, bod) =>
val result = pss.foldRight(bod):
case (ps, block) =>
Expand Down Expand Up @@ -179,6 +185,10 @@ class JSBuilder(using Elaborator.State, Elaborator.Ctx) extends CodeBuilder:
doc" # ${td.sym.nme}($params) { #{ # ${
bodyDoc
} #} # }"
case td @ FunDefn(_, Nil, bod) =>
doc" # get ${td.sym.nme}() { #{ # ${
this.body(bod)
} #} # }"
.mkDocument(" ")
}${
if mtds.exists(_.sym.nme == "toString")
Expand Down
34 changes: 24 additions & 10 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ object Elaborator:
";" -> ",",
"+." -> "+",
"-." -> "-",
"*." -> "*")
"*." -> "*",
"/." -> "/")
private val builtinBinOps = aliasOps ++ (binaryOps.map: op =>
op -> op).toMap

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

Expand All @@ -43,11 +46,15 @@ object Elaborator:

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

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

object Ctx:
abstract class Elem:
def nme: Str
def ref(id: Tree.Ident): Term
def ref(id: Tree.Ident)(using Elaborator.State): Term
def symbol: Opt[Symbol]
final case class RefElem(val sym: Symbol) extends Elem:
val nme = sym.nme
def ref(id: Tree.Ident): Term =
def ref(id: Tree.Ident)(using Elaborator.State): Term =
require(id.name == nme)
Term.Ref(sym)(id, 666) // TODO 666
def symbol = S(sym)
final case class SelElem(val base: Elem, val nme: Str, val symOpt: Opt[FieldSymbol]) extends Elem:
def ref(id: Tree.Ident): Term =
def ref(id: Tree.Ident)(using Elaborator.State): Term =
// * Note: due to symbolic ops, we may have `id.name =/= nme`;
// * e.g., we can have `id.name = "|>"` and `nme = "pipe"`.
Term.SynthSel(base.ref(Ident(base.nme)),
new Tree.Ident(nme).withLocOf(id))(symOpt)
def symbol = symOpt
final case class GetElem(val base: Elem) extends Elem:
def nme: Str = base.nme
def ref(id: Tree.Ident)(using Elaborator.State): Term =
val emptyTup: Tree.Tup = Tree.Tup(Nil)
Term.App(base.ref(id), Term.Tup(Nil)(emptyTup))(Tree.App(id, emptyTup), FlowSymbol("‹get-res›"))
def symbol: Opt[Symbol] = base.symbol
given Conversion[Symbol, Elem] = RefElem(_)
val empty: Ctx = Ctx(N, N, Map.empty)

Expand Down Expand Up @@ -273,7 +287,7 @@ extends Importer:
case App(Ident("&"), Tree.Tup(lhs :: rhs :: Nil)) =>
Term.CompType(term(lhs), term(rhs), false)
case App(Ident(":="), Tree.Tup(lhs :: rhs :: Nil)) =>
Term.Assgn(term(lhs), term(rhs))
Term.SetRef(term(lhs), term(rhs))
case App(Ident("#"), Tree.Tup(SynthSel(pre, idn: Ident) :: (idp: Ident) :: Nil)) =>
Term.SelProj(term(pre), term(idn), idp)
case App(Ident("#"), Tree.Tup(SynthSel(pre, Ident(name)) :: App(Ident(proj), args) :: Nil)) =>
Expand Down
4 changes: 4 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,16 @@ class BlockMemberSymbol(val nme: Str, val trees: Ls[Tree])(using State)
override def toString: Str =
s"member:$nme${State.dbgUid(uid)}"

override val isGetter: Bool = // TODO: this should be checked based on a special syntax for getter
trmImplTree.exists(t => t.k === Fun && t.paramLists.isEmpty)

end BlockMemberSymbol


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


class TermSymbol(val k: TermDefKind, val owner: Opt[InnerSymbol], val id: Tree.Ident)(using State)
Expand Down
5 changes: 5 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ enum Term extends Statement:
case RegRef(reg: Term, value: Term)
case Assgn(lhs: Term, rhs: Term)
case Deref(ref: Term)
case SetRef(ref: Term, value: Term)
case Ret(result: Term)
case Throw(result: Term)
case Try(body: Term, finallyDo: Term)
Expand Down Expand Up @@ -71,7 +72,9 @@ enum Term extends Statement:
case Region(name, body) => "region expression"
case RegRef(reg, value) => "reference creation"
case Assgn(lhs, rhs) => "assignment"
case SetRef(ref, value) => "mutable reference assignment"
case Deref(ref) => "dereference"
case Throw(e) => "throw"
end Term

import Term.*
Expand Down Expand Up @@ -111,6 +114,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
case Region(_, body) => body :: Nil
case RegRef(reg, value) => reg :: value :: Nil
case Assgn(lhs, rhs) => lhs :: rhs :: Nil
case SetRef(lhs, rhs) => lhs :: rhs :: Nil
case Deref(term) => term :: Nil
case TermDefinition(_, k, _, ps, sign, body, res, _) =>
ps.toList.flatMap(_.subTerms) ::: sign.toList ::: body.toList
Expand Down Expand Up @@ -180,6 +184,7 @@ sealed trait Statement extends AutoLocated with ProductWithExtraInfo:
case Region(name, body) => s"region ${name.nme} in ${body.showDbg}"
case RegRef(reg, value) => s"(${reg.showDbg}).ref ${value.showDbg}"
case Assgn(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}"
case SetRef(lhs, rhs) => s"${lhs.showDbg} := ${rhs.showDbg}"
case Deref(term) => s"!$term"
case CompType(lhs, rhs, pol) => s"${lhs.showDbg} ${if pol then "|" else "&"} ${rhs.showDbg}"
case Error => "<error>"
Expand Down
6 changes: 3 additions & 3 deletions hkmc2/shared/src/test/mlscript-compile/Predef.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const Predef$class = class Predef {
tmp = "| ".repeat(this.indentLvl) ?? null;
tmp1 = " ".repeat(this.indentLvl) ?? null;
tmp2 = "\n" + tmp1;
tmp3 = msg.replaceAll("\n", tmp2) ?? null;
tmp3 = msg.replaceAll("\n", tmp2);
tmp4 = tmp + tmp3;
return console.log(tmp4) ?? null;
} else {
Expand Down Expand Up @@ -88,7 +88,7 @@ const Predef$class = class Predef {
}
call(receiver1, f2) {
return (...args) => {
return f2.call(receiver1, ...args) ?? null;
return f2.call(receiver1, ...args);
};
}
print(x3) {
Expand All @@ -102,7 +102,7 @@ const Predef$class = class Predef {
return globalThis.Array.prototype.slice.call(xs, i, tmp) ?? null;
}
tupleGet(xs1, i1) {
return globalThis.Array.prototype.at.call(xs1, i1) ?? null;
return globalThis.Array.prototype.at.call(xs1, i1);
}
stringStartsWith(string, prefix) {
return string.startsWith(prefix) ?? null;
Expand Down
10 changes: 5 additions & 5 deletions hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ class Accounting {
constructor(fileName) {
this.fileName = fileName;
let tmp;
tmp = fs.writeFileSync(this.fileName, "# Accounting\n") ?? null;
tmp = fs.writeFileSync(this.fileName, "# Accounting\n");
}
w(txt) {
return fs.appendFileSync(this.fileName, txt) ?? null;
return fs.appendFileSync(this.fileName, txt);
}
wln(txt1) {
let tmp;
tmp = Str.concat(txt1, "\n");
return fs.appendFileSync(this.fileName, tmp) ?? null;
return fs.appendFileSync(this.fileName, tmp);
}
init() {
let tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11, tmp12, tmp13;
Expand Down Expand Up @@ -134,7 +134,7 @@ class Accounting {
}) ?? null;
tmp14 = tmp13.reduce((a, b) => {
return a + b;
}, 0) ?? null;
}, 0);
tmp15 = this$Accounting.display(tmp14);
tmp16 = Str.concat(tmp11, tmp15);
tmp17 = Str.concat(tmp16, "|");
Expand All @@ -149,7 +149,7 @@ class Accounting {
}) ?? null;
tmp23 = tmp22.reduce((a, b) => {
return a + b;
}, 0) ?? null;
}, 0);
tmp24 = this$Accounting.display(tmp23);
tmp25 = Str.concat(tmp20, tmp24);
tmp26 = Str.concat(tmp25, "|");
Expand Down
Loading
Loading