Skip to content

Commit a734489

Browse files
committed
Improve handling of builtin ops + add Predef functions & various tests
1 parent 676820e commit a734489

23 files changed

+429
-66
lines changed

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package hkmc2
22
package codegen
33

4+
import scala.language.implicitConversions
5+
46
import mlscript.utils.*, shorthands.*
57
import utils.*
68

@@ -76,6 +78,38 @@ class Lowering(using TL, Raise, Elaborator.State):
7678
}(Nil)
7779
case st.Ref(sym) =>
7880
sym match
81+
case sym: BuiltinSymbol =>
82+
warnStmt
83+
if sym.binary then
84+
val t1 = new Tree.Ident("arg1")
85+
val t2 = new Tree.Ident("arg2")
86+
val p1 = Param(FldFlags.empty, VarSymbol(t1), N)
87+
val p2 = Param(FldFlags.empty, VarSymbol(t2), N)
88+
val ps = PlainParamList(p1 :: p2 :: Nil)
89+
val bod = st.App(t, st.Tup(List(st.Ref(p1.sym)(t1, 666), st.Ref(p2.sym)(t2, 666)))
90+
(Tree.Tup(Nil // FIXME should not be required (using dummy value)
91+
)))(
92+
Tree.App(Tree.Empty(), Tree.Empty()), // FIXME should not be required (using dummy value)
93+
FlowSymbol(sym.nme)
94+
)
95+
val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme))
96+
tl.log(s"Ref builtin $sym")
97+
assert(paramLists.length === 1)
98+
return k(Value.Lam(paramLists.head, bodyBlock))
99+
if sym.unary then
100+
val t1 = new Tree.Ident("arg")
101+
val p1 = Param(FldFlags.empty, VarSymbol(t1), N)
102+
val ps = PlainParamList(p1 :: Nil)
103+
val bod = st.App(t, st.Tup(List(st.Ref(p1.sym)(t1, 666)))
104+
(Tree.Tup(Nil // FIXME should not be required (using dummy value)
105+
)))(
106+
Tree.App(Tree.Empty(), Tree.Empty()), // FIXME should not be required (using dummy value)
107+
FlowSymbol(sym.nme)
108+
)
109+
val (paramLists, bodyBlock) = setupFunctionDef(ps :: Nil, bod, S(sym.nme))
110+
tl.log(s"Ref builtin $sym")
111+
assert(paramLists.length === 1)
112+
return k(Value.Lam(paramLists.head, bodyBlock))
79113
case bs: BlockMemberSymbol =>
80114
bs.defn match
81115
case S(td: TermDefinition) if td.k is syntax.Fun =>
@@ -90,6 +124,35 @@ class Lowering(using TL, Raise, Elaborator.State):
90124
case _ => ()
91125
warnStmt
92126
k(subst(Value.Ref(sym)))
127+
case st.App(Ref(sym: BuiltinSymbol), arg) =>
128+
arg match
129+
case st.Tup(Nil) =>
130+
if !sym.nullary then raise:
131+
ErrorReport(
132+
msg"Expected arguments for ${sym.nme}" -> t.toLoc :: Nil, S(arg),
133+
source = Diagnostic.Source.Compilation)
134+
k(Value.Ref(sym))
135+
case st.Tup(Fld(FldFlags.benign(), arg, N) :: Nil) =>
136+
if !sym.unary then raise:
137+
ErrorReport(
138+
msg"Expected a single argument for ${sym.nme}" -> t.toLoc :: Nil, S(arg),
139+
source = Diagnostic.Source.Compilation)
140+
subTerm(arg): ar =>
141+
k(Call(Value.Ref(sym), Arg(false, ar) :: Nil)(true))
142+
case st.Tup(Fld(FldFlags.benign(), arg1, N) :: Fld(FldFlags.benign(), arg2, N) :: Nil) =>
143+
if !sym.binary then raise:
144+
ErrorReport(
145+
msg"Expected two arguments for ${sym.nme}" -> t.toLoc :: Nil, S(arg),
146+
source = Diagnostic.Source.Compilation)
147+
subTerm(arg1): ar1 =>
148+
subTerm(arg2): ar2 =>
149+
k(Call(Value.Ref(sym), Arg(false, ar1) :: Arg(false, ar2) :: Nil)(true))
150+
case _ =>
151+
raise:
152+
ErrorReport(
153+
msg"Unexpected arguments for builtin symbol '${sym.nme}'" -> arg.toLoc :: Nil, S(arg),
154+
source = Diagnostic.Source.Compilation)
155+
End("error")
93156
case st.App(f, arg) =>
94157
val isMlsFun = f.symbol.fold(f.isInstanceOf[st.Lam]):
95158
case _: sem.BuiltinSymbol => true

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ object Elaborator:
117117
val globalThisSymbol = TopLevelSymbol("globalThis")
118118
val builtinOpsMap =
119119
val baseBuiltins = builtins.map: op =>
120-
op -> BuiltinSymbol(op, binary = binaryOps(op), unary = unaryOps(op), nullary = false, functionLike = anyOps(op))
120+
op -> BuiltinSymbol(op,
121+
binary = binaryOps(op),
122+
unary = unaryOps(op),
123+
nullary = false,
124+
functionLike = anyOps(op))
121125
.toMap
122126
baseBuiltins ++ aliasOps.map:
123127
case (alias, base) => alias -> baseBuiltins(base)

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ sealed abstract class Elem:
365365
case Fld(_, term, asc) => term :: asc.toList
366366
case Spd(_, term) => term :: Nil
367367
def showDbg: Str
368+
object Elem:
369+
given Conversion[Term, Elem] = PlainFld(_)
368370
final case class Fld(flags: FldFlags, term: Term, asc: Opt[Term]) extends Elem with FldImpl
369371
object PlainFld:
370372
def apply(term: Term) = Fld(FldFlags.empty, term, N)

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

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const Predef$class = class Predef {
22
constructor() {
33
this.assert = console.assert;
4+
this.foldl = this.fold;
45
this.MatchResult = function MatchResult(captures1) { return new MatchResult.class(captures1); };
56
this.MatchResult.class = class MatchResult {
67
constructor(captures) {
@@ -138,13 +139,70 @@ const Predef$class = class Predef {
138139
get notImplementedError() {
139140
throw Error("Not implemented");
140141
}
141-
tupleSlice(xs1, i, j) {
142+
tuple(...xs1) {
143+
return xs1;
144+
}
145+
tupleSlice(xs2, i, j) {
142146
let tmp;
143-
tmp = xs1.length - j;
144-
return globalThis.Array.prototype.slice.call(xs1, i, tmp) ?? null;
147+
tmp = xs2.length - j;
148+
return globalThis.Array.prototype.slice.call(xs2, i, tmp) ?? null;
149+
}
150+
tupleGet(xs3, i1) {
151+
return globalThis.Array.prototype.at.call(xs3, i1);
152+
}
153+
fold(f9) {
154+
return (init, ...rest) => {
155+
let i2, len, scrut, tmp, tmp1, tmp2, tmp3;
156+
i2 = 0;
157+
len = rest.length;
158+
tmp4: while (true) {
159+
scrut = i2 < len;
160+
if (scrut) {
161+
tmp = rest.at(i2) ?? null;
162+
tmp1 = f9(init, tmp) ?? null;
163+
init = tmp1;
164+
tmp2 = i2 + 1;
165+
i2 = tmp2;
166+
tmp3 = null;
167+
continue tmp4;
168+
} else {
169+
tmp3 = null;
170+
}
171+
break;
172+
}
173+
return init;
174+
};
145175
}
146-
tupleGet(xs2, i1) {
147-
return globalThis.Array.prototype.at.call(xs2, i1);
176+
foldr(f10) {
177+
return (first, ...rest) => {
178+
let len, i2, init, scrut, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5;
179+
len = rest.length;
180+
scrut1 = len == 0;
181+
if (scrut1) {
182+
return first;
183+
} else {
184+
tmp = len - 1;
185+
i2 = tmp;
186+
tmp1 = rest.at(i2) ?? null;
187+
init = tmp1;
188+
tmp6: while (true) {
189+
scrut = i2 > 0;
190+
if (scrut) {
191+
tmp2 = i2 - 1;
192+
i2 = tmp2;
193+
tmp3 = rest.at(i2) ?? null;
194+
tmp4 = f10(tmp3, init) ?? null;
195+
init = tmp4;
196+
tmp5 = null;
197+
continue tmp6;
198+
} else {
199+
tmp5 = null;
200+
}
201+
break;
202+
}
203+
return f10(first, init) ?? null;
204+
}
205+
};
148206
}
149207
stringStartsWith(string, prefix) {
150208
return string.startsWith(prefix) ?? null;
@@ -156,7 +214,7 @@ const Predef$class = class Predef {
156214
return string2.slice(n) ?? null;
157215
}
158216
checkArgs(functionName, expected, isUB, got) {
159-
let scrut, name, scrut1, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10, tmp11;
217+
let scrut, name, scrut1, scrut2, tmp, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8;
160218
tmp = got < expected;
161219
tmp1 = got > expected;
162220
tmp2 = isUB && tmp1;
@@ -170,18 +228,22 @@ const Predef$class = class Predef {
170228
tmp4 = "";
171229
}
172230
name = tmp4;
173-
tmp5 = "Function" + name;
174-
tmp6 = tmp5 + " expected ";
231+
tmp5 = this.fold((arg1, arg2) => {
232+
return arg1 + arg2;
233+
});
175234
if (isUB) {
235+
tmp6 = "";
236+
} else {
237+
tmp6 = "at least ";
238+
}
239+
scrut2 = expected === 1;
240+
if (scrut2) {
176241
tmp7 = "";
177242
} else {
178-
tmp7 = "at least ";
243+
tmp7 = "s";
179244
}
180-
tmp8 = tmp6 + tmp7;
181-
tmp9 = tmp8 + expected;
182-
tmp10 = " argument(s) but got " + got;
183-
tmp11 = tmp9 + tmp10;
184-
throw globalThis.Error(tmp11) ?? null;
245+
tmp8 = tmp5("Function", name, " expected ", tmp6, expected, " argument", tmp7, " but got ", got) ?? null;
246+
throw Error(tmp8);
185247
} else {
186248
return null;
187249
}

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

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ fun id(x) = x
55

66
fun not(x) = x is false
77

8+
89
fun (|>) pipeInto(x, f) = f(x)
910
fun (<|) pipeFrom(f, x) = f(x)
1011

@@ -19,6 +20,7 @@ fun pass1(f)(...xs) = f(xs.0)
1920
fun pass2(f)(...xs) = f(xs.0, xs.1)
2021
fun pass3(f)(...xs) = f(xs.0, xs.1, xs.2)
2122

23+
2224
fun print(...xs) =
2325
console.log(...xs.map(String))
2426

@@ -27,28 +29,66 @@ val assert = console.assert
2729
fun (??) notImplemented(msg) = throw Error("Not implemented: " + msg)
2830
fun (???) notImplementedError = throw Error("Not implemented")
2931

32+
33+
fun tuple(...xs) = xs
34+
3035
fun tupleSlice(xs, i, j) =
3136
globalThis.Array.prototype.slice.call(xs, i, xs.length - j)
3237

3338
fun tupleGet(xs, i) =
3439
globalThis.Array.prototype.at.call(xs, i)
3540

41+
fun fold(f)(init, ...rest) =
42+
let
43+
i = 0
44+
len = rest.length
45+
while i < len do
46+
set
47+
init = f(init, rest.at(i))
48+
i += 1
49+
init
50+
51+
val foldl = fold
52+
53+
// fun foldr(f)(...rest, init) = // TODO allow this syntax
54+
fun foldr(f)(first, ...rest) =
55+
let len = rest.length
56+
if len == 0 then first else...
57+
let
58+
i = len - 1
59+
init = rest.at(i)
60+
while i > 0 do
61+
set
62+
i -= 1
63+
init = f(rest.at(i), init)
64+
f(first, init)
65+
66+
3667
fun stringStartsWith(string, prefix) = string.startsWith(prefix)
3768

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

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

73+
4274
class MatchResult(captures)
4375
class MatchFailure(errors)
4476

77+
4578
fun checkArgs(functionName, expected, isUB, got) =
4679
if got < expected || isUB && got > expected do
4780
let name = if functionName.length > 0 then " '" + functionName + "'" else ""
48-
throw globalThis.Error("Function" + name + " expected "
49-
+ (if isUB then "" else "at least ")
50-
+ expected
51-
+ " argument(s) but got " + got)
81+
// throw globalThis.Error("Function" + name + " expected "
82+
// + (if isUB then "" else "at least ")
83+
// + expected
84+
// + " argument(s) but got " + got)
85+
throw Error of fold(+) of
86+
"Function", name, " expected "
87+
if isUB then "" else "at least "
88+
expected, " argument"
89+
if expected === 1 then "" else "s"
90+
" but got ", got
91+
5292

5393
module TraceLogger with
5494
mut val enabled = false
@@ -68,7 +108,9 @@ module TraceLogger with
68108
console.log("| ".repeat(indentLvl) + msg.replaceAll("\n", "\n" + " ".repeat(indentLvl)))
69109
else ()
70110

111+
71112
class Test with
72113
print("Test")
73114
val y = 1
74115

116+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
:js
2+
3+
4+
// * Should we use this syntax?
5+
6+
:todo
7+
let rcd =
8+
fieldA: 1
9+
fieldB:
10+
"..."
11+
//│ ╔══[ERROR] Name not found: fieldA
12+
//│ ║ l.8: fieldA: 1
13+
//│ ╙── ^^^^^^
14+
//│ ╔══[ERROR] Name not found: fieldB
15+
//│ ║ l.9: fieldB:
16+
//│ ╙── ^^^^^^
17+
18+
// * Or this syntax?
19+
20+
:todo
21+
let rcd =
22+
fieldA = 1
23+
fieldB =
24+
"..."
25+
//│ ╔══[ERROR] Name not found: fieldA
26+
//│ ║ l.22: fieldA = 1
27+
//│ ╙── ^^^^^^
28+
//│ ╔══[ERROR] Name not found: fieldB
29+
//│ ║ l.23: fieldB =
30+
//│ ╙── ^^^^^^
31+
32+
// * Or this syntax?
33+
34+
:todo
35+
let rcd = new with
36+
fieldA = 1
37+
fieldB =
38+
"..."
39+
//│ ╔══[PARSE ERROR] Expected expression or block after `new` keyword; found 'with' keyword instead
40+
//│ ║ l.35: let rcd = new with
41+
//│ ╙── ^^^^
42+
43+

hkmc2/shared/src/test/mlscript/backlog/UCS.mls

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,18 @@ else
9595

9696
// ——— ——— ———
9797

98+
// * Example confusing parses that should be fixed when we fix the parsing of operator splits
99+
100+
if 1 + 0
101+
== 0 then "X"
102+
== 1 then "A"
103+
//│ = 'A'
104+
105+
if 1 + 1
106+
* 2 == 4 then "X"
107+
* 2 == 1 then "A"
108+
|> id is r then r
109+
//│ = 2
110+
111+
// ——— ——— ———
112+

hkmc2/shared/src/test/mlscript/basics/BadMemberProjections.mls

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
//│ let args = globalThis.Predef.tupleSlice(args1, 1, 0);
3030
//│ return self.x(...args) ?? null;
3131
//│ })()
32-
//│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument(s) but got 0
32+
//│ ═══[RUNTIME ERROR] Error: Function expected at least 1 argument but got 0
3333

3434

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

0 commit comments

Comments
 (0)