diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala index b3cc92ad5..1914f1ab5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala @@ -6,8 +6,8 @@ import mlscript.utils.*, shorthands.* import Message.MessageContext import utils.TraceLogger import hkmc2.syntax.Literal -import Keyword.{as, and, `else`, is, let, `then`} -import collection.mutable.HashMap +import Keyword.{as, and, `do`, `else`, is, let, `then`} +import collection.mutable.{HashMap, SortedSet} import Elaborator.{ctx, Ctxl} import ucs.DesugaringBase @@ -16,30 +16,52 @@ object Desugarer: infix def unapply(tree: Tree): Opt[(Tree, Tree)] = tree match case InfixApp(lhs, `op`, rhs) => S((lhs, rhs)) case _ => N - - /** An extractor that accepts either `A and B` or `A then B`. */ - object `~>`: - infix def unapply(tree: Tree): Opt[(Tree, Tree \/ Tree)] = tree match - case lhs and rhs => S((lhs, L(rhs))) - case lhs `then` rhs => S((lhs, R(rhs))) - case _ => N - + class ScrutineeData: val classes: HashMap[ClassSymbol, List[BlockLocalSymbol]] = HashMap.empty val tupleLead: HashMap[Int, BlockLocalSymbol] = HashMap.empty val tupleLast: HashMap[Int, BlockLocalSymbol] = HashMap.empty end Desugarer -class Desugarer(tl: TraceLogger, val elaborator: Elaborator) +class Desugarer(val elaborator: Elaborator) (using raise: Raise, state: Elaborator.State, c: Elaborator.Ctx) extends DesugaringBase: import Desugarer.* import Elaborator.Ctx - import elaborator.term - import tl.* + import elaborator.term, elaborator.tl.* + + given Ordering[Loc] = Ordering.by: loc => + (loc.spanStart, loc.spanEnd) + + /** Keep track of the locations where `do` and `then` are used as connectives. */ + private val kwLocSets = (SortedSet.empty[Loc], SortedSet.empty[Loc]) + + private def reportInconsistentConnectives(kw: Keyword, kwLoc: Opt[Loc]): Unit = + log(kwLocSets) + (kwLocSets._1.headOption, kwLocSets._2.headOption) match + case (Some(doLoc), Some(thenLoc)) => + raise(ErrorReport( + msg"Mixed use of `do` and `then` in the `${kw.name}` expression." -> kwLoc + :: msg"Keyword `then` is used here." -> S(thenLoc) + :: msg"Keyword `do` is used here." -> S(doLoc) :: Nil + )) + case _ => () + + private def topmostDefault: Split = + if kwLocSets._1.nonEmpty then Split.Else(Term.Lit(UnitLit(true))) else Split.End + + /** An extractor that accepts either `A and B`, `A then B`, and `A do B`. It + * also keeps track of the usage of `then` and `do`. + */ + object `~>`: + infix def unapply(tree: Tree): Opt[(Tree, Tree \/ Tree)] = tree match + case lhs and rhs => S((lhs, L(rhs))) + case lhs `then` rhs => kwLocSets._2 ++= tree.toLoc; S((lhs, R(rhs))) + case lhs `do` rhs => kwLocSets._1 ++= tree.toLoc; S((lhs, R(rhs))) + case _ => N // We're working on composing continuations in the UCS translation. // The type of continuation is `Split => Ctx => Split`. - // The first parameter represents the fallback split, which does not have + // The first parameter represents the "backup" split, which does not have // access to the bindings in the current match. The second parameter // represents the context with bindings in the current match. @@ -82,10 +104,6 @@ class Desugarer(tl: TraceLogger, val elaborator: Elaborator) def default: Split => Sequel = split => _ => split - /** Desugar UCS shorthands. */ - def shorthands(tree: Tree): Sequel = termSplitShorthands(tree, identity): - Split.default(Term.Lit(Tree.BoolLit(false))) - private def termSplitShorthands(tree: Tree, finish: Term => Term): Split => Sequel = tree match case Block(branches) => branches match case Nil => lastWords("encountered empty block") @@ -166,6 +184,12 @@ class Desugarer(tl: TraceLogger, val elaborator: Elaborator) val sym = VarSymbol(ident) val fallbackCtx = ctx + (ident.name -> sym) Split.Let(sym, term(termTree)(using ctx), elabFallback(fallback)(fallbackCtx)).withLocOf(t) + case Modified(Keyword.`do`, doLoc, computation) => fallback => ctx => trace( + pre = s"termSplit: do $computation", + post = (res: Split) => s"termSplit: else >>> $res" + ): + val sym = TempSymbol(N, "doTemp") + Split.Let(sym, term(computation)(using ctx), elabFallback(fallback)(ctx)).withLocOf(t) case Modified(Keyword.`else`, elsLoc, default) => fallback => ctx => trace( pre = s"termSplit: else $default", post = (res: Split) => s"termSplit: else >>> $res" @@ -241,6 +265,12 @@ class Desugarer(tl: TraceLogger, val elaborator: Elaborator) val sym = VarSymbol(ident) val fallbackCtx = ctx + (ident.name -> sym) Split.Let(sym, term(termTree)(using ctx), elabFallback(fallbackCtx)) + case (Tree.Empty(), Modified(Keyword.`do`, doLoc, computation)) => ctx => trace( + pre = s"termSplit: do $computation", + post = (res: Split) => s"termSplit: else >>> $res" + ): + val sym = TempSymbol(N, "doTemp") + Split.Let(sym, term(computation)(using ctx), elabFallback(ctx)) case (Tree.Empty(), Modified(Keyword.`else`, elsLoc, default)) => ctx => // TODO: report `rest` as unreachable Split.default(term(default)(using ctx)) @@ -322,6 +352,12 @@ class Desugarer(tl: TraceLogger, val elaborator: Elaborator) val sym = VarSymbol(ident) val fallbackCtx = ctx + (ident.name -> sym) Split.Let(sym, term(termTree)(using ctx), elabFallback(backup)(fallbackCtx)) + case Modified(Keyword.`do`, doLoc, computation) => fallback => ctx => trace( + pre = s"patternSplit (do) <<< $computation", + post = (res: Split) => s"patternSplit: else >>> $res" + ): + val sym = TempSymbol(N, "doTemp") + Split.Let(sym, term(computation)(using ctx), elabFallback(fallback)(ctx)) case Modified(Keyword.`else`, elsLoc, body) => backup => ctx => trace( pre = s"patternSplit (else) <<< $tree", post = (res: Split) => s"patternSplit (else) >>> ${res.showDbg}" @@ -499,4 +535,20 @@ class Desugarer(tl: TraceLogger, val elaborator: Elaborator) ): val innermostSplit = subMatches(rest, sequel)(fallback) expandMatch(scrutinee, pattern, innermostSplit)(fallback) + + /** Desugar `case` expressions. */ + def apply(tree: Case, scrut: VarSymbol)(using Ctx): Split = + val topmost = patternSplit(tree.branches, scrut)(Split.End)(ctx) + reportInconsistentConnectives(Keyword.`case`, tree.kwLoc) + topmost ++ topmostDefault + + /** Desugar `if` and `while` expressions. */ + def apply(tree: IfLike)(using Ctx): Split = + val topmost = termSplit(tree.split, identity)(Split.End)(ctx) + reportInconsistentConnectives(tree.kw, tree.kwLoc) + topmost ++ topmostDefault + + /** Desugar `is` and `and` shorthands. */ + def apply(tree: InfixApp)(using Ctx): Split = + termSplitShorthands(tree, identity)(Split.default(Term.Lit(Tree.BoolLit(false))))(ctx) end Desugarer diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala index d612056e4..20103a46a 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Elaborator.scala @@ -269,8 +269,8 @@ extends Importer: Term.Lam(syms, term(rhs)(using nestCtx)) case InfixApp(lhs, Keyword.`:`, rhs) => Term.Asc(term(lhs), term(rhs)) - case InfixApp(lhs, Keyword.`is` | Keyword.`and`, rhs) => - val des = new Desugarer(tl, this).shorthands(tree)(ctx) + case tree @ InfixApp(lhs, Keyword.`is` | Keyword.`and`, rhs) => + val des = new Desugarer(this)(tree) val nor = new ucs.Normalization(tl)(des) Term.IfLike(Keyword.`if`, des)(nor) case App(Ident("|"), Tree.Tup(lhs :: rhs :: Nil)) => @@ -348,8 +348,8 @@ extends Importer: Term.New(cls(c, inAppPrefix = false), Nil).withLocOf(tree) // case _ => // raise(ErrorReport(msg"Illegal new expression." -> tree.toLoc :: Nil)) - case Tree.IfLike(kw, split) => - val desugared = new Desugarer(tl, this).termSplit(split, identity)(Split.End)(ctx) + case tree @ Tree.IfLike(kw, _, split) => + val desugared = new Desugarer(this)(tree) scoped("ucs:desugared"): log(s"Desugared:\n${Split.display(desugared)}") val normalized = new ucs.Normalization(tl)(desugared) @@ -358,10 +358,9 @@ extends Importer: Term.IfLike(kw, desugared)(normalized) case Tree.Quoted(body) => Term.Quoted(term(body)) case Tree.Unquoted(body) => Term.Unquoted(term(body)) - case Tree.Case(branches) => + case tree @ Tree.Case(_, branches) => val scrut = VarSymbol(Ident("caseScrut")) - val desugarer = new Desugarer(tl, this) - val des = desugarer.patternSplit(branches, scrut)(Split.End)(ctx) + val des = new Desugarer(this)(tree, scrut) scoped("ucs:desugared"): log(s"Desugared:\n${Split.display(des)}") val nor = new ucs.Normalization(tl)(des) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 5c1e680a2..d0757fdd9 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -63,7 +63,11 @@ object Keyword: val `if` = Keyword("if", N, nextPrec) val `while` = Keyword("while", N, curPrec) - val `then` = Keyword("then", nextPrec, curPrec) + + val thenPrec = nextPrec + val `then` = Keyword("then", thenPrec, thenPrec) + val `do` = Keyword("do", thenPrec, thenPrec) + val `else` = Keyword("else", nextPrec, curPrec) val `case` = Keyword("case", N, N) val `fun` = Keyword("fun", N, N) @@ -81,7 +85,6 @@ object Keyword: val `in` = Keyword("in", curPrec, curPrec) val `out` = Keyword("out", N, curPrec) val `set` = Keyword("set", N, curPrec) - val `do` = Keyword("do", N, N) val `declare` = Keyword("declare", N, N) val `trait` = Keyword("trait", N, N) val `mixin` = Keyword("mixin", N, N) @@ -125,7 +128,7 @@ object Keyword: `abstract`, mut, virtual, `override`, declare, public, `private`) type Infix = `and`.type | `or`.type | `then`.type | `else`.type | `is`.type | `:`.type | `->`.type | - `=>`.type | `extends`.type | `restricts`.type | `as`.type + `=>`.type | `extends`.type | `restricts`.type | `as`.type | `do`.type type Ellipsis = `...`.type | `..`.type diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala index 8ce2ae86c..5f3397402 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/ParseRule.scala @@ -183,12 +183,12 @@ class ParseRules(using State): val items = split match case Block(stmts) => stmts.appended(clause) case _ => split :: clause :: Nil - IfLike(kw, Block(items)) - case (split, N) => IfLike(kw, split) + IfLike(kw, N/* TODO */, Block(items)) + case (split, N) => IfLike(kw, N/* TODO */, split) , Blk( ParseRule(s"'${kw.name}' block")(End(())) - ) { case (body, _) => IfLike(kw, body) } + ) { case (body, _) => IfLike(kw, N/* TODO */, body) } ) def typeAliasLike(kw: Keyword, kind: TypeDefKind): Kw[TypeDef] = @@ -258,7 +258,7 @@ class ParseRules(using State): , Kw(`case`): ParseRule("`case` keyword")( - Blk(ParseRule("`case` branches")(End(())))((body, _: Unit) => Case(body)) + Blk(ParseRule("`case` branches")(End(())))((body, _: Unit) => Case(N/* TODO */, body)) ) , Kw(`region`): @@ -358,6 +358,7 @@ class ParseRules(using State): genInfixRule(`:`, (rhs, _: Unit) => lhs => InfixApp(lhs, `:`, rhs)), genInfixRule(`extends`, (rhs, _: Unit) => lhs => InfixApp(lhs, `extends`, rhs)), genInfixRule(`restricts`, (rhs, _: Unit) => lhs => InfixApp(lhs, `restricts`, rhs)), + genInfixRule(`do`, (rhs, _: Unit) => lhs => InfixApp(lhs, `do`, rhs)), ) end ParseRules diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index f9b88ec9a..7a6ba4326 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -547,7 +547,7 @@ abstract class Parser( val ele = simpleExprImpl(prec) term match case InfixApp(lhs, Keyword.`then`, rhs) => - Quoted(IfLike(Keyword.`if`, Block( + Quoted(IfLike(Keyword.`if`, S(l0), Block( InfixApp(Unquoted(lhs), Keyword.`then`, Unquoted(rhs)) :: Modified(Keyword.`else`, N, Unquoted(ele)) :: Nil ))) case tk => @@ -900,6 +900,12 @@ abstract class Parser( case (NEWLINE, _) :: (KEYWORD(kw), _) :: _ if kw.canStartInfixOnNewLine && kw.leftPrecOrMin > prec && infixRules.kwAlts.contains(kw.name) + && (kw isnt Keyword.`do`) // This is to avoid the following case: + // ``` + // 0 then "null" + // do console.log("non-null") + // ``` + // Otherwise, `do` will be parsed as an infix operator => consume exprCont(acc, prec, allowNewlines = false) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 8cc4ca76a..2c65abffd 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -66,10 +66,10 @@ enum Tree extends AutoLocated: case Sel(prefix: 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, split: Tree) + case IfLike(kw: Keyword.`if`.type | Keyword.`while`.type, kwLoc: Opt[Loc], split: Tree) @deprecated("Use If instead", "hkmc2-ucs") case IfElse(cond: Tree, alt: Tree) - case Case(branches: Tree) + case Case(kwLoc: Opt[Loc], branches: Tree) case Region(name: Tree, body: Tree) case RegRef(reg: Tree, value: Tree) case Effectful(eff: Tree, body: Tree) @@ -95,9 +95,9 @@ enum Tree extends AutoLocated: case InfixApp(lhs, _, rhs) => Ls(lhs, rhs) case TermDef(k, head, rhs) => head :: rhs.toList case New(body) => body :: Nil - case IfLike(_, split) => split :: Nil + case IfLike(_, _, split) => split :: Nil case IfElse(cond, alt) => cond :: alt :: Nil - case Case(bs) => Ls(bs) + case Case(_, bs) => Ls(bs) case Region(name, body) => name :: body :: Nil case RegRef(reg, value) => reg :: value :: Nil case Effectful(eff, body) => eff :: body :: Nil @@ -133,9 +133,9 @@ enum Tree extends AutoLocated: case Sel(prefix, name) => "selection" case InfixApp(lhs, kw, rhs) => "infix operation" case New(body) => "new" - case IfLike(Keyword.`if`, split) => "if expression" - case IfLike(Keyword.`while`, split) => "while expression" - case Case(branches) => "case" + case IfLike(Keyword.`if`, _, split) => "if expression" + case IfLike(Keyword.`while`, _, split) => "while expression" + case Case(_, branches) => "case" case Region(name, body) => "region" case RegRef(reg, value) => "region reference" case Effectful(eff, body) => "effectful" diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index 273564850..5ba9be84f 100644 --- a/hkmc2/shared/src/test/mlscript-compile/Predef.mls +++ b/hkmc2/shared/src/test/mlscript-compile/Predef.mls @@ -30,7 +30,7 @@ class MatchResult(captures) class MatchFailure(errors) fun checkArgs(functionName, expected, isUB, got) = - if got < expected || isUB && got > expected then + if got < expected || isUB && got > expected do let name = if functionName.length > 0 then " '" + functionName + "'" else "" throw globalThis.Error("Function" + name + " expected " + expected + " arguments but got " + got) // TODO @@ -38,7 +38,6 @@ fun checkArgs(functionName, expected, isUB, got) = // + expected // + (if isUB then "" else " at least") // + " arguments but got " + got) - else () module TraceLogger with mut val enabled = false diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls index 3197637e9..3e7aaeea3 100644 --- a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls +++ b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls @@ -30,9 +30,8 @@ class Line(val name: Str, val proj: Project, val starting_balance: Num, val isMa fun expense(amt) = set balance = balance -. amt fun mustBeEmpty() = - if balance > 10_000 then + if balance > 10_000 do warnings.push of "> **❗️** Unspent balance of " ~ name ~ ": `" ~ display(balance) ~ "`" - else () // TODO allow omitting else branch val lines = [] diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index aa342d9bf..a2e1ac257 100644 --- a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls +++ b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls @@ -86,6 +86,7 @@ fun f(x) = if x //│ Ident of "x" //│ rhs = S of IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = App: //│ lhs = Ident of "x" //│ rhs = OpBlock of Ls of @@ -120,10 +121,10 @@ fun f(x) = if x > 0 then "a" is 0 then "b" //│ ╔══[PARSE ERROR] Expect an operator instead of 'is' keyword -//│ ║ l.121: is 0 then "b" +//│ ║ l.122: is 0 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Unexpected 'is' keyword here -//│ ║ l.121: is 0 then "b" +//│ ║ l.122: is 0 then "b" //│ ╙── ^^ //│ ═══[ERROR] Unrecognized operator branch. @@ -135,11 +136,11 @@ fun f(x) = if x foo(A) then a bar(B) then b //│ ╔══[ERROR] Unrecognized term split (juxtaposition). -//│ ║ l.134: fun f(x) = if x +//│ ║ l.135: fun f(x) = if x //│ ║ ^ -//│ ║ l.135: foo(A) then a +//│ ║ l.136: foo(A) then a //│ ║ ^^^^^^^^^^^^^^^ -//│ ║ l.136: bar(B) then b +//│ ║ l.137: bar(B) then b //│ ╙── ^^^^^^^^^^^^^^^ @@ -148,10 +149,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.149: is 1 then "b" +//│ ║ l.150: is 1 then "b" //│ ╙── ^^ //│ ╔══[PARSE ERROR] Expected end of input; found literal instead -//│ ║ l.149: is 1 then "b" +//│ ║ l.150: is 1 then "b" //│ ╙── ^ //│ = [Function: f] diff --git a/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls b/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls index 0f8619159..4b83e5a34 100644 --- a/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls +++ b/hkmc2/shared/src/test/mlscript/bbml/bbSyntax.mls @@ -121,33 +121,33 @@ fun id: [A] -> A -> A x => if x == 0 then 1 else x //│ Parsed: -//│ InfixApp(Tup(List(Ident(x))),keyword '=>',IfLike(keyword 'if',Block(List(InfixApp(App(Ident(==),Tup(List(Ident(x), IntLit(0)))),keyword 'then',IntLit(1)), Modified(keyword 'else',None,Ident(x)))))) +//│ InfixApp(Tup(List(Ident(x))),keyword '=>',IfLike(keyword 'if',None,Block(List(InfixApp(App(Ident(==),Tup(List(Ident(x), IntLit(0)))),keyword 'then',IntLit(1)), Modified(keyword 'else',None,Ident(x)))))) if 1 < 2 then 1 else 0 //│ Parsed: -//│ IfLike(keyword 'if',Block(List(InfixApp(App(Ident(<),Tup(List(IntLit(1), IntLit(2)))),keyword 'then',IntLit(1)), Modified(keyword 'else',None,IntLit(0))))) +//│ IfLike(keyword 'if',None,Block(List(InfixApp(App(Ident(<),Tup(List(IntLit(1), IntLit(2)))),keyword 'then',IntLit(1)), Modified(keyword 'else',None,IntLit(0))))) if false then 0 else 42 //│ Parsed: -//│ IfLike(keyword 'if',Block(List(InfixApp(BoolLit(false),keyword 'then',IntLit(0)), Modified(keyword 'else',None,IntLit(42))))) +//│ IfLike(keyword 'if',None,Block(List(InfixApp(BoolLit(false),keyword 'then',IntLit(0)), Modified(keyword 'else',None,IntLit(42))))) if 24 then false else true //│ Parsed: -//│ IfLike(keyword 'if',Block(List(InfixApp(IntLit(24),keyword 'then',BoolLit(false)), Modified(keyword 'else',None,BoolLit(true))))) +//│ IfLike(keyword 'if',None,Block(List(InfixApp(IntLit(24),keyword 'then',BoolLit(false)), Modified(keyword 'else',None,BoolLit(true))))) if x then true else false //│ Parsed: -//│ IfLike(keyword 'if',Block(List(InfixApp(Ident(x),keyword 'then',BoolLit(true)), Modified(keyword 'else',None,BoolLit(false))))) +//│ IfLike(keyword 'if',None,Block(List(InfixApp(Ident(x),keyword 'then',BoolLit(true)), Modified(keyword 'else',None,BoolLit(false))))) if 1 is Int then 1 else 0 //│ Parsed: -//│ IfLike(keyword 'if',Block(List(InfixApp(InfixApp(IntLit(1),keyword 'is',Ident(Int)),keyword 'then',IntLit(1)), Modified(keyword 'else',None,IntLit(0))))) +//│ IfLike(keyword 'if',None,Block(List(InfixApp(InfixApp(IntLit(1),keyword 'is',Ident(Int)),keyword 'then',IntLit(1)), Modified(keyword 'else',None,IntLit(0))))) fun fact = case 0 then 1 n then n * fact(n - 1) //│ Parsed: -//│ TermDef(Fun,Ident(fact),Some(Case(Block(List(InfixApp(IntLit(0),keyword 'then',IntLit(1)), InfixApp(Ident(n),keyword 'then',App(Ident(*),Tup(List(Ident(n), App(Ident(fact),Tup(List(App(Ident(-),Tup(List(Ident(n), IntLit(1)))))))))))))))) +//│ TermDef(Fun,Ident(fact),Some(Case(None,Block(List(InfixApp(IntLit(0),keyword 'then',IntLit(1)), InfixApp(Ident(n),keyword 'then',App(Ident(*),Tup(List(Ident(n), App(Ident(fact),Tup(List(App(Ident(-),Tup(List(Ident(n), IntLit(1)))))))))))))))) `42 @@ -186,12 +186,12 @@ g`(`1, `2) `if x `== `0.0 then `1.0 else x //│ Parsed: -//│ Quoted(IfLike(keyword 'if',Block(List(InfixApp(Unquoted(Quoted(App(Ident(==),Tup(List(Unquoted(Ident(x)), Unquoted(Quoted(DecLit(0.0)))))))),keyword 'then',Unquoted(Quoted(DecLit(1.0)))), Modified(keyword 'else',None,Unquoted(Ident(x))))))) +//│ Quoted(IfLike(keyword 'if',Some(Loc(1,3,bbSyntax.mls:+187)),Block(List(InfixApp(Unquoted(Quoted(App(Ident(==),Tup(List(Unquoted(Ident(x)), Unquoted(Quoted(DecLit(0.0)))))))),keyword 'then',Unquoted(Quoted(DecLit(1.0)))), Modified(keyword 'else',None,Unquoted(Ident(x))))))) x `=> if 0 == 0 then x else `0 //│ Parsed: -//│ Quoted(InfixApp(Tup(List(Ident(x))),keyword '=>',Unquoted(IfLike(keyword 'if',Block(List(InfixApp(App(Ident(==),Tup(List(IntLit(0), IntLit(0)))),keyword 'then',Ident(x)), Modified(keyword 'else',None,Quoted(IntLit(0))))))))) +//│ Quoted(InfixApp(Tup(List(Ident(x))),keyword '=>',Unquoted(IfLike(keyword 'if',None,Block(List(InfixApp(App(Ident(==),Tup(List(IntLit(0), IntLit(0)))),keyword 'then',Ident(x)), Modified(keyword 'else',None,Quoted(IntLit(0))))))))) region x in 42 //│ Parsed: diff --git a/hkmc2/shared/src/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 66f1898b2..f7c0ea50c 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -20,21 +20,28 @@ do 1 //│ ║ l.18: do 1 //│ ╙── ^ -:todo +:ucs desugared val f = case 0 then "null" do console.log("non-null") 1 then "unit" _ then "other" -//│ ╔══[ERROR] Unrecognized pattern split. -//│ ║ l.26: do console.log("non-null") -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ Desugared: +//│ > if +//│ > caseScrut is 0 then "null" +//│ > let $doTemp = globalThis:import#Prelude#666(.)console‹member:console›(.)log("non-null") +//│ > caseScrut is 1 then "unit" +//│ > else "other" //│ f = [Function (anonymous)] f(0) //│ = 'null' f(1) +//│ > non-null +//│ = 'unit' f(2) +//│ > non-null +//│ = 'other' diff --git a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index 0cfd605bf..3681254b9 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls @@ -16,10 +16,9 @@ f() :sjs fun f(x) = - if x < 0 then + if x < 0 do log("whoops") return 0 - else () x + 1 //│ JS: //│ function f(...args) { diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls b/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls index 06602f6b3..29cf5cd97 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls @@ -6,23 +6,16 @@ let x = -1 //│ x = -1 if x < 0 do set x = 0 -//│ ╔══[PARSE ERROR] Expected end of input; found 'do' keyword instead -//│ ║ l.8: if x < 0 do set x = 0 -//│ ╙── ^^ -//│ ╔══[ERROR] Unrecognized term split (integer literal). -//│ ║ l.8: if x < 0 do set x = 0 -//│ ╙── ^ -//│ ═══[RUNTIME ERROR] Error: match error fun f(x) = if x < 0 return 0 Math.sqrt(x) //│ ╔══[PARSE ERROR] Unexpected 'return' keyword here -//│ ║ l.19: if x < 0 return 0 +//│ ║ l.12: if x < 0 return 0 //│ ╙── ^^^^^^ //│ ╔══[ERROR] Unrecognized term split (integer literal). -//│ ║ l.19: if x < 0 return 0 +//│ ║ l.12: if x < 0 return 0 //│ ╙── ^ @@ -32,10 +25,10 @@ fun hasZeroElement(xs) = Cons(hd, tl) do set xs = tl Nil return false //│ ╔══[PARSE ERROR] Unexpected 'return' keyword here -//│ ║ l.31: Cons(0, tl) return true +//│ ║ l.24: Cons(0, tl) return true //│ ╙── ^^^^^^ //│ ╔══[ERROR] Unrecognized pattern split. -//│ ║ l.31: Cons(0, tl) return true +//│ ║ l.24: Cons(0, tl) return true //│ ╙── ^^^^^^^^^^^ diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index ccace1df0..b897b999d 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -40,7 +40,7 @@ while x then set x = false let x = true //│ x = true -while x then set x = false else () +while x do set x = false let x = true @@ -106,7 +106,7 @@ while log("checking"); i < 3 () => while let i = 0 - i < 10 then set i += 1 + i < 10 do set i += 1 //│ JS: //│ (...args) => { //│ globalThis.Predef.checkArgs("", 0, true, args.length); @@ -120,7 +120,7 @@ while //│ tmp1 = undefined; //│ continue tmp2; //│ } else { -//│ throw new this.Error("match error"); +//│ tmp1 = undefined; //│ } //│ break; //│ } @@ -137,8 +137,15 @@ let i = 0 in let i = 0 in while let _ = log(i) - i < 3 then set i += 1 - else () + i < 3 do set i += 1 +//│ > 0 +//│ > 1 +//│ > 2 +//│ > 3 + +let i = 0 in while + do log(i) + i < 3 do set i += 1 //│ > 0 //│ > 1 //│ > 2 @@ -233,6 +240,16 @@ f(Cons(1, Cons(2, Cons(3, 0)))) //│ > 3 //│ > Done! +fun f(ls) = + while + do print(ls) + ls is Cons(h, tl) do set ls = tl + +f(Cons(1, Cons(2, Cons(3, 0)))) +//│ > Cons(1, Cons(2, Cons(3, 0))) +//│ > Cons(2, Cons(3, 0)) +//│ > Cons(3, 0) +//│ > 0 // ——— FIXME: ——— @@ -240,14 +257,14 @@ f(Cons(1, Cons(2, Cons(3, 0)))) :fixme () => while true then 0 -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(InfixApp(Tup(List()),keyword '=>',IfLike(keyword 'while',BoolLit(true))),keyword 'then',IntLit(0)) (of class hkmc2.syntax.Tree$InfixApp) +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(InfixApp(Tup(List()),keyword '=>',IfLike(keyword 'while',None,BoolLit(true))),keyword 'then',IntLit(0)) (of class hkmc2.syntax.Tree$InfixApp) :fixme while log("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.247: then 0(0) +//│ ║ l.264: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ > Hello World @@ -257,7 +274,7 @@ while log("Hello World"); false while { log("Hello World"), false } then 0(0) else 1 -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',Block(List(App(Ident(log),Tup(List(StrLit(Hello World)))), BoolLit(false)))),keyword 'then',App(IntLit(0),Tup(List(IntLit(0))))) (of class hkmc2.syntax.Tree$InfixApp) +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',None,Block(List(App(Ident(log),Tup(List(StrLit(Hello World)))), BoolLit(false)))),keyword 'then',App(IntLit(0),Tup(List(IntLit(0))))) (of class hkmc2.syntax.Tree$InfixApp) :fixme while @@ -265,6 +282,6 @@ while false then 0(0) else 1 -//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',Block(List(App(Ident(log),Tup(List(StrLit(Hello World)))), BoolLit(false)))),keyword 'then',App(IntLit(0),Tup(List(IntLit(0))))) (of class hkmc2.syntax.Tree$InfixApp) +//│ /!!!\ Uncaught error: scala.MatchError: InfixApp(IfLike(keyword 'while',None,Block(List(App(Ident(log),Tup(List(StrLit(Hello World)))), BoolLit(false)))),keyword 'then',App(IntLit(0),Tup(List(IntLit(0))))) (of class hkmc2.syntax.Tree$InfixApp) diff --git a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls index 61b39cf0b..f483fe174 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/papers/OperatorSplit.mls @@ -24,6 +24,7 @@ fun example(args) = //│ rhs = S of Block of Ls of //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = App: //│ lhs = App: //│ lhs = Ident of "foo" diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls index 3e47aa5f0..2f143a68e 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/And.mls @@ -22,6 +22,7 @@ if x //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = InfixApp: //│ lhs = Ident of "x" @@ -36,6 +37,7 @@ if x //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: @@ -54,6 +56,7 @@ if x is //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = Ident of "x" //│ kw = keyword 'is' @@ -73,6 +76,7 @@ if x is //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = Ident of "x" //│ kw = keyword 'is' @@ -102,6 +106,7 @@ if x is //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = Ident of "x" //│ kw = keyword 'is' @@ -143,6 +148,7 @@ if x is //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = Ident of "x" //│ kw = keyword 'is' @@ -181,6 +187,7 @@ if x is //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = InfixApp: //│ lhs = Ident of "x" //│ kw = keyword 'is' diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls index cce295c71..a1b615e1b 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/ConjunctMatches.mls @@ -18,6 +18,7 @@ if //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: @@ -46,6 +47,7 @@ if //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = Block of Ls of //│ InfixApp: //│ lhs = InfixApp: diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls new file mode 100644 index 000000000..0e42340b3 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls @@ -0,0 +1,109 @@ +:js + +import "../../../mlscript-compile/Option.mls" + +open Option + +// Using `do` will not cause match errors. + +let x = false +//│ x = false + +if x do + print("executed") + set x = false + +x +//│ = false + +if not(x) do + print("executed") +//│ > executed + +:e +:re +if not of x do + print("executed") +//│ ╔══[ERROR] Unrecognized term split (application). +//│ ║ l.25: if not of x do +//│ ║ ^^^^^^^^^^^ +//│ ║ l.26: print("executed") +//│ ╙── ^^^^^^^^^^^^^^^^^^^ +//│ ═══[RUNTIME ERROR] Error: match error + +if (not of x) do + print("executed") + set x = false +//│ > executed + +x +//│ = false + +// Completely using `do` +// ===================== + +fun f(y) = + let x = Some(y) + if x is + Some(0) do set x = None + Some(v) and v % 2 == 0 do set x = Some(v / 2) + x + +f(0) +//│ = None { class: [class None] } + +f(42) +//│ = Some { value: 21 } + +f(41) +//│ = Some { value: 41 } + + +// Mix using `then` and `do` +// ========================= + +:e +fun g(y) = + let x = Some(y) + if x is + Some(0) do set x = None + Some(v) and v % 2 == 0 then set x = Some(v / 2) + x +//│ ╔══[ERROR] Mixed use of `do` and `then` in the `if` expression. +//│ ╟── Keyword `then` is used here. +//│ ║ l.70: Some(v) and v % 2 == 0 then set x = Some(v / 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Keyword `do` is used here. +//│ ║ l.69: Some(0) do set x = None +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ + +g(0) +//│ = None { class: [class None] } + +g(42) +//│ = Some { value: 21 } + +g(41) +//│ = Some { value: 41 } + + +// Completely using `then` +// ======================= + + +fun h(y) = + let x = Some(y) + if x is + Some(0) then set x = None + Some(v) and v % 2 == 0 then set x = Some(v / 2) + x + +h(0) +//│ = None { class: [class None] } + +h(42) +//│ = Some { value: 21 } + +:re +h(41) +//│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls index 6483cfb04..a6ed61443 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls @@ -18,6 +18,7 @@ fun f(x, y, z) = if x then y else z //│ Ident of "z" //│ rhs = S of IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = Block of Ls of //│ InfixApp: //│ lhs = Ident of "x" @@ -74,7 +75,7 @@ fun f(x, y, z) = if x then y else z //│ ╔══[PARSE ERROR] Unexpected 'else' keyword here -//│ ║ l.75: x then y else z +//│ ║ l.76: x then y else z //│ ╙── ^^^^ diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls index 930229246..aea9efee8 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Split.mls @@ -20,6 +20,7 @@ if f(x) == //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = App: //│ lhs = Ident of "==" //│ rhs = Tup of Ls of @@ -54,6 +55,7 @@ if x + //│ Parsed tree: //│ IfLike: //│ kw = keyword 'if' +//│ kwLoc = N //│ split = App: //│ lhs = Ident of "+" //│ rhs = Tup of Ls of