diff --git a/hkmc2/jvm/src/test/scala/hkmc2/DiffMaker.scala b/hkmc2/jvm/src/test/scala/hkmc2/DiffMaker.scala index dcd0c042f..429e274b3 100644 --- a/hkmc2/jvm/src/test/scala/hkmc2/DiffMaker.scala +++ b/hkmc2/jvm/src/test/scala/hkmc2/DiffMaker.scala @@ -272,6 +272,10 @@ abstract class DiffMaker: if diff.forall(l => l.startsWith(output.outputMarker) || l.startsWith(output.diffMidMarker) || l.startsWith(output.diff3MidMarker) || l.isEmpty) then { for _ <- 1 to blankLines do out.println() } else { + val blockLineNum = allLines.size - lines.size + 1 + failures += blockLineNum + doFail(blockLineNum, + s"Unmerged non-output changes at $relativeName.${file.ext}:" + blockLineNum) unmergedChanges += allLines.size - lines.size + 1 out.println(output.diffBegMarker) diff.foreach(out.println) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index 23b0e2d3d..78f264f76 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -85,7 +85,8 @@ object Elaborator: val Num = assumeBuiltinCls("Num") val Str = assumeBuiltinCls("Str") val Predef = assumeBuiltinMod("Predef") - def getBuiltinOp(op: Str): Opt[Str] = if getBuiltin(op).isDefined then builtinBinOps.get(op) else N + def getBuiltinOp(op: Str): Opt[Str] = + if getBuiltin(op).isDefined then builtinBinOps.get(op) else N object Ctx: abstract class Elem: @@ -319,6 +320,12 @@ extends Importer: Term.Deref(term(rhs)) case App(Ident("~"), Tree.Tup(rhs :: Nil)) => term(rhs) + case tree @ App(lhs, OpBlock(ops)) => + ops.foldLeft(term(lhs)): + case (acc, (op, arg)) => + val sym = FlowSymbol("‹app-res›") + val tup = new Tup(Nil) // TODO + Term.App(term(op), Term.Tup(PlainFld(acc) :: PlainFld(term(arg)) :: Nil)(tup))(tree, sym) case tree @ App(lhs, rhs) => val sym = FlowSymbol("‹app-res›") val lt = term(lhs, inAppPrefix = true) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala index a82fbd0ef..1d9b3feb9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Term.scala @@ -340,6 +340,9 @@ sealed abstract class Elem: case Spd(_, term) => term :: Nil def showDbg: Str final case class Fld(flags: FldFlags, term: Term, asc: Opt[Term]) extends Elem with FldImpl +object PlainFld: + def apply(term: Term) = Fld(FldFlags.empty, term, N) + def unapply(fld: Fld): Opt[Term] = S(fld.term) final case class Spd(eager: Bool, term: Term) extends Elem: def showDbg: Str = (if eager then "..." else "..") + term.showDbg diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 7203a7ca2..9f6ac2718 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -577,7 +577,7 @@ abstract class Parser( consume val bod = yeetSpaces match case Nil | (COMMA, _) :: _ => N - case _ => S(simpleExprImpl(prec)) + case _ => S(expr(prec)) Spread(if dotDotDot then Keyword.`...` else Keyword.`..`, S(loc), bod) case (tok, loc) :: _ => TODO(tok) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index b54217c1d..4b22e3968 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -172,6 +172,8 @@ enum Tree extends AutoLocated: case id: Ident => S(N, id, N) case Spread(Keyword.`..`, _, S(id: Ident)) => S(S(false), id, N) case Spread(Keyword.`...`, _, S(id: Ident)) => S(S(true), id, N) + case Spread(Keyword.`..`, _, S(und: Under)) => S(S(false), new Ident("_").withLocOf(und), N) + case Spread(Keyword.`...`, _, S(und: Under)) => S(S(true), new Ident("_").withLocOf(und), N) case InfixApp(lhs: Ident, Keyword.`:`, rhs) => S(N, lhs, S(rhs)) case TermDef(ImmutVal, inner, _) => inner.asParam diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs index 1e7030b59..d7ea60c1b 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mjs +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mjs @@ -62,8 +62,12 @@ const Predef$class = class Predef { }; this.TraceLogger = new TraceLogger$class; this.TraceLogger.class = TraceLogger$class; + + const this$Predef = this; this.Test = class Test { constructor() { + let tmp; + tmp = this$Predef.print("Test"); this.y = 1; } toString() { return "Test"; } diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index f68238ebe..ef863de44 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -64,5 +64,6 @@ module TraceLogger with else () class Test with + print("Test") val y = 1 diff --git a/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls b/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls new file mode 100644 index 000000000..160a2cf81 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/BlockArgs.mls @@ -0,0 +1,46 @@ +:todo + + +fun foo(x){f} = + f(x) +//│ ╔══[ERROR] Expected a valid definition head, found block instead +//│ ║ l.4: fun foo(x){f} = +//│ ╙── ^ + +foo(1){x => x} +//│ ╔══[ERROR] Name not found: foo +//│ ║ l.10: foo(1){x => x} +//│ ╙── ^^^ +//│ ╔══[ERROR] Illegal juxtaposition right-hand side. +//│ ║ l.10: foo(1){x => x} +//│ ╙── ^^^^^^ + +foo(1) + x => x +//│ ╔══[ERROR] Name not found: foo +//│ ║ l.18: foo(1) +//│ ╙── ^^^ +//│ ╔══[ERROR] Illegal juxtaposition right-hand side. +//│ ║ l.19: x => x +//│ ╙── ^^^^^^ + +foo(1) { x => x } +//│ ╔══[ERROR] Name not found: foo +//│ ║ l.27: foo(1) { x => x } +//│ ╙── ^^^ +//│ ╔══[ERROR] Illegal juxtaposition right-hand side. +//│ ║ l.27: foo(1) { x => x } +//│ ╙── ^^^^^^ + +foo(1) with x => x +//│ ╔══[PARSE ERROR] Expected end of input; found 'with' keyword instead +//│ ║ l.35: foo(1) with x => x +//│ ╙── ^^^^ +//│ ╔══[ERROR] Name not found: foo +//│ ║ l.35: foo(1) with x => x +//│ ╙── ^^^ + + + + + diff --git a/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls new file mode 100644 index 000000000..2ffb43db3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/basics/MiscArrayTests.mls @@ -0,0 +1,85 @@ +:js + + +let xs = [0, 1, 2] +//│ xs = [ 0, 1, 2 ] + +xs. print() +//│ > 0,1,2 + +let f = xs. print +//│ f = [Function (anonymous)] + +f() +//│ > 0,1,2 + +xs.reverse() +//│ = [ 2, 1, 0 ] + +xs +//│ = [ 2, 1, 0 ] + +:re +xs.map(x => x * 2) +//│ ═══[RUNTIME ERROR] Error: Function expected 1 arguments but got 3 + +xs.map((x, i, a) => x * 2) +//│ = [ 4, 2, 0 ] + +:fixme +xs.map((x, ...) => x * 2) +//│ /!!!\ Uncaught error: scala.MatchError: Spread(keyword '...',Some(Loc(11,14,MiscArrayTests.mls:+30)),None) (of class hkmc2.syntax.Tree$Spread) + +xs.map((x, ..._) => x * 2) +//│ = [ 4, 2, 0 ] + +fun map(xs, f) = xs.map((x, ...bs) => f(x)) + +xs. map(x => x * 2). print() +//│ > 4,2,0 + +:fixme +:p +xs. + map(x => x * 2) +//│ |xs|.|→|map|(|x| |=>| |x| |*| |2|)|←| +//│ Parsed: +//│ App(Ident(.),Tup(List(Ident(xs), Block(List(App(Ident(map),Tup(List(InfixApp(Tup(List(Ident(x))),keyword '=>',App(Ident(*),Tup(List(Ident(x), IntLit(2))))))))))))) +//│ ═══[RUNTIME ERROR] Error: Function 'map' expected 2 arguments but got 1 + +:fixme +:pt +xs + . map(f) +//│ Parsed tree: +//│ App: +//│ lhs = Ident of "xs" +//│ rhs = OpBlock of Ls of +//│ Tuple2: +//│ _1 = Ident of "." +//│ _2 = App: +//│ lhs = Ident of "map" +//│ rhs = Tup of Ls of +//│ Ident of "f" +//│ ═══[RUNTIME ERROR] Error: Function 'map' expected 2 arguments but got 1 + +:fixme +xs + . map(x => x * 2) +//│ ═══[RUNTIME ERROR] Error: Function 'map' expected 2 arguments but got 1 + +:fixme +xs + . map(x => x * 2) + . print() +//│ ═══[RUNTIME ERROR] Error: Function 'map' expected 2 arguments but got 1 + +fun f = print(0) + +f +//│ > 0 + +f +//│ > 0 + + diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index 8e99ece9b..9d3318d39 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -1,19 +1,60 @@ :js -:todo +2 + + 2 +//│ = 4 + +2 + + 2 + + 2 +//│ = 6 + +2 + + 1 + * 2 +//│ = 6 + +2 + + 1 + * 2 +//│ = 4 + +:pe +2 + + 1 + * 2 +//│ ╔══[PARSE ERROR] Expected end of input; found indented block instead +//│ ║ l.26: * 2 +//│ ╙── ^^ +//│ = 3 + + +let f = x => x * 2 +//│ f = [Function (anonymous)] + +2 + + 1 + |> f +//│ = 6 + + fun f(x) = x + 1 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),IntLit(1)))) (of class hkmc2.syntax.Tree$OpBlock) -:todo +f(123) +//│ = 124 + + fun f(x) = x * 2 + 1 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(*),IntLit(2)), (Ident(+),IntLit(1)))) (of class hkmc2.syntax.Tree$OpBlock) + +f(123) +//│ = 247 + :pt -:todo fun f(x) = x + 1 * 2 @@ -33,10 +74,12 @@ fun f(x) = x //│ Tuple2: //│ _1 = Ident of "*" //│ _2 = IntLit of 2 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),IntLit(1)), (Ident(*),IntLit(2)))) (of class hkmc2.syntax.Tree$OpBlock) + +f(123) +//│ = 248 + :pt -:todo fun f(x) = x + 1 * 2 @@ -58,16 +101,17 @@ fun f(x) = x //│ Tuple2: //│ _1 = Ident of "*" //│ _2 = IntLit of 2 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),App(IntLit(1),OpBlock(List((Ident(*),IntLit(2)))))))) (of class hkmc2.syntax.Tree$OpBlock) + +f(123) +//│ = 125 -:pe :fixme +:pe fun f(x) = x { + 1, * 2 } //│ ╔══[PARSE ERROR] Unexpected comma in this position -//│ ║ l.66: fun f(x) = x { + 1, * 2 } -//│ ╙── ^ -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(+),App(Ident(*),Tup(List(IntLit(1), IntLit(2))))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ ║ l.111: fun f(x) = x { + 1, * 2 } +//│ ╙── ^ :pt @@ -118,10 +162,10 @@ fun f(x) = if x > 0 then "a" is 0 then "b" //│ ╔══[PARSE ERROR] Expect an operator instead of 'is' keyword -//│ ║ l.119: is 0 then "b" +//│ ║ l.163: is 0 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Unexpected 'is' keyword here -//│ ║ l.119: is 0 then "b" +//│ ║ l.163: is 0 then "b" //│ ╙── ^^ //│ ═══[ERROR] Unrecognized operator branch. @@ -133,11 +177,11 @@ fun f(x) = if x foo(A) then a bar(B) then b //│ ╔══[ERROR] Unrecognized term split (juxtaposition). -//│ ║ l.132: fun f(x) = if x +//│ ║ l.176: fun f(x) = if x //│ ║ ^ -//│ ║ l.133: foo(A) then a +//│ ║ l.177: foo(A) then a //│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.134: bar(B) then b +//│ ║ l.178: bar(B) then b //│ ╙── ^^^^^^^^^^^^^^^ @@ -146,10 +190,10 @@ fun f(x) = if x is 0 then "a" is 1 then "b" //│ ╔══[PARSE ERROR] Expected start of statement in this position; found 'is' keyword instead -//│ ║ l.147: is 1 then "b" +//│ ║ l.191: is 1 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.147: is 1 then "b" +//│ ║ l.191: is 1 then "b" //│ ╙── ^ //│ = [Function: f] diff --git a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls index cfa319229..c09f0f1cb 100644 --- a/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls +++ b/hkmc2/shared/src/test/mlscript/basics/PredefTest.mls @@ -1,17 +1,37 @@ :js +Predef.Test +//│ = [class Test] + Test === Predef.Test //│ = true Predef.Test === globalThis.Predef.Test //│ = true +1 is Predef.Test +//│ = false + +:fixme +new Predef.Test is Predef.Test +//│ ═══[RUNTIME ERROR] TypeError: tmp2 is not a constructor + +(new Predef.Test) is Predef.Test +//│ > Test +//│ = true + +new Predef.Test +//│ > Test +//│ = Test { y: 1 } + (new Predef.Test).y +//│ > Test //│ = 1 :re (new Predef.Test).x +//│ > Test //│ ═══[RUNTIME ERROR] Error: Access to required field 'x' yielded 'undefined' diff --git a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls index ccab8b5b4..9339b124a 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Getters.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Getters.mls @@ -14,6 +14,32 @@ t //│ = 42 +:sjs +fun test() = + fun whoops = + print("ok") + 42 + whoops + whoops +//│ JS (unsanitized): +//│ function test() { +//│ let tmp, tmp1; +//│ function whoops() { +//│ let tmp2; +//│ tmp2 = Predef.print("ok"); +//│ return 42; +//│ } +//│ tmp = whoops(); +//│ tmp1 = whoops(); +//│ return tmp + tmp1; +//│ } +//│ null + +test() +//│ > ok +//│ > ok +//│ = 84 + + :sjs module T with fun t() = 1 @@ -251,3 +277,19 @@ fun foo(f) = foo(Foo(42)) //│ = 42 + + +fun foo(f) = + f.oops + +class Foo(x) with + fun oops = x + +foo(Foo(42)) +//│ = 42 + +:re +foo(Foo(42))() +//│ ═══[RUNTIME ERROR] TypeError: tmp1 is not a function + + diff --git a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls index 77ea74f9e..d47cc2340 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/OptMatch.mls @@ -94,19 +94,23 @@ fun keepIfGreaterThan(x, y) = console.log() //│ > true +:fixme +1 + . keepIfGreaterThan(0) +//│ ═══[RUNTIME ERROR] Error: Function 'keepIfGreaterThan' expected 2 arguments but got 1 -:todo // alternative syntax? + +:fixme // or use an alternative syntax? 1 . keepIfGreaterThan(0) . isDefined() . console.log() -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(.),App(Ident(keepIfGreaterThan),Tup(List(IntLit(0))))), (Ident(.),App(Ident(isDefined),Tup(List()))), (Ident(.),App(Sel(Ident(console),Ident(log)),Tup(List()))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ ═══[RUNTIME ERROR] Error: Function 'keepIfGreaterThan' expected 2 arguments but got 1 -:todo 1 |> keepIfGreaterThan(_, 0) |> isDefined(_) -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(|>),App(Ident(keepIfGreaterThan),Tup(List(Under(), IntLit(0))))), (Ident(|>),App(Ident(isDefined),Tup(List(Under())))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ = true diff --git a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls index 6e5605de5..dc0d72844 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/PartialApps.mls @@ -37,21 +37,11 @@ foo(_, _, 3)(1, _)(2) :todo foo(..._) -//│ ╔══[PARSE ERROR] Unexpected keyword '_' in this position -//│ ║ l.39: foo(..._) -//│ ╙── ^ -//│ ╔══[PARSE ERROR] Unexpected '_' keyword here -//│ ║ l.39: foo(..._) -//│ ╙── ^ +//│ ═══[ERROR] Illegal position for '_' placeholder. :todo [..._] -//│ ╔══[PARSE ERROR] Unexpected keyword '_' in this position -//│ ║ l.48: [..._] -//│ ╙── ^ -//│ ╔══[PARSE ERROR] Unexpected '_' keyword here -//│ ║ l.48: [..._] -//│ ╙── ^ +//│ ═══[ERROR] Illegal position for '_' placeholder. let h = _ - 2 @@ -145,17 +135,16 @@ _ - 2 <| 1 1 |> _ - 2 //│ ╔══[PARSE ERROR] Expected end of input; found '_' keyword instead -//│ ║ l.146: |> _ - 2 +//│ ║ l.136: |> _ - 2 //│ ╙── ^ //│ ╔══[WARNING] Pure expression in statement position -//│ ║ l.145: 1 +//│ ║ l.135: 1 //│ ╙── ^ //│ = [Function: pipeInto] -:todo 1 |> _ - 2 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(|>),App(Ident(-),Tup(List(Under(), IntLit(2))))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ = -1 :sjs 1 . _ - 2 diff --git a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls index dfa6ae1f7..e3db58688 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ThisCallVariations.mls @@ -72,16 +72,19 @@ let oops2 = Example2(0).f Example2(1) . oops2(2) . oops2(2) //│ = Example2 { a: 1 } +Example2(1). oops2(2). oops2(2) +//│ = Example2 { a: 1 } + :todo new Example2(1) . oops2(2) -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(.),App(Ident(oops2),Tup(List(IntLit(2))))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ ═══[RUNTIME ERROR] TypeError: tmp2 is not a constructor :todo new Example2(1) . oops2(2) . oops2(2) -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(.),App(Ident(oops2),Tup(List(IntLit(2))))), (Ident(.),App(Ident(oops2),Tup(List(IntLit(2))))))) (of class hkmc2.syntax.Tree$OpBlock) +//│ ═══[RUNTIME ERROR] TypeError: tmp4 is not a constructor // * Alternative syntax – precedence is now inappropriate @@ -113,4 +116,11 @@ Example |>. oops(2) Example2(1) |>. oops2(2) |>. oops2(2) //│ = Example2 { a: 1 } +:fixme +(Example2(1) + |>. oops2(2) + |>. oops2(2) +).a +//│ ═══[RUNTIME ERROR] Error: Access to required field 'a' yielded 'undefined' + diff --git a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls index a190d5672..b254294f7 100644 --- a/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls +++ b/hkmc2/shared/src/test/mlscript/parser/PrefixOps.mls @@ -108,9 +108,8 @@ fun (??) foo(x, y) = x + y //│ ╙── ^ //│ = [Function: foo] -:fixme 1 ?? 2 -//│ /!!!\ Uncaught error: scala.MatchError: OpBlock(List((Ident(??),IntLit(2)))) (of class hkmc2.syntax.Tree$OpBlock) +//│ = 3