From 5702bde258af3bc548d3685f8fa07bcf3bb485c3 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 01:18:11 +0800 Subject: [PATCH 1/6] Support the use of `do` as a connective in UCS --- .../scala/hkmc2/semantics/Desugarer.scala | 70 ++++++++++---- .../scala/hkmc2/semantics/Elaborator.scala | 13 ++- .../src/main/scala/hkmc2/syntax/Keyword.scala | 9 +- .../main/scala/hkmc2/syntax/ParseRule.scala | 9 +- .../src/main/scala/hkmc2/syntax/Parser.scala | 2 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 14 +-- .../src/test/mlscript/ucs/syntax/Do.mls | 94 +++++++++++++++++++ 7 files changed, 171 insertions(+), 40 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala index b3cc92ad58..2552e23910 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") @@ -499,4 +517,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 d612056e4c..20103a46ae 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 5c1e680a29..cf7ce40059 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, curPrec) + val `do` = Keyword("do", thenPrec, curPrec) + 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 8ce2ae86c7..5f33974028 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 f9b88ec9ae..1784f1f7fa 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 => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 8cc4ca76a8..2c65abffda 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/ucs/syntax/Do.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls new file mode 100644 index 0000000000..6390900a59 --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls @@ -0,0 +1,94 @@ +: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 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.55: Some(v) and v % 2 == 0 then set x = Some(v / 2) +//│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╟── Keyword `do` is used here. +//│ ║ l.54: 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 From 84320e32818ac9c17f07b77a986157fbf45b7acd Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 11:34:38 +0800 Subject: [PATCH 2/6] Update Keyword.scala Co-authored-by: Lionel Parreaux --- hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index cf7ce40059..d0757fdd95 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -65,8 +65,8 @@ object Keyword: val `while` = Keyword("while", N, curPrec) val thenPrec = nextPrec - val `then` = Keyword("then", thenPrec, curPrec) - val `do` = Keyword("do", thenPrec, curPrec) + val `then` = Keyword("then", thenPrec, thenPrec) + val `do` = Keyword("do", thenPrec, thenPrec) val `else` = Keyword("else", nextPrec, curPrec) val `case` = Keyword("case", N, N) From 2c9be33874149494c41171d980423447cb5ce9ac Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 16:24:01 +0800 Subject: [PATCH 3/6] Add updated test files --- .../src/test/mlscript/basics/OpBlocks.mls | 15 ++++++------ .../src/test/mlscript/bbml/bbSyntax.mls | 18 +++++++-------- hkmc2/shared/src/test/mlscript/codegen/Do.mls | 23 ++++++++++++++++--- .../codegen/ImpreativeConditionals.mls | 15 ++++-------- .../src/test/mlscript/codegen/While.mls | 6 ++--- .../mlscript/ucs/papers/OperatorSplit.mls | 1 + .../src/test/mlscript/ucs/syntax/And.mls | 7 ++++++ .../mlscript/ucs/syntax/ConjunctMatches.mls | 2 ++ .../src/test/mlscript/ucs/syntax/Else.mls | 3 ++- .../src/test/mlscript/ucs/syntax/Split.mls | 2 ++ 10 files changed, 58 insertions(+), 34 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls b/hkmc2/shared/src/test/mlscript/basics/OpBlocks.mls index aa342d9bfb..a2e1ac2578 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 0f86191596..4b83e5a34d 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 66f1898b21..5972d77d21 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -26,15 +26,32 @@ val f = case do console.log("non-null") 1 then "unit" _ then "other" -//│ ╔══[ERROR] Unrecognized pattern split. +//│ ╔══[ERROR] Unrecognized pattern. +//│ ║ l.25: 0 then "null" +//│ ╙── ^^^^^^^^^^^^^ +//│ ╔══[ERROR] Mixed use of `do` and `then` in the `case` expression. +//│ ╟── Keyword `then` is used here. +//│ ║ l.27: 1 then "unit" +//│ ║ ^^^^^^^^^^^^^ +//│ ╟── Keyword `do` is used here. +//│ ║ l.25: 0 then "null" +//│ ║ ^^^^^^^^^^^^^ //│ ║ l.26: do console.log("non-null") -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +//│ ╔══[ERROR] The following branches are unreachable. +//│ ╟── Because the previous split is full. +//│ ║ l.27: 1 then "unit" +//│ ║ ^^^^^^^^^^^^^ +//│ ║ l.28: _ then "other" +//│ ╙── ^^^^^^^^^^^^^^^^ //│ f = [Function (anonymous)] f(0) -//│ = 'null' +//│ = 'other' f(1) +//│ = 'unit' f(2) +//│ = 'other' diff --git a/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls b/hkmc2/shared/src/test/mlscript/codegen/ImpreativeConditionals.mls index 06602f6b32..29cf5cd970 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 bd7cb95f03..d3ea5c75ba 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -240,7 +240,7 @@ 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 @@ -256,7 +256,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 @@ -264,6 +264,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 61b39cf0bf..f483fe174d 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 3e47aa5f03..2f143a68e9 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 cce295c717..a1b615e1b1 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/Else.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Else.mls index 6483cfb04d..a6ed61443c 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 930229246e..aea9efee87 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 From 006ce44e30031b08e0f79b9b14a404a75f920322 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 17:02:17 +0800 Subject: [PATCH 4/6] Support `do` for interleaved computations --- .../scala/hkmc2/semantics/Desugarer.scala | 18 +++++++++++ .../src/main/scala/hkmc2/syntax/Parser.scala | 6 ++++ hkmc2/shared/src/test/mlscript/codegen/Do.mls | 30 +++++++------------ 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala index 2552e23910..1914f1ab5e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala @@ -184,6 +184,12 @@ class Desugarer(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" @@ -259,6 +265,12 @@ class Desugarer(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)) @@ -340,6 +352,12 @@ class Desugarer(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}" diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 1784f1f7fa..e0bd732c3e 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -900,6 +900,12 @@ abstract class Parser( case (NEWLINE, _) :: (KEYWORD(kw), _) :: _ if kw.canStartInfixOnNewLine && kw.leftPrecOrMin > prec && infixRules.kwAlts.contains(kw.name) + && kw != 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/test/mlscript/codegen/Do.mls b/hkmc2/shared/src/test/mlscript/codegen/Do.mls index 5972d77d21..f7c0ea50cd 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/Do.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/Do.mls @@ -20,38 +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. -//│ ║ l.25: 0 then "null" -//│ ╙── ^^^^^^^^^^^^^ -//│ ╔══[ERROR] Mixed use of `do` and `then` in the `case` expression. -//│ ╟── Keyword `then` is used here. -//│ ║ l.27: 1 then "unit" -//│ ║ ^^^^^^^^^^^^^ -//│ ╟── Keyword `do` is used here. -//│ ║ l.25: 0 then "null" -//│ ║ ^^^^^^^^^^^^^ -//│ ║ l.26: do console.log("non-null") -//│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -//│ ╔══[ERROR] The following branches are unreachable. -//│ ╟── Because the previous split is full. -//│ ║ l.27: 1 then "unit" -//│ ║ ^^^^^^^^^^^^^ -//│ ║ l.28: _ then "other" -//│ ╙── ^^^^^^^^^^^^^^^^ +//│ 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) -//│ = 'other' +//│ = 'null' f(1) +//│ > non-null //│ = 'unit' f(2) +//│ > non-null //│ = 'other' From 910b671c254c19aa439bd62a537e26562d471d32 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 4 Dec 2024 17:03:52 +0800 Subject: [PATCH 5/6] Update tests that need the `do` keyword --- hkmc2/shared/src/test/mlscript-compile/Predef.mls | 3 +-- .../src/test/mlscript-compile/apps/Accounting.mls | 3 +-- .../shared/src/test/mlscript/codegen/EarlyReturn.mls | 3 +-- hkmc2/shared/src/test/mlscript/codegen/While.mls | 11 +++++------ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/hkmc2/shared/src/test/mlscript-compile/Predef.mls b/hkmc2/shared/src/test/mlscript-compile/Predef.mls index c3d63e1929..72e8be49e9 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 () class Test with val y = 1 diff --git a/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls b/hkmc2/shared/src/test/mlscript-compile/apps/Accounting.mls index 3197637e9a..3e7aaeea34 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/codegen/EarlyReturn.mls b/hkmc2/shared/src/test/mlscript/codegen/EarlyReturn.mls index 0cfd605bf4..3681254b93 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/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index d3ea5c75ba..9b18301995 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,7 @@ 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 @@ -247,7 +246,7 @@ while log("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.247: then 0(0) +//│ ║ l.246: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ ═══[RUNTIME ERROR] Error: match error From fe1085dc41eb4e07e6ac5aebbf6ce1b1ae02a3a3 Mon Sep 17 00:00:00 2001 From: Lionel Parreaux Date: Wed, 4 Dec 2024 20:24:06 +0800 Subject: [PATCH 6/6] Add a couple of test cases + small change --- .../src/main/scala/hkmc2/syntax/Parser.scala | 2 +- .../src/test/mlscript/codegen/While.mls | 20 ++++++++++++++++++- .../src/test/mlscript/ucs/syntax/Do.mls | 19 ++++++++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index e0bd732c3e..7a6ba4326c 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -900,7 +900,7 @@ abstract class Parser( case (NEWLINE, _) :: (KEYWORD(kw), _) :: _ if kw.canStartInfixOnNewLine && kw.leftPrecOrMin > prec && infixRules.kwAlts.contains(kw.name) - && kw != Keyword.`do` // This is to avoid the following case: + && (kw isnt Keyword.`do`) // This is to avoid the following case: // ``` // 0 then "null" // do console.log("non-null") diff --git a/hkmc2/shared/src/test/mlscript/codegen/While.mls b/hkmc2/shared/src/test/mlscript/codegen/While.mls index 9b18301995..249d239f9b 100644 --- a/hkmc2/shared/src/test/mlscript/codegen/While.mls +++ b/hkmc2/shared/src/test/mlscript/codegen/While.mls @@ -143,6 +143,14 @@ let i = 0 in while //│ > 2 //│ > 3 +let i = 0 in while + do log(i) + i < 3 do set i += 1 +//│ > 0 +//│ > 1 +//│ > 2 +//│ > 3 + // * Note that the semantics of UCS-while is quite subtle. // * Currently, we only treat the *top-level* `else` as terminating the loop; @@ -232,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: ——— @@ -246,7 +264,7 @@ while log("Hello World"); false then 0(0) else 1 //│ ╔══[PARSE ERROR] Unexpected 'then' keyword here -//│ ║ l.246: then 0(0) +//│ ║ l.264: then 0(0) //│ ╙── ^^^^ //│ ═══[ERROR] Unrecognized term split (false literal). //│ ═══[RUNTIME ERROR] Error: match error diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls index 6390900a59..0e42340b35 100644 --- a/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/Do.mls @@ -16,6 +16,21 @@ if x do 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 @@ -56,10 +71,10 @@ fun g(y) = x //│ ╔══[ERROR] Mixed use of `do` and `then` in the `if` expression. //│ ╟── Keyword `then` is used here. -//│ ║ l.55: Some(v) and v % 2 == 0 then set x = Some(v / 2) +//│ ║ l.70: Some(v) and v % 2 == 0 then set x = Some(v / 2) //│ ║ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ //│ ╟── Keyword `do` is used here. -//│ ║ l.54: Some(0) do set x = None +//│ ║ l.69: Some(0) do set x = None //│ ╙── ^^^^^^^^^^^^^^^^^^^^^^^ g(0)