Skip to content

Commit a83d71c

Browse files
Merge branch 'hkmc2' into move-implicit-resolution
2 parents cfa6077 + b81146d commit a83d71c

File tree

118 files changed

+8635
-2110
lines changed

Some content is hidden

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

118 files changed

+8635
-2110
lines changed

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
5858
val lift: Bool = config.liftDefns.isDefined
5959

6060
private lazy val unreachableFn =
61-
Select(Select(Value.Ref(State.globalThisSymbol), Tree.Ident("Predef"))(N), Tree.Ident("unreachable"))(N)
61+
Select(Value.Ref(State.runtimeSymbol), Tree.Ident("unreachable"))(N)
6262

6363
def unit: Path =
6464
Select(Value.Ref(State.runtimeSymbol), Tree.Ident("Unit"))(S(summon[Ctx].builtins.Unit))
@@ -762,9 +762,9 @@ trait LoweringTraceLog(instrument: Bool)(using TL, Raise, State)
762762
extension (k: Block => Block)
763763
def |>: (b: Block): Block = k(b)
764764

765-
private val traceLogFn = selFromGlobalThis("Predef", "TraceLogger", "log")
766-
private val traceLogIndentFn = selFromGlobalThis("Predef", "TraceLogger", "indent")
767-
private val traceLogResetFn = selFromGlobalThis("Predef", "TraceLogger", "resetIndent")
765+
private val traceLogFn = Value.Ref(State.runtimeSymbol).selSN("TraceLogger").selSN("log")
766+
private val traceLogIndentFn = Value.Ref(State.runtimeSymbol).selSN("TraceLogger").selSN("indent")
767+
private val traceLogResetFn = Value.Ref(State.runtimeSymbol).selSN("TraceLogger").selSN("resetIndent")
768768
private val strConcatFn = selFromGlobalThis("String", "prototype", "concat", "call")
769769
private val inspectFn = selFromGlobalThis("util", "inspect")
770770

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

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ abstract class CodeBuilder:
2323

2424

2525
class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
26+
import JSBuilder.*
2627

2728
def checkMLsCalls: Bool = false
2829
def checkSelections: Bool = false
@@ -76,6 +77,8 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
7677
else summon[Scope].findThis_!(ts)
7778
case _ => summon[Scope].lookup_!(l)
7879

80+
def runtimeVar(using Raise, Scope): Document = getVar(State.runtimeSymbol)
81+
7982
def argument(a: Arg)(using Raise, Scope): Document =
8083
if a.spread then doc"...${result(a.value)}" else result(a.value)
8184

@@ -94,7 +97,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
9497

9598
def result(r: Result)(using Raise, Scope): Document = r match
9699
case Value.This(sym) => summon[Scope].findThis_!(sym)
97-
case Value.Lit(Tree.StrLit(value)) => JSBuilder.makeStringLiteral(value)
100+
case Value.Lit(Tree.StrLit(value)) => makeStringLiteral(value)
98101
case Value.Lit(lit) => lit.idStr
99102
case Value.Ref(l: BuiltinSymbol) =>
100103
if l.nullary then l.nme
@@ -128,20 +131,20 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
128131
val argsDoc = args.map(argument).mkDocument(", ")
129132
if c.isMlsFun
130133
then if checkMLsCalls
131-
then doc"${getVar(State.runtimeSymbol)}.checkCall(${base}(${argsDoc}))"
134+
then doc"$runtimeVar.checkCall(${base}(${argsDoc}))"
132135
else doc"${base}(${argsDoc})"
133-
else doc"${getVar(State.runtimeSymbol)}.safeCall(${base}(${argsDoc}))"
136+
else doc"$runtimeVar.safeCall(${base}(${argsDoc}))"
134137
case Value.Lam(ps, bod) => scope.nest givenIn:
135138
val (params, bodyDoc) = setupFunction(none, ps, bod)
136139
doc"($params) => ${ braced(bodyDoc) }"
137140
case Select(qual, id) =>
138141
val name = id.name
139142
doc"${result(qual)}${
140-
if JSBuilder.isValidFieldName(name)
143+
if isValidFieldName(name)
141144
then doc".$name"
142145
else name.toIntOption match
143146
case S(index) => s"[$index]"
144-
case N => s"[${JSBuilder.makeStringLiteral(name)}]"
147+
case N => s"[${makeStringLiteral(name)}]"
145148
}"
146149
case DynSelect(qual, fld, ai) =>
147150
doc"${result(qual)}[${result(fld)}]"
@@ -199,7 +202,9 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
199202
val name = if sym.nameIsMeaningful then S(sym.nme) else N
200203
val (params, bodyDoc) = setupFunction(name, ps, result)
201204
if sym.nameIsMeaningful then
202-
doc"${getVar(sym)} = function ${sym.nme}($params) ${ braced(bodyDoc) };"
205+
// If the name is not valid JavaScript identifiers, do not use it in the generated function.
206+
val nme = if isValidIdentifier(sym.nme) then sym.nme else ""
207+
doc"${getVar(sym)} = function $nme($params) ${ braced(bodyDoc) };"
203208
else
204209
// in JS, let name = (0, function (args) => {} ) prevents function's name from being bound to `name`
205210
doc"${getVar(sym)} = (undefined, function ($params) ${ braced(bodyDoc) });"
@@ -262,9 +267,9 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
262267
.flatMap:
263268
case td @ FunDefn(_, _, ps :: pss, bod) => S:
264269
doc" # get ${td.sym.nme}$$__checkNotMethod() { ${
265-
getVar(State.runtimeSymbol)
266-
}.deboundMethod(${JSBuilder.makeStringLiteral(td.sym.nme)}, ${
267-
JSBuilder.makeStringLiteral(sym.nme)
270+
runtimeVar
271+
}.deboundMethod(${makeStringLiteral(td.sym.nme)}, ${
272+
makeStringLiteral(sym.nme)
268273
}); }"
269274
case _ => N
270275
.mkDocument(" ")
@@ -286,10 +291,10 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
286291
else doc""" # ${mtdPrefix}toString() { return "${sym.nme}${
287292
if paramsOpt.isEmpty then doc"""""""
288293
else doc"""(" + ${
289-
ctorFields.headOption.fold("\"\"")(f => "globalThis.Predef.render(this" + fieldSelect(f._1.name) + ")")
294+
ctorFields.headOption.fold("\"\"")(f => "runtime.render(this" + fieldSelect(f._1.name) + ")")
290295
}${
291296
ctorFields.tailOption.fold("")(_.map(f =>
292-
""" + ", " + globalThis.Predef.render(this""" + fieldSelect(f._1.name) + ")").mkString)
297+
""" + ", " + runtime.render(this""" + fieldSelect(f._1.name) + ")").mkString)
293298
} + ")""""
294299
}; }"""
295300
} #} # }"
@@ -325,7 +330,8 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
325330
val funBod = pss.foldRight(bod):
326331
case (psDoc, doc_) => doc"($psDoc) => $doc_"
327332
val funBodRet = if pss.isEmpty then funBod else braced(doc" # return $funBod")
328-
S(doc"function ${sym.nme}($ps) ${ funBodRet }")
333+
val nme = if isValidIdentifier(sym.nme) then sym.nme else ""
334+
S(doc"function $nme($ps) ${ funBodRet }")
329335
case Nil => N
330336

331337
ownr match
@@ -464,17 +470,14 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
464470

465471
def worksheet(p: Program)(using Raise, Scope): (Document, Document) =
466472
reserveNames(p)
467-
p.imports.foreach: i =>
468-
i._1 -> scope.allocateName(i._1)
469-
val imps = p.imports.map: i =>
470-
val v = doc"this.${getVar(i._1)}"
471-
doc"""$v = await import("${i._2.toString
472-
}"); # if ($v.default !== undefined) $v = $v.default;"""
473-
blockPreamble(p.main) -> (imps.mkDocument(doc" # ") :/: returningTerm(p.main, endSemi = false).stripBreaks)
473+
lazy val imps = p.imports.map: i =>
474+
doc"""${getVar(i._1)} = await import("${i._2.toString}").then(m => m.default ?? m);"""
475+
blockPreamble(p.imports.map(_._1).toSeq ++ p.main.definedVars.toSeq) ->
476+
(imps.mkDocument(doc" # ") :/: returningTerm(p.main, endSemi = false).stripBreaks)
474477

475-
def blockPreamble(t: Block)(using Raise, Scope): Document =
478+
def blockPreamble(ss: Iterable[Symbol])(using Raise, Scope): Document =
476479
// TODO document: mutable var assnts require the lookup
477-
val vars = t.definedVars.toSeq.filter(scope.lookup(_).isEmpty).sortBy(_.uid).iterator.map(l =>
480+
val vars = ss.filter(scope.lookup(_).isEmpty).toArray.sortBy(_.uid).iterator.map(l =>
478481
l -> scope.allocateName(l))
479482
if vars.isEmpty then doc"" else
480483
doc" # let " :: vars.map: (_, nme) =>
@@ -483,7 +486,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
483486
:: doc";"
484487

485488
def block(t: Block, endSemi: Bool)(using Raise, Scope): Document =
486-
blockPreamble(t) :: returningTerm(t, endSemi)
489+
blockPreamble(t.definedVars) :: returningTerm(t, endSemi)
487490

488491
def body(t: Block, endSemi: Bool)(using Raise, Scope): Document = scope.nest givenIn:
489492
block(t, endSemi)
@@ -616,12 +619,12 @@ trait JSBuilderArgNumSanityChecks(using Config, Elaborator.State)
616619
val paramRest = params.restParam.map(p => Scope.scope.allocateName(p.sym))
617620
val paramsStr = Scope.scope.allocateName(functionParamVarargSymbol)
618621
val functionName = JSBuilder.makeStringLiteral(name.fold("")(n => s"${JSBuilder.escapeStringCharacters(n)}"))
619-
val checkArgsNum = doc"\nglobalThis.Predef.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);"
622+
val checkArgsNum = doc"\n$runtimeVar.checkArgs($functionName, ${params.paramCountLB}, ${params.paramCountUB.toString}, $paramsStr.length);"
620623
val paramsAssign = paramsList.zipWithIndex.map{(nme, i) =>
621624
doc"\nlet ${nme} = ${paramsStr}[$i];"}.mkDocument("")
622625
val restAssign = paramRest match
623626
case N => doc""
624-
case S(p) => doc"\nlet $p = runtime.Tuple.slice($paramsStr, ${params.paramCountLB}, 0);"
627+
case S(p) => doc"\nlet $p = $runtimeVar.Tuple.slice($paramsStr, ${params.paramCountLB}, 0);"
625628
(doc"...$paramsStr", doc"$checkArgsNum$paramsAssign$restAssign${this.body(body, endSemi = false)}")
626629
else
627630
super.setupFunction(name, params, body)

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,8 @@ extends Importer:
270270
case S(cls: ClassSymbol) =>
271271
trm
272272
case S(mem: BlockMemberSymbol) =>
273-
if !mem.hasLiftedClass then trm
273+
// FIXME: `defn` is not available before elaboration. See pull/277#discussion_r2051448677
274+
if !mem.hasLiftedClass || mem.defn.exists(_.isDeclare.isDefined) then trm
274275
else Term.SynthSel(trm, Ident("class"))(mem.clsTree.orElse(mem.modOrObjTree).map(_.symbol))
275276
case _ => trm
276277

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

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -485,9 +485,14 @@ class Resolver(tl: TraceLogger)
485485
case tup: Term.Tup => (
486486
!tup.fields.exists(_.isInstanceOf[Spd]),
487487
tup.fields.map:
488+
case Fld(asc = S(_)) => 0
488489
case _: Fld => 1
489490
case _: Spd => 0
490-
.sum,
491+
.sum +
492+
tup.fields.exists:
493+
case Fld(asc = S(_)) => true
494+
case _ => false
495+
.into(if _ then 1 else 0)
491496
)
492497
// Other: spread arguments
493498
case _ => (false, 0)
@@ -512,33 +517,41 @@ class Resolver(tl: TraceLogger)
512517
* try to pair as many as possible.
513518
*/
514519
@tailrec
515-
def zip(ps: Ls[Param], as: Ls[Elem], beforeSpread: Bool): Unit = (ps, as) match
520+
def zip(ps: Ls[Param], as: Ls[Elem], recordArgs: Ls[Fld], beforeSpread: Bool): Ls[Fld] = (ps, as) match
516521
// The spread argument takes all the remaining arguments.
517522
case (ps, (a: Spd) :: as) =>
518523
traverse(a.term, expect = NonModule(N))
519-
zip(ps, as, false)
524+
zip(ps, as, recordArgs, false)
520525
case (ps, a :: as) if !beforeSpread =>
521526
a.subTerms.foreach(traverse(_, expect = NonModule(N)))
522-
zip(ps, as, false)
527+
zip(ps, as, recordArgs, false)
523528

524529
// Pair the parameter and the argument.
525-
case (p :: ps, (a: Fld) :: as) =>
530+
case (p :: ps, (a @ Fld(asc = N)) :: as) =>
526531
traverse(a.term,
527532
// note: we accept regular arguments for module parameters
528533
expect = if p.modulefulness.isModuleful
529534
then Any
530535
else NonModule(S(msg"Module argument passed to a non-module parameter."))
531536
)
532-
zip(ps, as, true)
537+
zip(ps, as, recordArgs, true)
533538

534-
// If there are more parameters, there must be a spread argument before.
535-
case (p :: ps, Nil) => ()
539+
// Record Arguments. They are pushed to the last parameter.
540+
case (_, (a @ Fld(asc = S(_))) :: as) =>
541+
zip(ps, as, a :: recordArgs, true)
542+
543+
// If there are more parameters, there must be a spread
544+
// argument, or some record arguments before.
545+
case (p :: ps, Nil) =>
546+
recordArgs.reverse
547+
536548
// If there are more arguments, all of them go to `restParam`.
537549
case (Nil, a :: as) =>
538550
a.subTerms.foreach(traverse(_, expect = NonModule(N)))
539-
zip(Nil, as, beforeSpread)
551+
zip(Nil, as, recordArgs, beforeSpread)
540552

541-
case (Nil, Nil) => ()
553+
case (Nil, Nil) =>
554+
recordArgs.reverse
542555
end zip
543556

544557
val args = as match
@@ -547,7 +560,9 @@ class Resolver(tl: TraceLogger)
547560

548561
// The lhs of the App is already traversed by the recursive
549562
// `traverse` or `resolve` at the beginning.
550-
zip(ps.params, args, true)
563+
val recordArgs = zip(ps.params, args, Nil, true)
564+
recordArgs.foreach:
565+
_.subTerms.foreach(traverse(_, expect = NonModule(N)))
551566
S(defn.copy(params = pss))
552567
case _ =>
553568
traverse(as, expect = NonModule(N))
@@ -623,7 +638,6 @@ class Resolver(tl: TraceLogger)
623638
case t: Term.App => sym.map(sym => t.sym = S(sym))
624639
case t: Term.TyApp => sym.map(sym => t.sym = S(sym))
625640
case t: Term.Ref => sym.map(sym => t.resSym = S(sym))
626-
case _ =>
627641
log(s"Resolved symbol for ${t}: ${lhsDefn.sym}")
628642
case _ =>
629643
case _ =>

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package hkmc2
22
package semantics
33
package ucs
44

5-
import syntax.{Keyword, Tree}, Tree.*
5+
import syntax.{Keyword, Tree, BracketKind}, Tree.*
66
import mlscript.utils.*, shorthands.*
77
import Message.MessageContext
88
import utils.TraceLogger
@@ -523,7 +523,7 @@ class Desugarer(val elaborator: Elaborator)
523523
// Raise an error and discard `sequel`. Use `fallback` instead.
524524
raise(ErrorReport(msg"Cannot use this ${ctor.describe} as an extractor" -> ctor.toLoc :: Nil))
525525
fallback
526-
pattern.deparenthesized match
526+
pattern.deparenthesized.desugared match
527527
// A single wildcard pattern.
528528
case Under() => _ => ctx => sequel(ctx)
529529
// Alias pattern
@@ -610,6 +610,16 @@ class Desugarer(val elaborator: Elaborator)
610610
case Jux(Ident(".."), Ident(_)) => fallback => _ =>
611611
raise(ErrorReport(msg"Illegal rest pattern." -> pattern.toLoc :: Nil))
612612
fallback
613+
case InfixApp(id: Ident, Keyword.`:`, pat) => fallback => ctx =>
614+
val sym = VarSymbol(id)
615+
val ctxWithAlias = ctx + (id.name -> sym)
616+
Split.Let(sym, ref.sel(id, N),
617+
expandMatch(sym, pat, sequel)(fallback)(ctxWithAlias))
618+
case Block(st :: Nil) => fallback => ctx =>
619+
expandMatch(scrutSymbol, st, sequel)(fallback)(ctx)
620+
// case Block(sts) => fallback => ctx => // TODO
621+
case Bra(BracketKind.Curly | BracketKind.Round, inner) => fallback => ctx =>
622+
expandMatch(scrutSymbol, inner, sequel)(fallback)(ctx)
613623
case pattern => fallback => _ =>
614624
// Raise an error and discard `sequel`. Use `fallback` instead.
615625
raise(ErrorReport(msg"Unrecognized pattern (${pattern.describe})" -> pattern.toLoc :: Nil))

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ abstract class Parser(
281281
case (IDENT("@", _), l0) :: rest if rest.nonEmpty =>
282282
consume
283283
blockOf(rule, simpleExpr(AppPrec) :: annotations, allowNewlines)
284-
case (tok @ (id: IDENT), loc) :: _ =>
284+
case (tok @ (id: IDENT), loc) :: _ if id.name =/= ":" =>
285285
Keyword.all.get(id.name) match
286286
case S(kw) =>
287287
consume
@@ -501,6 +501,14 @@ abstract class Parser(
501501
def simpleExpr(prec: Int)(using Line): Tree = wrap(prec)(simpleExprImpl(prec))
502502
def simpleExprImpl(prec: Int): Tree =
503503
yeetSpaces match
504+
case (IDENT("=", _), l0) :: (IDENT(nme, false), l1) :: rest =>
505+
consume
506+
consume
507+
Pun(true, new Ident(nme).withLoc(S(l1)))
508+
case (IDENT(":", _), l0) :: (IDENT(nme, false), l1) :: rest =>
509+
consume
510+
consume
511+
Pun(false, new Ident(nme).withLoc(S(l1)))
504512
case (IDENT("@", _), l0) :: rest if rest.nonEmpty =>
505513
consume
506514
val annotation = simpleExpr(AppPrec)

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ enum Tree extends AutoLocated:
4545
case Under()
4646
case Unt()
4747
case Ident(name: Str)
48+
case Pun(eql: Bool, id: Ident) // `=ident` (eql) or `:ident` (!eql)
4849
case Keywrd(kw: Keyword)
4950
case IntLit(value: BigInt) extends Tree with Literal
5051
case DecLit(value: BigDecimal) extends Tree with Literal
@@ -88,6 +89,7 @@ enum Tree extends AutoLocated:
8889

8990
def children: Ls[Tree] = this match
9091
case _: Empty | _: Error | _: Ident | _: Literal | _: Under | _: Unt => Nil
92+
case Pun(_, e) => e :: Nil
9193
case Bra(_, e) => e :: Nil
9294
case Block(stmts) => stmts
9395
case OpBlock(items) => items.flatMap:
@@ -155,7 +157,7 @@ enum Tree extends AutoLocated:
155157
case SynthSel(prefix, name) => "synthetic selection"
156158
case DynAccess(prefix, name, true) => "dynamic index access"
157159
case DynAccess(prefix, name, false) => "dynamic field access"
158-
case InfixApp(lhs, kw, rhs) => "infix operation"
160+
case InfixApp(lhs, kw, rhs) => "infix operator"
159161
case New(body, _) => "new"
160162
case IfLike(Keyword.`if`, _, split) => "if expression"
161163
case IfLike(Keyword.`while`, _, split) => "while expression"
@@ -171,6 +173,7 @@ enum Tree extends AutoLocated:
171173
case Open(_) => "open"
172174
case MemberProj(_, _) => "member projection"
173175
case Keywrd(kw) => s"'${kw.name}' keyword"
176+
case Unt() => "unit"
174177
case Dummy => "‹dummy›"
175178

176179
def deparenthesized: Tree = this match
@@ -181,6 +184,9 @@ enum Tree extends AutoLocated:
181184

182185
lazy val desugared: Tree = this match
183186

187+
case Pun(false, id) =>
188+
InfixApp(id, Keyword.`:`, id)
189+
184190
// TODO generalize to pattern-let and rm this special case
185191
case LetLike(kw, und @ Under(), r, b) =>
186192
LetLike(kw, Ident("_").withLocOf(und), r, b)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.mjs
2+
!/Predef.mjs
3+
!/Runtime.mjs
4+
!/RuntimeJS.mjs
5+
!/apps/parsing/vendors/railroad/railroad.mjs
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module Char with
2+
pattern Lowercase = "a" ..= "z"
3+
pattern Uppercase = "A" ..= "Z"
4+
pattern Letter = Lowercase | Uppercase
5+
pattern Digit = "0" ..= "9"
6+
pattern HexDigit = Digit | "a" ..= "f" | "A" ..= "F"
7+
pattern OctDigit = "0" ..= "7"
8+
pattern BinDigit = "0" | "1"
9+
pattern Whitespace = " " | "\t" | "\n" | "\r"

0 commit comments

Comments
 (0)