Skip to content

Commit 2cfa234

Browse files
committed
Merge remote-tracking branch 'upstream/hkmc2' into logic-subtyping
2 parents afb08c5 + 12479ae commit 2cfa234

36 files changed

+873
-10
lines changed

hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class ParserSetup(file: os.Path, dbgParsing: Bool)(using Elaborator.State, Raise
4141
class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Unit)(using Config):
4242

4343
val runtimeFile: os.Path = preludeFile/os.up/os.up/os.up/"mlscript-compile"/"Runtime.mjs"
44+
val termFile: os.Path = preludeFile/os.up/os.up/os.up/"mlscript-compile"/"Term.mjs"
4445

4546

4647
val report = ReportFormatter: outputConsumer =>
@@ -85,7 +86,7 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni
8586
val resolver = Resolver(rtl)
8687
resolver.traverseBlock(blk0)(using Resolver.ICtx.empty)
8788
val blk = new semantics.Term.Blk(
88-
semantics.Import(State.runtimeSymbol, runtimeFile.toString) :: blk0.stats,
89+
semantics.Import(State.runtimeSymbol, runtimeFile.toString) :: semantics.Import(State.termSymbol, termFile.toString) :: blk0.stats,
8990
blk0.res
9091
)
9192
val low = ltl.givenIn:
@@ -96,6 +97,9 @@ class MLsCompiler(preludeFile: os.Path, mkOutput: ((Str => Unit) => Unit) => Uni
9697
val le = low.program(blk)
9798
val baseScp: utils.Scope =
9899
utils.Scope.empty
100+
// * This line serves for `import.meta.url`, which retrieves directory and file names of mjs files.
101+
// * Having `module id"import" with ...` in `prelude.mls` will generate `globalThis.import` that is undefined.
102+
baseScp.bindings += Elaborator.State.importSymbol -> "import"
99103
val nestedScp = baseScp.nest
100104
val nme = file.baseName
101105
val exportedSymbol = parsed.definedSymbols.find(_._1 === nme).map(_._2)

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

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package codegen
33

44
import scala.language.implicitConversions
55
import scala.annotation.tailrec
6+
import os.{Path as AbsPath, RelPath}
67

78
import mlscript.utils.*, shorthands.*
89
import utils.*
@@ -549,7 +550,9 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
549550
subTerm_nonTail(finallyDo)(_ => End()),
550551
k(Value.Ref(l))
551552
)
552-
553+
554+
case Quoted(body) => quote(body)(k)
555+
553556
// * BbML-specific cases: t.Cls#field and mutable operations
554557
case sp @ SelProj(prefix, _, proj) =>
555558
setupSelection(prefix, proj, sp.sym)(k)
@@ -587,6 +590,127 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
587590

588591
// case _ =>
589592
// subTerm(t)(k)
593+
594+
def setupTerm(name: Str, args: Ls[Path])(k: Result => Block)(using Subst): Block =
595+
k(Instantiate(Value.Ref(State.termSymbol).selSN(name), args))
596+
597+
def setupQuotedKeyword(kw: Str): Path =
598+
Value.Ref(State.termSymbol).selSN("Keyword").selSN(kw)
599+
600+
def setupSymbol(symbol: Local)(k: Result => Block)(using Subst): Block =
601+
k(Instantiate(Value.Ref(State.termSymbol).selSN("Symbol"), Value.Lit(Tree.StrLit(symbol.nme)) :: Nil))
602+
603+
def quotePattern(p: Pattern)(k: Result => Block)(using Subst): Block = p match
604+
case Pattern.Lit(lit) => setupTerm("LitPattern", Value.Lit(lit) :: Nil)(k)
605+
case _ => // TODO
606+
raise(ErrorReport(
607+
msg"Unsupported quasiquote pattern type ${p.showDbg}" ->
608+
p.toLoc :: Nil,
609+
source = Diagnostic.Source.Compilation
610+
))
611+
End("error")
612+
613+
614+
def quoteSplit(split: Split)(k: Result => Block)(using Subst): Block = split match
615+
case Split.Cons(Branch(scrutinee, pattern, continuation), tail) => quote(scrutinee): r1 =>
616+
val l1, l2, l3, l4, l5 = new TempSymbol(N)
617+
blockBuilder.assign(l1, r1)
618+
.chain(b => quotePattern(pattern)(r2 => Assign(l2, r2, b)))
619+
.chain(b => quoteSplit(continuation)(r3 => Assign(l3, r3, b)))
620+
.chain(b => setupTerm("Branch", (l1 :: l2 :: l3 :: Nil).map(s => Value.Ref(s)))(r4 => Assign(l4, r4, b)))
621+
.chain(b => quoteSplit(tail)(r5 => Assign(l5, r5, b)))
622+
.rest(setupTerm("Cons", (l4 :: l5 :: Nil).map(s => Value.Ref(s)))(k))
623+
case Split.Let(sym, term, tail) => setupSymbol(sym): r1 =>
624+
val l1, l2, l3 = new TempSymbol(N)
625+
blockBuilder.assign(l1, r1)
626+
.chain(b => setupTerm("Ref", Value.Ref(l1) :: Nil)(r => Assign(sym, r, b)))
627+
.chain(b => quote(term)(r2 => Assign(l2, r2, b)))
628+
.chain(b => quoteSplit(tail)(r3 => Assign(l3, r3, b)))
629+
.rest(setupTerm("Let", (l1 :: l2 :: l3 :: Nil).map(s => Value.Ref(s)))(k))
630+
case Split.Else(default) => quote(default): r =>
631+
val l = new TempSymbol(N)
632+
Assign(l, r, setupTerm("Else", Value.Ref(l) :: Nil)(k))
633+
case Split.End => setupTerm("End", Nil)(k)
634+
635+
lazy val setupFilename: Path =
636+
val state = summon[State]
637+
Value.Ref(state.importSymbol).selSN("meta").selSN("url")
638+
639+
def quote(t: st)(k: Result => Block)(using Subst): Block = t match
640+
case Lit(lit) =>
641+
setupTerm("Lit", Value.Lit(lit) :: Nil)(k)
642+
case Ref(sym) if Elaborator.binaryOps.contains(sym.nme) => // builtin symbols
643+
val l = new TempSymbol(N)
644+
setupTerm("Builtin", Value.Lit(Tree.StrLit(sym.nme)) :: Nil)(k)
645+
case Ref(sym) =>
646+
k(Value.Ref(sym))
647+
case SynthSel(Ref(sym: ModuleSymbol), name) => // Local cross-stage references
648+
setupSymbol(sym): r1 =>
649+
val l1, l2 = new TempSymbol(N)
650+
Assign(l1, r1, setupTerm("CSRef", Value.Ref(l1) :: setupFilename :: Value.Lit(syntax.Tree.UnitLit(false)) :: Nil)(r2 =>
651+
Assign(l2, r2, setupTerm("Sel", Value.Ref(l2) :: Value.Lit(syntax.Tree.StrLit(name.name)) :: Nil)(k))
652+
))
653+
case SynthSel(Ref(sym: BlockMemberSymbol), name) => // Multi-file cross-stage references
654+
(t.toLoc, sym.toLoc) match
655+
case (S(Loc(_, _, Origin(base, _, _))), S(Loc(_, _, Origin(filename, _, _)))) => setupSymbol(sym): r1 =>
656+
val l1, l2 = new TempSymbol(N)
657+
val basePath = base / os.up
658+
val targetPath = filename
659+
val relPath = targetPath.relativeTo(basePath).toString
660+
Assign(l1, r1, setupTerm("CSRef", Value.Ref(l1) :: setupFilename :: Value.Lit(syntax.Tree.StrLit(relPath)) :: Nil)(r2 =>
661+
Assign(l2, r2, setupTerm("Sel", Value.Ref(l2) :: Value.Lit(syntax.Tree.StrLit(name.name)) :: Nil)(k))
662+
))
663+
case _ =>
664+
raise(ErrorReport(
665+
msg"Cannot refer to imported module ${sym.nme} due to the lack of path." ->
666+
t.toLoc :: Nil,
667+
source = Diagnostic.Source.Compilation
668+
))
669+
End("error")
670+
case Lam(params, body) =>
671+
def rec(ps: Ls[LocalSymbol & NamedSymbol], ds: Ls[Path])(k: Result => Block)(using Subst): Block = ps match
672+
case Nil => quote(body): r =>
673+
val l = new TempSymbol(N)
674+
Assign(l, r, setupTerm("Lam", Value.Arr(ds.reverse.map(_.asArg)) :: Value.Ref(l) :: Nil)(k))
675+
case sym :: rest =>
676+
setupSymbol(sym): r =>
677+
val l = new TempSymbol(N)
678+
Assign(l, r, setupTerm("Ref", Value.Ref(l) :: Nil): r1 =>
679+
Assign(sym, r1, rec(rest, Value.Ref(l) :: ds)(k)))
680+
rec(params.params.map(_.sym), Nil)(k) // TODO: restParam?
681+
case App(lhs, Tup(rhs)) => quote(lhs): r1 =>
682+
def rec(es: Ls[Elem], xs: Ls[Path])(k: Result => Block): Block = es match
683+
case Nil => setupTerm("Tup", Value.Arr(xs.reverse.map(_.asArg)) :: Nil): r2 =>
684+
val l1, l2 = new TempSymbol(N)
685+
Assign(l1, r1, Assign(l2, r2, setupTerm("App", Value.Ref(l1) :: Value.Ref(l2) :: Nil)(k)))
686+
case Fld(_, t, _) :: rest => quote(t): r2 =>
687+
val l = new TempSymbol(N)
688+
Assign(l, r2, rec(rest, Value.Ref(l) :: xs)(k))
689+
rec(rhs, Nil)(k)
690+
case Blk(LetDecl(sym, _) :: DefineVar(sym2, rhs) :: Nil, res) => // Let bindings
691+
require(sym2 is sym)
692+
setupSymbol(sym){r1 =>
693+
val l1, l2, l3, l4, l5 = new TempSymbol(N)
694+
blockBuilder.assign(l1, r1)
695+
.chain(b => setupTerm("Ref", Value.Ref(l1) :: Nil)(r => Assign(sym, r, b)))
696+
.chain(b => quote(rhs)(r2 => Assign(l2, r2, b)))
697+
.chain(b => quote(res)(r3 => Assign(l3, r3, b)))
698+
.chain(b => setupTerm("LetDecl", Value.Ref(l1) :: Nil)(r4 => Assign(l4, r4, b)))
699+
.chain(b => setupTerm("DefineVar", Value.Ref(l1) :: Value.Ref(l2) :: Nil)(r5 => Assign(l5, r5, b)))
700+
.rest(setupTerm("Blk", Value.Arr((l4 :: l5 :: Nil).map(s => Value.Ref(s).asArg)) :: Value.Ref(l3) :: Nil)(k))
701+
}
702+
case IfLike(syntax.Keyword.`if`, split) => quoteSplit(split): r =>
703+
val l = new TempSymbol(N)
704+
Assign(l, r, setupTerm("IfLike", setupQuotedKeyword("If") :: Value.Ref(l) :: Nil)(k))
705+
case Unquoted(body) => term(body)(k)
706+
case _ =>
707+
raise(ErrorReport(
708+
msg"Unsupported quasiquote type ${t.describe}" ->
709+
t.toLoc :: Nil,
710+
source = Diagnostic.Source.Compilation
711+
))
712+
End("error")
713+
590714

591715
def gatherMembers(clsBody: ObjBody)(using Subst): (Ls[FunDefn], Ls[BlockMemberSymbol], Ls[TermSymbol], Block) =
592716
val mtds = clsBody.methods

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ trait BlockImpl(using Elaborator.State):
1717
stmt.desugared match
1818
case PossiblyAnnotated(anns, h @ Hndl(body = N)) =>
1919
PossiblyAnnotated(anns, h.copy(body = S(Block(stmts)))) :: Nil
20+
case PossiblyAnnotated(anns, TypeDef(syntax.Cls, Ident(name), rhs, S(Block(Constructor(Block(ctors)) :: rest)))) =>
21+
PossiblyAnnotated(anns, TypeDef(syntax.Cls, Ident(name), rhs, if rest.isEmpty then N else S(Block(rest)))) ::
22+
(ctors.map(head => PossiblyAnnotated(anns, TypeDef(syntax.Cls, InfixApp(head, syntax.Keyword.`extends`, Ident(name)), N, N))))
23+
::: desug(stmts)
2024
case stmt => stmt :: desug(stmts)
2125
case Nil => Nil
2226
desug(stmts)

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ object Elaborator:
192192
val suid = new Uid.Symbol.State
193193
given State = this
194194
val globalThisSymbol = TopLevelSymbol("globalThis")
195+
// In JavaScript, `import` can be used for getting current file path, as `import.meta`
196+
val importSymbol = new VarSymbol(syntax.Tree.Ident("import"))
195197
val runtimeSymbol = TempSymbol(N, "runtime")
198+
val termSymbol = TempSymbol(N, "Term")
196199
val effectSigSymbol = ClassSymbol(TypeDef(syntax.Cls, Dummy, N, N), Ident("EffectSig"))
197200
val nonLocalRetHandlerTrm =
198201
val id = new Ident("NonLocalReturn")
@@ -702,6 +705,9 @@ extends Importer:
702705
case Keywrd(kw) =>
703706
raise(ErrorReport(msg"Unexpected keyword '${kw.name}' in this position." -> tree.toLoc :: Nil))
704707
Term.Error
708+
case Constructor(delc) =>
709+
raise(ErrorReport(msg"Unsupported constructor in this position." -> tree.toLoc :: Nil))
710+
Term.Error
705711
// case _ =>
706712
// ???
707713

hkmc2/shared/src/main/scala/hkmc2/semantics/ucs/Desugarer.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -612,9 +612,10 @@ class Desugarer(val elaborator: Elaborator)
612612
fallback
613613
case InfixApp(id: Ident, Keyword.`:`, pat) => fallback => ctx =>
614614
val sym = VarSymbol(id)
615-
val ctxWithAlias = ctx + (id.name -> sym)
615+
val ctx2 = ctx
616+
// + (id.name -> sym) // * This binds the field's name in the context; probably surprising
616617
Split.Let(sym, ref.sel(id, N),
617-
expandMatch(sym, pat, sequel)(fallback)(ctxWithAlias))
618+
expandMatch(sym, pat, sequel)(fallback)(ctx2))
618619
case Block(st :: Nil) => fallback => ctx =>
619620
expandMatch(scrutSymbol, st, sequel)(fallback)(ctx)
620621
// case Block(sts) => fallback => ctx => // TODO

hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,12 @@ class Lexer(origin: Origin, dbg: Bool)(using raise: Raise):
306306
IDENT(n, false),
307307
loc(i + 1, j)
308308
) :: Nil)(loc(i, j))))
309+
case 'i' if i + 2 < length && bytes(i + 1) === 'd' && bytes(i + 2) === '"' =>
310+
val (n, j) = takeWhile(i + 3)(isIdentChar)
311+
lex(j + 1, ind, next(j + 1,
312+
if bytes(j) === '"' && !n.isEmpty() then ESC_IDENT(n)
313+
else { pe(msg"unexpected identifier escape"); ERROR }
314+
))
309315
case ';' =>
310316
val j = i + 1
311317
// lex(j, ind, next(j, SEMI))
@@ -611,6 +617,7 @@ object Lexer:
611617
case (COMMENT(text: String), _) => "/*" + text + "*/"
612618
case (SUSPENSION(true), _) => "..."
613619
case (SUSPENSION(false), _) => ".."
620+
case (ESC_IDENT(name), _) => name
614621
def printTokens(ts: Ls[TokLoc]): Str =
615622
ts.iterator.map(printToken).mkString("|", "|", "|")
616623

hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ class ParseRules(using State):
296296
){ (body, _: Unit) => Outer(S(body)) },
297297
End(Outer(N))
298298
),
299+
Kw(`constructor`):
300+
ParseRule("constructor keyword"):
301+
Blk(
302+
ParseRule(s"constructor block")(End(()))
303+
) { case (body, _) => Tree.Constructor(body) },
299304
Kw(`fun`)(termDefBody(Fun)),
300305
Kw(`val`)(termDefBody(ImmutVal)),
301306
Kw(`use`)(termDefBody(Ins)),

hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ abstract class Parser(
513513
consume
514514
val annotation = simpleExpr(AppPrec)
515515
Annotated(annotation, simpleExpr(prec))
516+
case (ESC_IDENT(name), loc) :: _ =>
517+
consume
518+
val id = Tree.Ident(name).withLoc(S(loc))
519+
exprCont(id, prec, allowNewlines = true)
516520
case (IDENT(nme, sym), loc) :: _ =>
517521
Keyword.all.get(nme) match
518522
case S(kw) => // * Expressions starting with keywords should be handled in parseRule

hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ sealed abstract class Token:
2929
case COMMENT(text) => "comment"
3030
case SUSPENSION(true) => "'...' ellipsis"
3131
case SUSPENSION(false) => "'..' ellipsis"
32+
case ESC_IDENT(_) => "identifier"
3233

3334
/** Type of 'Structured Tokens' aka 'Strokens',
3435
* which use a `BRACKETS` construct instead of `OPEN_BRACKET`/`CLOSE_BRACKET` and `INDENT`/`DEINDENT` */
@@ -51,6 +52,7 @@ final case class CLOSE_BRACKET(k: BracketKind) extends Token
5152
final case class BRACKETS(k: BracketKind, contents: Ls[Stroken -> Loc])(val innerLoc: Loc) extends Token with Stroken
5253
final case class COMMENT(text: String) extends Token with Stroken
5354
final case class SUSPENSION(dotDotDot: Bool) extends Token with Stroken
55+
final case class ESC_IDENT(name: String) extends Token with Stroken
5456

5557

5658
sealed abstract class BracketKind:

hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ enum Tree extends AutoLocated:
8686
case Outer(name: Opt[Tree])
8787
case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree])
8888
case Annotated(annotation: Tree, target: Tree)
89+
case Constructor(decl: Tree)
8990

9091
def children: Ls[Tree] = this match
9192
case _: Empty | _: Error | _: Ident | _: Literal | _: Under | _: Unt => Nil
@@ -125,6 +126,7 @@ enum Tree extends AutoLocated:
125126
case Def(lhs, rhs) => lhs :: rhs :: Nil
126127
case Spread(_, _, body) => body.toList
127128
case Annotated(annotation, target) => annotation :: target :: Nil
129+
case Constructor(decl) => decl :: Nil
128130
case MemberProj(cls, name) => cls :: Nil
129131
case Keywrd(kw) => Nil
130132
case Dummy => Nil
@@ -171,6 +173,7 @@ enum Tree extends AutoLocated:
171173
case Spread(_, _, _) => "spread"
172174
case Annotated(_, _) => "annotated"
173175
case Open(_) => "open"
176+
case Constructor(_) => "constructor"
174177
case MemberProj(_, _) => "member projection"
175178
case Keywrd(kw) => s"'${kw.name}' keyword"
176179
case Unt() => "unit"
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import "./Example.mls"
2+
import "./quotes/CSPNest.mls"
3+
open Example
4+
open CSPNest
5+
6+
module CSP with ...
7+
8+
fun test() = 123
9+
fun foo() =
10+
`test`() `+ `1
11+
fun bar() =
12+
`inc`(`0)
13+
fun baz() = `nest_f`()

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import runtime from "./Runtime.mjs";
2+
import Term from "./Term.mjs";
23
import Runtime from "./Runtime.mjs";
34
import Rendering from "./Rendering.mjs";
45
let Predef1;

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ fun (???) notImplementedError = throw Error("Not implemented")
5656

5757
fun tuple(...xs) = xs
5858

59+
5960
val foldl = fold
6061

6162
// fun foldr(f)(...rest, init) = // TODO allow this syntax
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import "./Predef.mls"
2+
3+
module QuoteExample with
4+
fun foo() = `1 `+ `1
5+
fun inc() = x `=> x `+ `1
6+
fun power(x) = case
7+
0 then `1.0
8+
n then x `*. power(x)(n - 1)
9+
fun bind(rhs, k) = `let x = rhs `in k(x)
10+
fun body(x, y) = case
11+
0 then x
12+
1 then y
13+
n then bind of x `+ y, (z => body(y, z)(n - 1))
14+
fun gib(n) = (x, y) `=> body(x, y)(n)
15+
fun safeDiv() = (x, y, d) `=> `if y `== `0.0 then d else x `/ y
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import "./Term.mls"
2+
import "./QuoteExample.mls"
3+
4+
5+
module QuoteExample2 with
6+
fun codegen() =
7+
Term.codegen(QuoteExample.foo(), "./hkmc2/shared/src/test/mlscript-compile/quotes/QuoteFoo.mls")
8+
Term.codegen(QuoteExample.inc(), "./hkmc2/shared/src/test/mlscript-compile/quotes/QuoteInc.mls")
9+
fun genCubic() =
10+
Term.codegen(x `=> QuoteExample.power(x)(3), "./hkmc2/shared/src/test/mlscript-compile/quotes/Cubic.mls")
11+
fun genGib12() =
12+
Term.codegen(QuoteExample.gib(12), "./hkmc2/shared/src/test/mlscript-compile/quotes/Gib12.mls")
13+
fun genSafeDiv() =
14+
Term.codegen(QuoteExample.safeDiv(), "./hkmc2/shared/src/test/mlscript-compile/quotes/SafeDiv.mls")
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
import "./Predef.mls"
3+
open Predef
4+
5+
import "./Iter.mls"
6+
7+
8+
module Record with...
9+
10+
fun steal(from, ...fields) =
11+
let rcd = new Object
12+
fields Iter.each of f => set rcd.(f) = from.(f)
13+
rcd
14+
15+

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import runtime from "./Runtime.mjs";
2+
import Term from "./Term.mjs";
23
import RuntimeJS from "./RuntimeJS.mjs";
34
import Rendering from "./Rendering.mjs";
45
let Runtime1;

0 commit comments

Comments
 (0)