Skip to content

Commit 1ae0448

Browse files
committed
merge fom hkmc2
2 parents c05b439 + a743ea4 commit 1ae0448

File tree

121 files changed

+3323
-1469
lines changed

Some content is hidden

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

121 files changed

+3323
-1469
lines changed

.github/workflows/scala.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ jobs:
2424
- name: Run tests
2525
run: sbt -J-Xmx4096M -J-Xss4M test
2626
- name: Check no changes
27-
run: git diff-files -p --exit-code
27+
run: |
28+
git update-index -q --refresh
29+
git diff-files -p --exit-code

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Wart._
22

33
enablePlugins(ScalaJSPlugin)
44

5-
val scala3Version = "3.5.1"
5+
val scala3Version = "3.5.2"
66
val directoryWatcherVersion = "0.18.0"
77

88
ThisBuild / scalaVersion := "2.13.13"

hkmc2/jvm/src/test/scala/hkmc2/DiffTestRunner.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,9 @@ object DiffTestRunner:
5555
else if filePath.ext =/= "mls" then N
5656
else S(filePath)
5757
}.toSet catch
58-
case err: Throwable => System.err.println("/!\\ git command failed with: " + err)
59-
Set.empty
58+
case err: Throwable =>
59+
System.err.println("/!\\ git command failed with: " + err)
60+
Set.empty
6061

6162
end State
6263

@@ -82,7 +83,7 @@ abstract class DiffTestRunner(state: DiffTestRunner.State)
8283
"\n\tNote: you can increase this limit by changing DiffTests.TimeLimit")
8384
// * Thread.stop() is considered bad practice because normally it's better to implement proper logic
8485
// * to terminate threads gracefully, avoiding leaving applications in a bad state.
85-
// * But here we DGAF since all the test is doing is runnign a type checker and some Node REPL,
86+
// * But here we DGAF since all the test is doing is running a type checker and some Node REPL,
8687
// * which would be a much bigger pain to make receptive to "gentle" interruption.
8788
// * It would feel extremely wrong to intersperse the pure type checker algorithms
8889
// * with ugly `Thread.isInterrupted` checks everywhere...

hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
1212
val js = NullaryCommand("js")
1313
val sjs = NullaryCommand("sjs")
1414
val showRepl = NullaryCommand("showRepl")
15+
val silent = NullaryCommand("silent")
1516

1617
private val baseScp: codegen.js.Scope =
1718
codegen.js.Scope.empty
@@ -61,17 +62,18 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
6162
output(jsStr)
6263
def mkQuery(prefix: Str, jsStr: Str) =
6364
val queryStr = jsStr.replaceAll("\n", " ")
64-
val (reply, stderr) = host.query(queryStr)
65+
val (reply, stderr) = host.query(queryStr, expectRuntimeErrors.isUnset && fixme.isUnset && todo.isUnset)
6566
reply match
6667
case ReplHost.Result(content, stdout) =>
67-
stdout match
68-
case None | Some("") =>
69-
case Some(str) =>
70-
str.splitSane('\n').foreach: line =>
71-
output(s"> ${line}")
72-
content match
73-
case "undefined" =>
74-
case _ => output(s"$prefix= ${content}")
68+
if silent.isUnset then
69+
stdout match
70+
case None | Some("") =>
71+
case Some(str) =>
72+
str.splitSane('\n').foreach: line =>
73+
output(s"> ${line}")
74+
content match
75+
case "undefined" =>
76+
case _ => output(s"$prefix= ${content}")
7577
case ReplHost.Empty =>
7678
case ReplHost.Unexecuted(message) => ???
7779
case ReplHost.Error(isSyntaxError, message) =>

hkmc2/jvm/src/test/scala/hkmc2/ReplHost.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,10 @@ class ReplHost(using TL) {
9898
stdin.write(if code.endsWith("\n") then code else code + "\n")
9999
stdin.flush()
100100

101-
def query(code: Str): (ReplHost.Reply, Str) =
101+
def query(code: Str, showStackTrace: Bool): (ReplHost.Reply, Str) =
102102
// Wrap the code with `try`-`catch` block.
103103
val wrapped =
104-
s"try { $code } catch (e) { console.log('\\u200B' + e + '\\u200B'); }"
104+
s"try { $code } catch (e) { console.log('\\u200B' + ${if showStackTrace then "e.stack" else "e"} + '\\u200B'); }"
105105
// Send the code
106106
send(wrapped)
107107
(parseQueryResult() match

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
287287
val res = freshVar(using ctx)
288288
constrain(bodyCtx, sk | res)
289289
(bodyTy, rhsCtx | res, rhsEff | bodyEff)
290-
case Term.If(Split.Cons(Branch(cond, Pattern.LitPat(BoolLit(true)), Split.Else(cons)), Split.Else(alts))) =>
290+
case Term.IfLike(Keyword.`if`, Split.Cons(Branch(cond, Pattern.LitPat(BoolLit(true)), Split.Else(cons)), Split.Else(alts))) =>
291291
val (condTy, condCtx, condEff) = typeCode(cond)
292292
val (consTy, consCtx, consEff) = typeCode(cons)
293293
val (altsTy, altsCtx, altsEff) = typeCode(alts)
@@ -347,7 +347,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
347347
case Split.Else(alts) => sign match
348348
case S(sign) => ascribe(alts, sign)
349349
case _ => typeCheck(alts)
350-
case Split.Nil => ???
350+
case Split.End => ???
351351

352352
// * Note: currently, the returned type is not used or useful, but it could be in the future
353353
private def ascribe(lhs: Term, rhs: GeneralType)(using ctx: Ctx): (GeneralType, Type) =
@@ -373,7 +373,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
373373
given Ctx = nextCtx
374374
constrain(ascribe(term, skolemize(pt))._2, Bot) // * never generalize terms with effects
375375
(pt, Bot)
376-
case (Term.If(branches), ty) => // * propagate
376+
case (Term.IfLike(Keyword.`if`, branches), ty) => // * propagate
377377
typeSplit(branches, S(ty))
378378
case (Term.Asc(term, ty), rhs) =>
379379
ascribe(term, typeType(ty))
@@ -448,11 +448,11 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
448448
effBuff += eff
449449
nestCtx += sym -> rhsTy
450450
goStats(stats)
451-
case TermDefinition(Fun, sym, params, sig, Some(body), _) :: stats =>
452-
typeFunDef(sym, params match {
453-
case S(params) => Term.Lam(params, body)
454-
case _ => body // * may be a case expressions
455-
}, sig, ctx)
451+
case TermDefinition(Fun, sym, ParamList(_, ps) :: Nil, sig, Some(body), _) :: stats =>
452+
typeFunDef(sym, Term.Lam(ps, body), sig, ctx)
453+
goStats(stats)
454+
case TermDefinition(Fun, sym, Nil, sig, Some(body), _) :: stats =>
455+
typeFunDef(sym, body, sig, ctx) // * may be a case expressions
456456
goStats(stats)
457457
case TermDefinition(Fun, sym, _, S(sig), None, _) :: stats =>
458458
ctx += sym -> typeType(sig)
@@ -536,7 +536,7 @@ class BBTyper(using elState: Elaborator.State, tl: TL):
536536
case Term.Asc(term, ty) =>
537537
val res = typeType(ty)(using ctx)
538538
ascribe(term, res)
539-
case Term.If(branches) => typeSplit(branches, N)
539+
case Term.IfLike(Keyword.`if`, branches) => typeSplit(branches, N)
540540
case Term.Region(sym, body) =>
541541
val nestCtx = ctx.nextLevel
542542
given Ctx = nestCtx

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ sealed abstract class Block extends Product with AutoLocated:
3232
case Match(scrut, arms, dflt, rst) =>
3333
arms.flatMap(_._2.definedVars).toSet ++ dflt.toList.flatMap(_.definedVars) ++ rst.definedVars
3434
case End(_) => Set.empty
35+
case Break(_, _) => Set.empty
3536
case Define(defn, rst) => rst.definedVars
3637
case TryBlock(sub, fin, rst) => sub.definedVars ++ fin.definedVars ++ rst.definedVars
38+
case Label(lbl, bod, rst) => bod.definedVars ++ rst.definedVars
3739

3840
// TODO conserve if no changes
3941
def mapTail(f: BlockTail => BlockTail): Block = this match
@@ -60,6 +62,11 @@ case class Return(res: Result, implct: Bool) extends BlockTail
6062

6163
case class Throw(exc: Result) extends BlockTail
6264

65+
case class Label(label: Local, body: Block, rest: Block) extends BlockTail
66+
67+
case class Break(label: Local, toBeginning: Bool) extends BlockTail
68+
69+
// TODO: remove this form?
6370
case class Begin(sub: Block, rest: Block) extends Block with ProductWithTail
6471

6572
case class TryBlock(sub: Block, finallyDo: Block, rest: Block) extends Block with ProductWithTail
@@ -74,7 +81,7 @@ sealed abstract class Defn:
7481
final case class TermDefn(
7582
k: syntax.TermDefKind,
7683
sym: TermSymbol,
77-
params: Opt[Ls[Param]],
84+
params: Ls[ParamList],
7885
body: Block,
7986
) extends Defn
8087

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

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,14 @@ class Lowering(using TL, Raise, Elaborator.State):
5151
k(Value.Lit(lit))
5252
case st.Ret(res) =>
5353
returnedTerm(res)
54+
case st.Asc(lhs, rhs) =>
55+
term(lhs)(k)
5456
case st.Tup(fs) =>
5557
fs.foldRight[Ls[Path] => Block](args => k(Value.Arr(args.reverse)))((a, acc) =>
5658
args => subTerm(a.value)(r => acc(r :: args))
5759
)(Nil)
60+
case st.This(sym) =>
61+
k(subst(Value.This(sym)))
5862
case st.Ref(sym) =>
5963
sym match
6064
case sym: LocalSymbol =>
@@ -101,6 +105,8 @@ class Lowering(using TL, Raise, Elaborator.State):
101105
TODO("Other argument list forms")
102106

103107
case st.Blk(Nil, res) => term(res)(k)
108+
case st.Blk(Lit(Tree.UnitLit(true)) :: stats, res) =>
109+
subTerm(st.Blk(stats, res))(k)
104110
case st.Blk((p @ (_: Ref | _: Lit)) :: stats, res) =>
105111
raise(WarningReport(msg"Pure expression in statement position" -> p.toLoc :: Nil))
106112
subTerm(st.Blk(stats, res))(k)
@@ -200,52 +206,69 @@ class Lowering(using TL, Raise, Elaborator.State):
200206
)
201207
*/
202208

203-
case iftrm: st.If =>
209+
case iftrm: st.IfLike =>
210+
211+
tl.log(s"${iftrm.kw} $iftrm")
204212

205-
tl.log(s"If $iftrm")
213+
val isIf = iftrm.kw match
214+
case syntax.Keyword.`if` => true
215+
case syntax.Keyword.`while` => false
216+
val isWhile = !isIf
206217

207218
var usesResTmp = false
208219
lazy val l =
209220
usesResTmp = true
210221
new TempSymbol(summon[Elaborator.State].nextUid, S(t))
211222

212-
def go(split: Split)(using Subst): Block = split match
223+
lazy val lbl =
224+
new TempSymbol(summon[Elaborator.State].nextUid, S(t))
225+
226+
def go(split: Split, topLevel: Bool)(using Subst): Block = split match
213227
case Split.Let(sym, trm, tl) =>
214228
term(trm): r =>
215-
Assign(sym, r, go(tl))
229+
Assign(sym, r, go(tl, topLevel))
216230
case Split.Cons(Branch(scrut, pat, tail), restSplit) =>
217231
subTerm(scrut): sr =>
218232
tl.log(s"Binding scrut $scrut to $sr ${summon[Subst].map}")
219233
val cse = pat match
220-
case Pattern.LitPat(lit) => Case.Lit(lit) -> go(tail)
234+
case Pattern.LitPat(lit) => Case.Lit(lit) -> go(tail, topLevel = false)
221235
case Pattern.Class(cls, args0, _refined) =>
222236
val args = args0.getOrElse(Nil)
223237
val clsDefn = cls.defn.getOrElse(die)
224238
val clsParams = clsDefn.paramsOpt.getOrElse(Nil)
225239
assert(args0.isEmpty || clsParams.length === args.length)
226240
def mkArgs(args: Ls[Param -> BlockLocalSymbol])(using Subst): Case -> Block = args match
227-
case Nil => Case.Cls(cls) -> go(tail)
241+
case Nil => Case.Cls(cls) -> go(tail, topLevel = false)
228242
case (param, arg) :: args =>
229243
// summon[Subst].+(arg -> Value.Ref(new TempSymbol(summon[Elaborator.State].nextUid, N)))
230244
// Assign(arg, Select(sr, Tree.Ident("head")), mkArgs(args))
231245
val (cse, blk) = mkArgs(args)
232246
(cse, Assign(arg, Select(sr, param.sym.id/*FIXME incorrect Ident?*/), blk))
233247
mkArgs(clsParams.zip(args))
234248
Match(sr, cse :: Nil,
235-
// elseBranch,
236-
S(go(restSplit)),
249+
S(go(restSplit, topLevel = true)),
237250
End()
238251
)
239252
case Split.Else(els) =>
240-
if k.isInstanceOf[Ret] then term(els)(k)
241-
else term(els)(r => Assign(l, r, End()))
242-
case Split.Nil =>
253+
if k.isInstanceOf[Ret] && isIf then term(els)(k)
254+
else
255+
term(els): r =>
256+
Assign(l, r,
257+
if isWhile && !topLevel then Break(lbl, toBeginning = true)
258+
// if isWhile then Break(lbl, toBeginning = !topLevel)
259+
else End()
260+
)
261+
case Split.End =>
243262
Throw(Instantiate(Value.Ref(Elaborator.Ctx.errorSymbol),
244263
Value.Lit(syntax.Tree.StrLit("match error")) :: Nil)) // TODO add failed-match scrutinee info
245264

246-
if k.isInstanceOf[Ret] then go(iftrm.normalized)
247-
else Begin(
248-
go(iftrm.normalized),
265+
if k.isInstanceOf[Ret] && isIf then go(iftrm.normalized, topLevel = true)
266+
else
267+
val body = if isWhile
268+
then Label(lbl, go(iftrm.normalized, topLevel = true), End())
269+
else go(iftrm.normalized, topLevel = true)
270+
Begin(
271+
body,
249272
if usesResTmp then k(Value.Ref(l))
250273
else k(Value.Lit(syntax.Tree.UnitLit(true))) // * it seems this currently never happens
251274
)

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

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import hkmc2.semantics.Elaborator
1212
import hkmc2.syntax.Tree
1313
import hkmc2.semantics.TopLevelSymbol
1414
import hkmc2.semantics.MemberSymbol
15+
import hkmc2.semantics.ParamList
16+
import hkmc2.codegen.Value.Lam
17+
import hkmc2.semantics.ImportedSymbol
1518

1619

1720
// TODO factor some logic for other codegen backends
@@ -57,6 +60,8 @@ class JSBuilder extends CodeBuilder:
5760
doc"${result(Value.This(owner))}.${ts.id.name}"
5861
case N =>
5962
ts.id.name
63+
case imp: ImportedSymbol =>
64+
doc"${getVar(imp.base)}.${imp.id.name}"
6065
case _ => summon[Scope].lookup_!(l)
6166

6267
def result(r: Result)(using Raise, Scope): Document = r match
@@ -99,11 +104,14 @@ class JSBuilder extends CodeBuilder:
99104
result(Value.This(sym))
100105
val (thisProxy, res) = scope.nestRebindThis(defn.sym):
101106
val defnJS = defn match
102-
case TermDefn(syntax.Fun, sym, N, body) =>
107+
case TermDefn(syntax.Fun, sym, Nil, body) =>
103108
TODO("getters")
104-
case TermDefn(syntax.Fun, sym, S(ps), bod) =>
105-
val vars = ps.map(p => scope.allocateName(p.sym)).mkDocument(", ")
106-
doc"function ${sym.nme}($vars) { #{ # ${body(bod)} #} # }"
109+
case TermDefn(syntax.Fun, sym, ParamList(_, ps) :: pss, bod) =>
110+
val paramList = ps.map(p => scope.allocateName(p.sym)).mkDocument(", ")
111+
val result = pss.foldRight(bod):
112+
case (ParamList(_, ps), block) =>
113+
Return(Lam(ps, block), false)
114+
doc"function ${sym.nme}(${paramList}) { #{ # ${body(result)} #} # }"
107115
case ClsDefn(sym, syntax.Cls, mtds, flds, ctor) =>
108116
val clsDefn = sym.defn.getOrElse(die)
109117
val clsParams = clsDefn.paramsOpt.getOrElse(Nil)
@@ -118,11 +126,12 @@ class JSBuilder extends CodeBuilder:
118126
}) { #{ # ${
119127
ctorCode.stripBreaks
120128
} #} # }${
121-
mtds.map: td =>
122-
val vars = td.params.getOrElse(Nil).map(p => scope.allocateName(p.sym)).mkDocument(", ")
123-
doc" # ${td.sym.nme}($vars) { #{ # ${
124-
body(td.body)
125-
} #} # }"
129+
mtds.map:
130+
case td @ TermDefn(_, _, ParamList(_, ps) :: Nil, _) =>
131+
val vars = ps.map(p => scope.allocateName(p.sym)).mkDocument(", ")
132+
doc" # ${td.sym.nme}($vars) { #{ # ${
133+
body(td.body)
134+
} #} # }"
126135
.mkDocument(" ")
127136
}${
128137
if mtds.exists(_.sym.nme == "toString")
@@ -194,6 +203,18 @@ class JSBuilder extends CodeBuilder:
194203

195204
case Throw(res) =>
196205
doc" # throw ${result(res)}"
206+
207+
case Break(lbl, false) =>
208+
doc" # break ${getVar(lbl)}"
209+
210+
case Break(lbl, true) =>
211+
doc" # continue ${getVar(lbl)}"
212+
213+
case Label(lbl, bod, rst) =>
214+
scope.allocateName(lbl)
215+
doc" # ${getVar(lbl)}: while (true) { #{ ${
216+
returningTerm(bod)
217+
} # break; #} # }${returningTerm(rst)}"
197218

198219
case TryBlock(sub, fin, rst) =>
199220
doc" # try { #{ ${returningTerm(sub)

0 commit comments

Comments
 (0)