Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion hkmc2/shared/src/main/scala/hkmc2/MLsCompiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class MLsCompiler
codegen.js.JSBuilder()
val le = low.program(blk)
val baseScp: utils.Scope =
utils.Scope.empty
utils.Scope.empty(utils.Scope.Cfg.default)
// * This line serves for `import.meta.url`, which retrieves directory and file names of mjs files.
// * Having `module id"import" with ...` in `prelude.mls` will generate `globalThis.import` that is undefined.
baseScp.addToBindings(Elaborator.State.importSymbol, "import", shadow = false)
Expand Down
4 changes: 2 additions & 2 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import hkmc2.Message.MessageContext
import hkmc2.{semantics => sem}
import hkmc2.semantics.{Term => st}

import syntax.{Literal, Tree}
import syntax.{Literal, Tree, SpreadKind}
import semantics.*
import semantics.Term.*
import sem.Elaborator.State
Expand Down Expand Up @@ -753,7 +753,7 @@ object Value:
// * If the ref is a symbol that does not refer to a definition, then there is no disambiguation.
def apply(l: TempSymbol | VarSymbol | BuiltinSymbol): Ref = Ref(l, N)

case class Arg(spread: Opt[Bool], value: Path)
case class Arg(spread: Opt[SpreadKind], value: Path)

// * `IndxdArg(S(idx), value)` represents a key-value pair in a record `(idx): value`
// * `IndxdArg(N, value)` represents a spread element in a record `...value`
Expand Down
21 changes: 10 additions & 11 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import semantics.{Term => st}
import semantics.Term.{Throw => _, *}
import semantics.Elaborator.{State, Ctx, ctx}

import syntax.{Literal, Tree}
import syntax.{Literal, Tree, SpreadKind}
import hkmc2.syntax.Fun


Expand Down Expand Up @@ -370,7 +370,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
arg match
case Tup(fs) =>
if fs.exists(e => e match
case Spd(false, _) => true // is lazy spread
case Spd(SpreadKind.Lazy, _) => true // is lazy spread
case _ => false)
then
raise(ErrorReport(
Expand All @@ -380,7 +380,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
case _ =>
// Application arguments that are not tuples represent spreads, as in `f(...arg)`
subTerm_nonTail(arg): ar =>
k(Arg(spread = S(true), ar) :: Nil)
k(Arg(spread = S(SpreadKind.Eager), ar) :: Nil)

def ref(ref: st.Ref, annots: List[Annot], disamb: Opt[DefinitionSymbol[?]], inStmtPos: Bool)(k: Result => Block)(using LoweringCtx): Block =
def warnStmt = if inStmtPos then
Expand Down Expand Up @@ -438,10 +438,11 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
return k(Lambda(paramLists.head, bodyBlock).withLocOf(ref))
case bs: BlockMemberSymbol =>
disamb.flatMap(_.defn) match
case S(_) if bs.asCls.exists(_ is ctx.builtins.Int31) =>
return term(Sel(State.runtimeSymbol.ref().resolve, ref.tree)(S(bs), N).withLocOf(ref).resolve)(k)
case S(d) if d.hasDeclareModifier.isDefined =>
return term(Sel(State.globalThisSymbol.ref().resolve, ref.tree)(S(bs), N).withLocOf(ref).resolve)(k)
return term(Sel(State.globalThisSymbol.ref().resolve, ref.tree)(S(bs), N, N).withLocOf(ref).resolve)(k)
// * Note: the alternative below, which might seem more appealing,
// * works but does not instrument the selection to check for `undefined`!
// return k(Value.Ref(State.globalThisSymbol).sel(ref.tree, bs).withLocOf(ref))
case S(td: TermDefinition) if td.k is syntax.Fun =>
// * Local functions with no parameter lists are getters
// * and are lowered to functions with an empty parameter list
Expand Down Expand Up @@ -578,8 +579,6 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
} =>
val sym = t.resolvedSym.get.asInstanceOf[BlockMemberSymbol]
conclude(Value.Ref(State.wasmSymbol).selN(Tree.Ident(sym.nme)))
case t if t.resolvedSym.exists(_ is ctx.builtins.Int31) =>
conclude(Value.Ref(State.runtimeSymbol).selN(Tree.Ident("Int31")))
case t if instantiatedResolvedBms.exists(_ is ctx.builtins.debug.printStack) =>
if !config.effectHandlers.exists(_.debug) then
return fail:
Expand Down Expand Up @@ -785,7 +784,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
msg"Cannot compile ${t.describe} term that was not elaborated (maybe elaboration was one in 'lightweight' mode?)" ->
t.toLoc :: Nil,
source = Diagnostic.Source.Compilation)
case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted
case _: CompType | _: Neg | _: Term.FunTy | _: Term.Forall | _: Term.WildcardTy | _: Term.Unquoted | _: LeadingDotSel
=> fail:
ErrorReport(
msg"Unexpected term form in expression position (${t.describe})" ->
Expand Down Expand Up @@ -963,7 +962,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
case sem.Fld(sem.FldFlags.benign(), value, N) => R(N -> value)
case sem.Fld(sem.FldFlags.benign(), idx, S(rhs)) => L(idx -> rhs)
case arg @ sem.Fld(flags, value, asc) => TODO(s"Other argument forms: $arg")
case spd: Spd => R(S(spd.eager) -> spd.term)
case spd: Spd => R(S(spd.k) -> spd.term)
// * The straightforward way to lower arguments creates too much recursion depth
// * and makes Lowering stack overflow when lowering functions with lots of arguments.
/*
Expand All @@ -976,7 +975,7 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
*/
var asr: Ls[Arg] = Nil
var fsr: Ls[RcdArg] = Nil
def rec(as: Ls[(Term -> Term) \/ (Opt[Bool] -> st)]): Block = as match
def rec(as: Ls[(Term -> Term) \/ (Opt[SpreadKind] -> st)]): Block = as match
case Nil => End()
case R((spd, a)) :: as =>
subTerm_nonTail(a): ar =>
Expand Down
6 changes: 3 additions & 3 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/TailRecOpt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import hkmc2.codegen.*
import hkmc2.semantics.*
import hkmc2.Message.*
import hkmc2.semantics.Elaborator.State
import hkmc2.syntax.Tree
import hkmc2.syntax.{Tree, SpreadKind}
import scala.collection.mutable.ArrayBuffer
import java.lang.instrument.ClassDefinition

Expand Down Expand Up @@ -145,7 +145,7 @@ class TailRecOpt(using State, TL, Raise):

var bad = false
val hd = for a <- headArgs yield a.spread match
case Some(true) =>
case Some(SpreadKind.Eager) =>
if c.explicitTailCall then
raise(ErrorReport(msg"Spreads are not yet fully supported in calls marked @tailcall." -> a.value.toLoc :: Nil))
bad = true
Expand All @@ -156,7 +156,7 @@ class TailRecOpt(using State, TL, Raise):
if head.restParam.isDefined then
val rest =
restArgs match
case Arg(S(true), value) :: Nil => value
case Arg(S(SpreadKind.Eager), value) :: Nil => value
case _ => Tuple(true, restArgs)
hd.appended(rest)
else
Expand Down
8 changes: 4 additions & 4 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import document.*
import document.Document.{braced, bracketed}

import hkmc2.Message.MessageContext
import hkmc2.syntax.{Tree, MutVal, ImmutVal}
import hkmc2.syntax.{Tree, MutVal, ImmutVal, SpreadKind}
import hkmc2.semantics.*
import Elaborator.{State, Ctx}
import hkmc2.codegen.Lambda
Expand Down Expand Up @@ -87,8 +87,8 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:

def argument(a: Arg)(using Raise, Scope): Document =
val spd = a.spread match
case S(true) => doc"..."
case S(false) => doc"$runtimeVar.Tuple.split, "
case S(SpreadKind.Eager) => doc"..."
case S(SpreadKind.Lazy) => doc"$runtimeVar.Tuple.split, "
case N => doc""
doc"${spd}${result(a.value)}"

Expand Down Expand Up @@ -173,7 +173,7 @@ class JSBuilder(using TL, State, Ctx) extends CodeBuilder:
case Tuple(mut, es) if es.isEmpty => if mut then "[]" else doc"$freeze([])"
case Tuple(mut, es) =>
val inner =
val lazyConcat = es.exists(!_.spread.getOrElse(true))
val lazyConcat = es.exists(!_.spread.fold(true)(_.isEager))
if lazyConcat
then doc"$runtimeVar.Tuple.lazyConcat(${es.map(argument).mkDocument(doc", ")})"
else bracketed("[", "]", insertBreak = true):
Expand Down
60 changes: 46 additions & 14 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import Keyword.{`let`, `set`}
object Elaborator:

val binaryOps = Set(
",",
",", // * Not currently used directly; but `;` (below) maps to it
"+", "-", "*", "/", "%",
"==", "!=", "<", "<=", ">", ">=",
"===", "!==",
Expand Down Expand Up @@ -109,6 +109,36 @@ object Elaborator:
case _: OuterCtx.LocalScope =>
parent.fold(ReturnHandler.NotInFunction)(_.getRetHandler)

/** Computes the outermost context from which the current context can still be accessed.
* For instance, from [ctx2] here, [ctx1] is the outermost accessible base:
* [ctx0]
* fun foo =
* [ctx1]
* module Foo with
* module Bar with
* [ctx2]
* and the path is Foo :: Bar :: Nil.
* [ctx0] cannot access [ctx2] because there is a function (Function outer) on the way.
* The same would happen for:
* [ctx0]
* if ... then // LocalScope also blocks access to [ctx1] and [ctx2]
* [ctx1]
* module Foo with
* module Bar with
* [ctx2]
*/
lazy val outermostAcessibleBase: (Ctx, Ls[InnerSymbol]) =
import OuterCtx.*
outer match
case InnerScope(inner) =>
parent match
case N => (this, inner :: Nil)
case S(par) =>
val (base, path) = par.outermostAcessibleBase
(base, inner :: path)
case _: (Function | LocalScope) | LambdaOrHandlerBlock | NonReturnContext =>
(this, Nil)

// * Invariant: We expect that the top-level context only contain hard-coded symbols like `globalThis`
// * and that built-in symbols like Int and Str be imported into another nested context on top of it.
// * It should not be possible to shadow these built-in symbols, so user code should always be compiled
Expand Down Expand Up @@ -261,7 +291,7 @@ object Elaborator:
val bsym = BlockMemberSymbol("ret", Nil, true)
val defn = ClassDef(N, syntax.Cls, sym, bsym, Nil, Nil, N, ObjBody(Blk(Nil, Term.Lit(UnitLit(false)))), Nil, N)
sym.defn = S(defn)
Term.Sel(runtimeSymbol.ref(), id)(S(sym), N)
Term.Sel(runtimeSymbol.ref(), id)(S(sym), N, N)
val nonLocalRet =
val id = new Ident("ret")
BlockMemberSymbol(id.name, Nil, true)
Expand Down Expand Up @@ -436,10 +466,10 @@ extends Importer with ucs.SplitElaborator:
case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil))

val tds = elabed.stats.map {
case td @ TermDefinition(Fun, sym, tsym, params, tparams, sign, body, resSym, flags, mf, annotations, comp) =>
case td @ TermDefinition(Fun, sym, tsym, params, tparams, sign, body, flags, mf, annotations, comp) =>
params.reverse match
case ParamList(_, value :: Nil, _) :: newParams =>
val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, resSym, flags, mf, annotations, comp)
val newTd = TermDefinition(Fun, sym, tsym, newParams.reverse, tparams, sign, body, flags, mf, annotations, comp)
S(HandlerTermDefinition(value.sym, newTd))
case _ =>
raise(ErrorReport(msg"Handler function is missing resumption parameter" -> td.toLoc :: Nil))
Expand Down Expand Up @@ -579,6 +609,8 @@ extends Importer with ucs.SplitElaborator:
val preTrm = subterm(pre)
val sym = resolveField(nme, preTrm.symbol, nme)
Term.SynthSel(preTrm, nme)(sym, N)
case Sel(Empty(), nme) =>
Term.LeadingDotSel(nme)(S(summon)).withLocOf(tree)
case Sel(pre, nme) =>
val preTrm = subterm(pre)
val sym = resolveField(nme, preTrm.symbol, nme)
Expand All @@ -605,7 +637,7 @@ extends Importer with ucs.SplitElaborator:
val loc = tree.toLoc.getOrElse(???)
Term.Lit(StrLit(loc.origin.fileName.toString))
else
Term.Sel(preTrm, nme)(sym, N)
Term.Sel(preTrm, nme)(sym, N, S(summon))
case MemberProj(ct, nme) =>
val c = subterm(ct)
val f = c.symbol.flatMap(_.asCls) match
Expand Down Expand Up @@ -699,7 +731,7 @@ extends Importer with ucs.SplitElaborator:
val argTree = new Tup(body :: Nil)
val dummyIdent = new Ident("return").withLocOf(kw)
Term.App(
Term.Sel(sym.ref(dummyIdent), retMtdTree)(S(state.nonLocalRet), N),
Term.Sel(sym.ref(dummyIdent), retMtdTree)(S(state.nonLocalRet), N, S(summon)),
Term.Tup(PlainFld(subterm(body)) :: Nil)(argTree)
)(App(Sel(dummyIdent, retMtdTree), argTree), N, rs)
case ReturnHandler.NotInFunction =>
Expand Down Expand Up @@ -828,9 +860,9 @@ extends Importer with ucs.SplitElaborator:
case InfixApp(lhs, Keywrd(Keyword.`:`), rhs) =>
Fld(FldFlags.empty, term(lhs), S(arg(rhs)))
case Spread(Keywrd(Keyword.`..`), S(trm)) =>
Spd(false, arg(trm))
Spd(SpreadKind.Lazy, arg(trm))
case Spread(Keywrd(Keyword.`...`), S(trm)) =>
Spd(true, arg(trm))
Spd(SpreadKind.Eager, arg(trm))
case _ =>
val t = arg(tree)
var flags = FldFlags.empty
Expand Down Expand Up @@ -1137,7 +1169,7 @@ extends Importer with ucs.SplitElaborator:
val tsym = TermSymbol(Fun, N, Ident("ret"))
val td = TermDefinition(
Fun, mtdSym, tsym, PlainParamList(Param(FldFlags.empty, valueSym, N, Modulefulness.none) :: Nil) :: Nil,
N, N, S(valueSym.ref(Ident("value"))), FlowSymbol(s"‹result of non-local return›"), TermDefFlags.empty, Modulefulness.none, Nil, N)
N, N, S(valueSym.ref(Ident("value"))), TermDefFlags.empty, Modulefulness.none, Nil, N)
tsym.defn = S(td)
mtdSym.tsym = S(tsym)
val htd = HandlerTermDefinition(resumeSym, td)
Expand All @@ -1154,7 +1186,7 @@ extends Importer with ucs.SplitElaborator:
Modulefulness.none

val tsym = TermSymbol(k, owner, id) // TODO?
val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body, r,
val tdf = TermDefinition(k, sym, tsym, pss, tps, s, body,
TermDefFlags.empty.copy(isMethod = isMethod), mfn, annotations, N).withLocOf(td)
tsym.defn = S(tdf)
sym.tsym = S(tsym)
Expand Down Expand Up @@ -1189,6 +1221,8 @@ extends Importer with ucs.SplitElaborator:

val sym = members.getOrElse(nme.name, lastWords(s"Symbol not found: ${nme.name}"))

val outerCtx = ctx

var newCtx = S(td.symbol).collectFirst:
case s: InnerSymbol => s
.fold(ctx.nest(OuterCtx.NonReturnContext))(ctx.nestInner(_))
Expand Down Expand Up @@ -1244,7 +1278,6 @@ extends Importer with ucs.SplitElaborator:
tsym,
Nil, N, N,
S(p.sym.ref()),
FlowSymbol("‹class-param-res›"),
TermDefFlags.empty.copy(isMethod = (k is Cls)),
p.modulefulness,
Nil,
Expand Down Expand Up @@ -1364,7 +1397,7 @@ extends Importer with ucs.SplitElaborator:
val md =
val (bod, c) = mkBody
ModuleOrObjectDef(owner, modSym, sym,
tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)
tps, pss.headOption, pss.tailOr(Nil), newOf(td), k, ObjBody(bod), comp, annotations)(outerCtx)
modSym.defn = S(md)
md
case Cls =>
Expand All @@ -1388,7 +1421,6 @@ extends Importer with ucs.SplitElaborator:
S(tps.map(tp => Param(FldFlags.empty, tp.sym, N, Modulefulness.none))),
S(clsSym.ref()),
N,
FlowSymbol(s"‹constructor›"),
TermDefFlags.empty,
Modulefulness.none,
annotations.collect:
Expand Down Expand Up @@ -1708,7 +1740,7 @@ extends Importer with ucs.SplitElaborator:
def computeVariances(s: Statement): Unit =
val trav = VarianceTraverser()
def go(s: Statement): Unit = s match
case TermDefinition(k, sym, tsym, pss, _, sign, body, r, _, _, _, _) =>
case TermDefinition(k, sym, tsym, pss, _, sign, body, r, _, _, _) =>
pss.foreach(ps => ps.params.foreach(trav.traverseType(S(false))))
sign.foreach(trav.traverseType(S(true)))
body match
Expand Down
Loading
Loading