Skip to content

Commit

Permalink
Add support for member projection syntax Cls::mem
Browse files Browse the repository at this point in the history
  • Loading branch information
LPTK committed Jan 11, 2025
1 parent 9efe83a commit 4aedfaa
Show file tree
Hide file tree
Showing 48 changed files with 350 additions and 79 deletions.
1 change: 0 additions & 1 deletion hkmc2/jvm/src/test/scala/hkmc2/JSBackendDiffMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ abstract class JSBackendDiffMaker extends MLsDiffMaker:
val showSanitizedJS = NullaryCommand("ssjs")
val showJS = NullaryCommand("sjs")
val showRepl = NullaryCommand("showRepl")
val silent = NullaryCommand("silent")
val noSanityCheck = NullaryCommand("noSanityCheck")
val traceJS = NullaryCommand("traceJS")
val handler = NullaryCommand("handler")
Expand Down
3 changes: 2 additions & 1 deletion hkmc2/jvm/src/test/scala/hkmc2/MLsDiffMaker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ abstract class MLsDiffMaker extends DiffMaker:
):
def post: Product => Str = get.getOrElse(Function.const(""))

val silent = NullaryCommand("silent")
val dbgElab = NullaryCommand("de")
val dbgParsing = NullaryCommand("dp")

Expand All @@ -46,7 +47,7 @@ abstract class MLsDiffMaker extends DiffMaker:
val typeCheck = FlagCommand(false, "typeCheck")

val importCmd = Command("import"): ln =>
importFile(file / os.up / os.RelPath(ln.trim), verbose = true)
importFile(file / os.up / os.RelPath(ln.trim), verbose = silent.isUnset)

val showUCS = Command("ucs"): ln =>
ln.split(" ").iterator.map(x => "ucs:" + x.trim).toSet
Expand Down
58 changes: 30 additions & 28 deletions hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,34 +73,36 @@ class Lowering(using TL, Raise, Elaborator.State):
case sym: sem.BlockMemberSymbol =>
sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun)
case _ => false
arg match
case Tup(fs) =>
val as = fs.map:
case sem.Fld(sem.FldFlags.empty, value, N) => false -> value
case sem.Fld(sem.FldFlags(false, false, false, true), value, N) => false -> value
case sem.Fld(flags, value, asc) =>
TODO("Other argument forms")
case spd: Spd => true -> spd.term
val l = new TempSymbol(S(t))
def conclude(fr: Path) =
def rec(as: Ls[Bool -> st], asr: Ls[Arg]): Block = as match
case Nil => k(Call(fr, asr.reverse)(isMlsFun))
case (spd, a) :: as =>
subTerm(a): ar =>
rec(as, Arg(spd, ar) :: asr)
rec(as, Nil)
f match
// * Due to whacky JS semantics, we need to make sure that selections leading to a call
// * are preserved in the call and not moved to a temporary variable.
case sel @ Sel(prefix, nme) =>
subTerm(prefix): p =>
conclude(Select(p, nme)(sel.sym))
case sel @ SelProj(prefix, _, nme) =>
subTerm(prefix): p =>
conclude(Select(p, nme)(sel.sym))
case _ => subTerm(f)(conclude)
case _ =>
TODO("Other argument list forms")
def conclude(fr: Path) =
arg match
case Tup(fs) =>
val as = fs.map:
case sem.Fld(sem.FldFlags.empty, value, N) => false -> value
case sem.Fld(sem.FldFlags(false, false, false, true), value, N) => false -> value
case sem.Fld(flags, value, asc) =>
TODO("Other argument forms")
case spd: Spd => true -> spd.term
val l = new TempSymbol(S(t))
def rec(as: Ls[Bool -> st], asr: Ls[Arg]): Block = as match
case Nil => k(Call(fr, asr.reverse)(isMlsFun))
case (spd, a) :: as =>
subTerm(a): ar =>
rec(as, Arg(spd, ar) :: asr)
rec(as, Nil)
case _ =>
// Application arguments that are not tuples represent spreads, as in `f(...arg)`
subTerm(arg): ar =>
k(Call(fr, Arg(spread = true, ar) :: Nil)(isMlsFun))
f match
// * Due to whacky JS semantics, we need to make sure that selections leading to a call
// * are preserved in the call and not moved to a temporary variable.
case sel @ Sel(prefix, nme) =>
subTerm(prefix): p =>
conclude(Select(p, nme)(sel.sym))
case sel @ SelProj(prefix, _, nme) =>
subTerm(prefix): p =>
conclude(Select(p, nme)(sel.sym))
case _ => subTerm(f)(conclude)
case st.Blk(Nil, res) => term(res)(k)
case st.Blk(Lit(Tree.UnitLit(true)) :: stats, res) =>
subTerm(st.Blk(stats, res))(k)
Expand Down
40 changes: 36 additions & 4 deletions hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@ object Elaborator:
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›"))
Term.App(base.ref(id), Term.Tup(Nil)(emptyTup))(
Tree.App(id, emptyTup) // FIXME
, 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 @@ -319,7 +321,7 @@ extends Importer:
case _ =>
raise(ErrorReport(msg"Identifier `${idn.name}` does not name a known class symbol." -> idn.toLoc :: Nil))
N
Term.SelProj(term(pre), c, idp)(N)
Term.SelProj(term(pre), c, idp)(f)
case App(Ident("#"), Tree.Tup(Sel(pre, Ident(name)) :: App(Ident(proj), args) :: Nil)) =>
term(App(App(Ident("#"), Tree.Tup(Sel(pre, Ident(name)) :: Ident(proj) :: Nil)), args))
case App(Ident("!"), Tree.Tup(rhs :: Nil)) =>
Expand Down Expand Up @@ -377,6 +379,36 @@ extends Importer:
val preTrm = term(pre)
val sym = resolveField(nme, preTrm.symbol, nme)
Term.Sel(preTrm, nme)(sym)
case MemberProj(ct, nme) =>
val c = cls(ct, inAppPrefix = false)
val f = c.symbol.flatMap(_.asCls) match
case S(cls: ClassSymbol) =>
cls.tree.allSymbols.get(nme.name) match
case S(fld: FieldSymbol) => S(fld)
case _ =>
raise(ErrorReport(msg"Class '${cls.nme}' does not contain member '${nme.name}'." -> nme.toLoc :: Nil))
N
case _ =>
raise:
ErrorReport:
msg"${ct.describe.capitalize} is not a known class." -> ct.toLoc ::
msg"Note: any expression of the form `‹expression›::‹identifier›` is a member projection;" -> N ::
msg" add a space before ‹identifier› to make it an operator application." -> N ::
Nil
N
val self = VarSymbol(Ident("self"))
val args = VarSymbol(Ident("args"))
val ps = ParamList(ParamListFlags.empty,
Param(FldFlags.empty, self, N) :: Nil,
S:
Param(FldFlags.empty, args, N)
)
val rs = FlowSymbol("‹app-res›")
Term.Lam(ps,
Term.App(Term.SelProj(self.ref(), c, nme)(f), args.ref())(
Tree.App(nme, Tree.Tup(Nil)) // FIXME
, rs)
)
case tree @ Tup(fields) =>
Term.Tup(fields.map(fld(_)))(tree)
case New(body) => // TODO handle Under
Expand Down Expand Up @@ -631,13 +663,13 @@ extends Importer:
reportUnusedAnnotations
val sym = fieldOrVarSym(HandlerBind, id)
log(s"Processing `handle` statement $id (${sym}) ${ctx.outer}")

val elabed = block(sts_)._1

elabed.res match
case Term.Lit(UnitLit(true)) =>
case trm => raise(WarningReport(msg"Terms in handler block do nothing" -> trm.toLoc :: Nil))

val tds = elabed.stats.map {
case td @ TermDefinition(owner, Fun, sym, params, sign, body, resSym, flags, annotations) =>
params.reverse match
Expand Down
6 changes: 6 additions & 0 deletions hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ abstract class Parser(
case (br @ BRACKETS(Indent | Curly, toks @ ((IDENT(opStr, true), _) :: _)), loc) :: _ if opPrec(opStr)._1 > prec =>
consume
App(acc, rec(toks, S(loc), "operator block").concludeWith(_.opBlock))

case (OP("::"), l0) :: (IDENT(id, false), l1) :: _ =>
consume
consume
exprCont(MemberProj(acc, new Ident(id).withLoc(S(l1))).withLoc(S(l0 ++ l1)), prec, allowNewlines)
case (OP(opStr), l0) :: _ if /* isInfix(opStr) && */ opPrec(opStr)._1 > prec =>
consume
val v = Ident(opStr).withLoc(S(l0))
Expand Down Expand Up @@ -780,6 +785,7 @@ abstract class Parser(
}
case _ => App(v, PlainTup(acc, rhs))
}, prec, allowNewlines)

/*
case (KEYWORD(":"), l0) :: _ if prec <= NewParser.prec(':') =>
consume
Expand Down
1 change: 1 addition & 0 deletions hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ enum Tree extends AutoLocated:
case Jux(lhs: Tree, rhs: Tree)
case SynthSel(prefix: Tree, name: Ident)
case Sel(prefix: Tree, name: Ident)
case MemberProj(cls: Tree, name: Ident)
case InfixApp(lhs: Tree, kw: Keyword.Infix, rhs: Tree)
case New(body: Tree)
case IfLike(kw: Keyword.`if`.type | Keyword.`while`.type, kwLoc: Opt[Loc], split: Tree)
Expand Down
30 changes: 30 additions & 0 deletions hkmc2/shared/src/test/mlscript/backlog/OfStatements.mls
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
:js
:todo


print of
let x = 1
x + 1
//│ ╔══[ERROR] Expected a body for let bindings in expression position
//│ ║ l.6: let x = 1
//│ ╙── ^^^^^
//│ ╔══[ERROR] Name not found: x
//│ ║ l.7: x + 1
//│ ╙── ^
//│ = 1

print of
open Predef
let x = 1
x + 1
//│ ╔══[ERROR] Illegal position for 'open' statement.
//│ ║ l.17: open Predef
//│ ╙── ^^^^^^
//│ ╔══[ERROR] Expected a body for let bindings in expression position
//│ ║ l.18: let x = 1
//│ ╙── ^^^^^
//│ ╔══[ERROR] Name not found: x
//│ ║ l.19: x + 1
//│ ╙── ^


64 changes: 64 additions & 0 deletions hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
:js


:sjs
:e
1::x
//│ ╔══[ERROR] Integer literal is not a known class.
//│ ║ l.6: 1::x
//│ ║ ^
//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection;
//│ ╙── add a space before ‹identifier› to make it an operator application.
//│ JS (unsanitized):
//│ (self, ...args) => { return self.x(...args) ?? null; }
//│ = [Function (anonymous)]

:ssjs
:e
:re
1::x()
//│ ╔══[ERROR] Integer literal is not a known class.
//│ ║ l.19: 1::x()
//│ ║ ^
//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection;
//│ ╙── add a space before ‹identifier› to make it an operator application.
//│ JS:
//│ ((...args1) => {
//│ globalThis.Predef.checkArgs("", 1, false, args1.length);
//│ let self = args1[0];
//│ let args = globalThis.Predef.tupleSlice(args1, 1, 0);
//│ return self.x(...args) ?? null;
//│ })()
//│ ═══[RUNTIME ERROR] Error: Function expected 1 arguments but got 0


fun (::) f(a, b) = [a, b]

let x = 1
//│ x = 1

"A" :: x
//│ = [ 'A', 1 ]

"A":: x
//│ = [ 'A', 1 ]

:e
"A"::x
//│ ╔══[ERROR] String literal is not a known class.
//│ ║ l.47: "A"::x
//│ ║ ^^^
//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection;
//│ ╙── add a space before ‹identifier› to make it an operator application.
//│ = [Function (anonymous)]

:e
"A" ::x
//│ ╔══[ERROR] String literal is not a known class.
//│ ║ l.56: "A" ::x
//│ ║ ^^^
//│ ╟── Note: any expression of the form `‹expression›::‹identifier›` is a member projection;
//│ ╙── add a space before ‹identifier› to make it an operator application.
//│ = [Function (anonymous)]


Loading

0 comments on commit 4aedfaa

Please sign in to comment.