Skip to content

Move UCS helper functions to the runtime module #299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
11 changes: 10 additions & 1 deletion hkmc2/shared/src/main/scala/hkmc2/codegen/Lowering.scala
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,16 @@ class Lowering()(using Config, TL, Raise, State, Ctx):
End("error")
case st.TyApp(f, ts) => term(f)(k) // * Type arguments are erased
case st.App(f, arg) =>
val isMlsFun = f.symbol.fold(f.isInstanceOf[st.Lam]):
val isMlsFun = f.symbol.fold(
f.isInstanceOf[st.Lam] || {
@tailrec def base(t: Term): Term = t match
case SynthSel(s, _) => base(s)
case _ => t
base(f) match
case st.Ref(symbol) => symbol == State.runtimeSymbol
case _ => false
}
):
case _: sem.BuiltinSymbol => true
case sym: sem.BlockMemberSymbol =>
sym.trmImplTree.fold(sym.clsTree.isDefined)(_.k is syntax.Fun)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ trait JSBuilderArgNumSanityChecks(using Config, Elaborator.State)
doc"\nlet ${nme} = ${paramsStr}[$i];"}.mkDocument("")
val restAssign = paramRest match
case N => doc""
case S(p) => doc"\nlet $p = globalThis.Predef.tupleSlice($paramsStr, ${params.paramCountLB}, 0);"
case S(p) => doc"\nlet $p = runtime.Tuple.slice($paramsStr, ${params.paramCountLB}, 0);"
(doc"...$paramsStr", doc"$checkArgsNum$paramsAssign$restAssign${this.body(body, endSemi = false)}")
else
super.setupFunction(name, params, body)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import syntax.Tree.*, Elaborator.{Ctxl, ctx}, Elaborator.State
trait DesugaringBase(using state: State):
val elaborator: Elaborator

import elaborator.tl.*, state.globalThisSymbol
import elaborator.tl.*

protected final def sel(p: Term, k: Ident): Term.SynthSel = Term.SynthSel(p, k)(N)
protected final def sel(p: Term, k: Ident, s: FieldSymbol): Term.SynthSel = Term.SynthSel(p, k)(S(s))
Expand All @@ -22,54 +22,50 @@ trait DesugaringBase(using state: State):
protected final def app(l: Term, r: Term, label: Str): Term.App = app(l, r, FlowSymbol(label))
protected final def app(l: Term, r: Term, s: FlowSymbol): Term.App = Term.App(l, r)(App(Empty(), Empty()), s)

/** Make a term looks like `globalThis.Predef.MatchResult` with its symbol. */
/** Make a term looks like `runtime.MatchResult` with its symbol. */
protected lazy val matchResultClass: Ctxl[(Term.Sel | Term.SynthSel, ClassSymbol)] =
(State.runtimeSymbol.ref().selNoSym("MatchResult", synth=true), State.matchResultClsSymbol)

/** Make a pattern looks like `globalThis.Predef.MatchResult.class`. */
/** Make a pattern looks like `runtime.MatchResult.class`. */
protected def matchResultPattern(parameters: Opt[List[BlockLocalSymbol]]): Ctxl[Pattern.ClassLike] =
val (classRef, classSym) = matchResultClass
val classSel = Term.SynthSel(classRef, Ident("class"))(S(classSym))
Pattern.ClassLike(classSym, classSel, parameters, false)(Empty())

/** Make a term looks like `globalThis.Predef.MatchFailure` with its symbol. */
/** Make a term looks like `runtime.MatchFailure` with its symbol. */
protected lazy val matchFailureClass: Ctxl[(Term.Sel | Term.SynthSel, ClassSymbol)] =
(State.runtimeSymbol.ref().selNoSym("MatchFailure", synth=true), State.matchFailureClsSymbol)

/** Make a pattern looks like `globalThis.Predef.MatchFailure.class`. */
/** Make a pattern looks like `runtime.MatchFailure.class`. */
protected def matchFailurePattern(parameters: Opt[List[BlockLocalSymbol]]): Ctxl[Pattern.ClassLike] =
val (classRef, classSym) = matchResultClass
val classSel = Term.SynthSel(classRef, Ident("class"))(S(classSym))
Pattern.ClassLike(classSym, classSel, parameters, false)(Empty())

/** Create a term that selects a method in the `Predef` module. */
protected final def selectPredefMethod =
sel(sel(globalThisSymbol.ref(), "Predef"), _: Str)
protected lazy val tupleSlice = sel(sel(state.runtimeSymbol.ref(), "Tuple"), "slice")
protected lazy val tupleGet = sel(sel(state.runtimeSymbol.ref(), "Tuple"), "get")
protected lazy val stringStartsWith = sel(sel(state.runtimeSymbol.ref(), "Str"), "startsWith")
protected lazy val stringGet = sel(sel(state.runtimeSymbol.ref(), "Str"), "get")
protected lazy val stringDrop = sel(sel(state.runtimeSymbol.ref(), "Str"), "drop")

protected lazy val tupleSlice = selectPredefMethod("tupleSlice")
protected lazy val tupleGet = selectPredefMethod("tupleGet")
protected lazy val stringStartsWith = selectPredefMethod("stringStartsWith")
protected lazy val stringGet = selectPredefMethod("stringGet")
protected lazy val stringDrop = selectPredefMethod("stringDrop")

/** Make a term that looks like `tupleGet(t, i)`. */
/** Make a term that looks like `runtime.Tuple.get(t, i)`. */
protected final def callTupleGet(t: Term, i: Int, label: Str): Ctxl[Term] =
callTupleGet(t, i, FlowSymbol(label))

/** Make a term that looks like `tupleGet(t, i)`. */
/** Make a term that looks like `runtime.Tuple.slice(t, i)`. */
protected final def callTupleGet(t: Term, i: Int, s: FlowSymbol): Ctxl[Term] =
app(tupleGet, tup(fld(t), fld(int(i))), s)

/** Make a term that looks like `stringStartsWith(t, p)`. */
protected final def callStringStartsWith(t: Term, p: Term, label: Str): Ctxl[Term] =
/** Make a term that looks like `runtime.Str.startsWith(t, p)`. */
protected final def callStringStartsWith(t: Term.Ref, p: Term, label: Str): Ctxl[Term] =
app(stringStartsWith, tup(fld(t), fld(p)), FlowSymbol(label))

/** Make a term that looks like `stringStartsWith(t, i)`. */
protected final def callStringGet(t: Term, i: Int, label: Str): Ctxl[Term] =
/** Make a term that looks like `runtime.Str.get(t, i)`. */
protected final def callStringGet(t: Term.Ref, i: Int, label: Str): Ctxl[Term] =
app(stringGet, tup(fld(t), fld(int(i))), FlowSymbol(label))

/** Make a term that looks like `stringStartsWith(t, n)`. */
protected final def callStringDrop(t: Term, n: Int, label: Str): Ctxl[Term] =
/** Make a term that looks like `runtime.Str.drop(t, n)`. */
protected final def callStringDrop(t: Term.Ref, n: Int, label: Str): Ctxl[Term] =
app(stringDrop, tup(fld(t), fld(int(n))), FlowSymbol(label))

protected final def tempLet(dbgName: Str, term: Term)(inner: TempSymbol => Split): Split =
Expand Down
35 changes: 9 additions & 26 deletions hkmc2/shared/src/test/mlscript-compile/Predef.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,32 +150,24 @@ let Predef1;
static tuple(...xs1) {
return xs1
}
static tupleSlice(xs2, i, j) {
let tmp;
tmp = xs2.length - j;
return runtime.safeCall(globalThis.Array.prototype.slice.call(xs2, i, tmp))
}
static tupleGet(xs3, i1) {
return globalThis.Array.prototype.at.call(xs3, i1)
}
static foldr(f9) {
return (first, ...rest) => {
let len, i2, init, scrut, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5;
let len, i, init, scrut, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5;
len = rest.length;
scrut1 = len == 0;
if (scrut1 === true) {
return first
} else {
tmp = len - 1;
i2 = tmp;
tmp1 = runtime.safeCall(rest.at(i2));
i = tmp;
tmp1 = runtime.safeCall(rest.at(i));
init = tmp1;
tmp6: while (true) {
scrut = i2 > 0;
scrut = i > 0;
if (scrut === true) {
tmp2 = i2 - 1;
i2 = tmp2;
tmp3 = runtime.safeCall(rest.at(i2));
tmp2 = i - 1;
i = tmp2;
tmp3 = runtime.safeCall(rest.at(i));
tmp4 = runtime.safeCall(f9(tmp3, init));
init = tmp4;
tmp5 = runtime.Unit;
Expand All @@ -189,7 +181,7 @@ let Predef1;
}
}
}
static mkStr(...xs4) {
static mkStr(...xs2) {
let tmp, tmp1, lambda;
lambda = (undefined, function (acc, x7) {
let tmp2, tmp3, tmp4;
Expand All @@ -204,16 +196,7 @@ let Predef1;
});
tmp = lambda;
tmp1 = runtime.safeCall(Predef.fold(tmp));
return runtime.safeCall(tmp1(...xs4))
}
static stringStartsWith(string, prefix) {
return runtime.safeCall(string.startsWith(prefix))
}
static stringGet(string1, i2) {
return runtime.safeCall(string1.at(i2))
}
static stringDrop(string2, n) {
return runtime.safeCall(string2.slice(n))
return runtime.safeCall(tmp1(...xs2))
}
static get unreachable() {
throw globalThis.Error("unreachable");
Expand Down
16 changes: 0 additions & 16 deletions hkmc2/shared/src/test/mlscript-compile/Predef.mls
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,6 @@ fun (???) notImplementedError = throw Error("Not implemented")

fun tuple(...xs) = xs

fun tupleSlice(xs, i, j) =
// * This is more robust than `xs.slice(i, xs.length - j)`
// * as it is not affected by users redefining `slice`
globalThis.Array.prototype.slice.call(xs, i, xs.length - j)

fun tupleGet(xs, i) =
// * Contrary to `xs.[i]`, this supports negative indices (Python-style)
globalThis.Array.prototype.at.call(xs, i)

val foldl = fold

// fun foldr(f)(...rest, init) = // TODO allow this syntax
Expand All @@ -78,13 +69,6 @@ fun mkStr(...xs) =
fold((acc, x) => assert(x is Str); acc + x) of ...xs


fun stringStartsWith(string, prefix) = string.startsWith(prefix)

fun stringGet(string, i) = string.at(i)

fun stringDrop(string, n) = string.slice(n)


fun unreachable = throw Error("unreachable")

fun checkArgs(functionName, expected, isUB, got) =
Expand Down
29 changes: 29 additions & 0 deletions hkmc2/shared/src/test/mlscript-compile/Runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,35 @@ let Runtime1;
}
toString() { return "MatchFailure(" + globalThis.Predef.render(this.errors) + ")"; }
};
(class Tuple {
static {
Runtime.Tuple = Tuple;
}
static slice(xs, i, j) {
let tmp;
tmp = xs.length - j;
return runtime.safeCall(globalThis.Array.prototype.slice.call(xs, i, tmp))
}
static get(xs1, i1) {
return globalThis.Array.prototype.at.call(xs1, i1)
}
static toString() { return "Tuple"; }
});
(class Str {
static {
Runtime.Str = Str;
}
static startsWith(string, prefix) {
return runtime.safeCall(string.startsWith(prefix))
}
static get(string1, i) {
return runtime.safeCall(string1.at(i))
}
static drop(string2, n) {
return runtime.safeCall(string2.slice(n))
}
static toString() { return "Str"; }
});
const FatalEffect$class = class FatalEffect {
constructor() {}
toString() { return "FatalEffect"; }
Expand Down
17 changes: 17 additions & 0 deletions hkmc2/shared/src/test/mlscript-compile/Runtime.mls
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ fun try(f) =
data class MatchResult(captures)
data class MatchFailure(errors)

// For pattern matching on tuples
module Tuple with
fun slice(xs, i, j) =
// * This is more robust than `xs.slice(i, xs.length - j)`
// * as it is not affected by users redefining `slice`
globalThis.Array.prototype.slice.call(xs, i, xs.length - j)

fun get(xs, i) =
// * Contrary to `xs.[i]`, this supports negative indices (Python-style)
globalThis.Array.prototype.at.call(xs, i)

module Str with
fun startsWith(string, prefix) = string.startsWith(prefix)

fun get(string, i) = string.at(i)

fun drop(string, n) = string.slice(n)

// Private definitions for algebraic effects

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
//│ lambda1 = (undefined, function (...args1) {
//│ globalThis.Predef.checkArgs("", 1, false, args1.length);
//│ let self = args1[0];
//│ let args = globalThis.Predef.tupleSlice(args1, 1, 0);
//│ let args = runtime.Tuple.slice(args1, 1, 0);
//│ return runtime.safeCall(self.x(...args))
//│ });
//│ block$res2 = runtime.checkCall(lambda1());
Expand Down
30 changes: 9 additions & 21 deletions hkmc2/shared/src/test/mlscript/ucs/patterns/RestTuple.mls
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
:js

// Drop the first and last elements.
tupleSlice([1, 2, 3, 4], 1, -1)
//│ = [2, 3, 4]

// Get the first element.
tupleGet([1, 2, 3, 4], 0)
//│ = 1

// Get the last element.
tupleGet([1, 2, 3, 4], -1)
//│ = 4

:sjs
fun nonsense(xs) = if xs is
[..ys] then ys
Expand All @@ -21,7 +9,7 @@ fun nonsense(xs) = if xs is
//│ nonsense = function nonsense(xs) {
//│ let rest, ys;
//│ if (globalThis.Array.isArray(xs) && xs.length >= 0) {
//│ rest = runtime.safeCall(globalThis.Predef.tupleSlice(xs, 0, 0));
//│ rest = runtime.Tuple.slice(xs, 0, 0);
//│ ys = rest;
//│ return ys
//│ } else {
Expand All @@ -45,8 +33,8 @@ fun lead_and_last(xs) = if xs is
//│ let last0, rest, first0, x, ys, y;
//│ if (globalThis.Array.isArray(xs) && xs.length >= 2) {
//│ first0 = xs[0];
//│ rest = runtime.safeCall(globalThis.Predef.tupleSlice(xs, 1, 1));
//│ last0 = globalThis.Predef.tupleGet(xs, -1);
//│ rest = runtime.Tuple.slice(xs, 1, 1);
//│ last0 = runtime.Tuple.get(xs, -1);
//│ x = first0;
//│ ys = rest;
//│ y = last0;
Expand Down Expand Up @@ -78,21 +66,21 @@ fun nested_tuple_patterns(xs) = if xs is
//│ JS (unsanitized):
//│ let nested_tuple_patterns;
//│ nested_tuple_patterns = function nested_tuple_patterns(xs) {
//│ let last0, rest, first0, x, first1, first01, y, z, w, tmp2, tmp3;
//│ let last0, rest, first0, x, first1, first01, y, z, w, tmp, tmp1;
//│ if (globalThis.Array.isArray(xs) && xs.length >= 2) {
//│ first0 = xs[0];
//│ rest = runtime.safeCall(globalThis.Predef.tupleSlice(xs, 1, 1));
//│ last0 = globalThis.Predef.tupleGet(xs, -1);
//│ rest = runtime.Tuple.slice(xs, 1, 1);
//│ last0 = runtime.Tuple.get(xs, -1);
//│ x = first0;
//│ if (globalThis.Array.isArray(rest) && rest.length === 2) {
//│ first01 = rest[0];
//│ first1 = rest[1];
//│ y = first01;
//│ z = first1;
//│ w = last0;
//│ tmp2 = x + y;
//│ tmp3 = tmp2 + z;
//│ return tmp3 + w
//│ tmp = x + y;
//│ tmp1 = tmp + z;
//│ return tmp1 + w
//│ } else {
//│ throw new globalThis.Error("match error");
//│ }
Expand Down
Loading