From 251d1c175dfcbfd8b99e777d65b28ca26e240404 Mon Sep 17 00:00:00 2001 From: Luyu Cheng Date: Wed, 13 Nov 2024 12:48:44 +0800 Subject: [PATCH] Parse and desugar tuple patterns with `...` --- .../scala/hkmc2/semantics/Desugarer.scala | 4 +- .../src/main/scala/hkmc2/syntax/Keyword.scala | 4 + .../src/main/scala/hkmc2/syntax/Lexer.scala | 10 +- .../src/main/scala/hkmc2/syntax/Parser.scala | 7 + .../src/main/scala/hkmc2/syntax/Token.scala | 5 +- .../src/main/scala/hkmc2/syntax/Tree.scala | 3 + .../test/mlscript/ucs/syntax/TupleRest.mls | 127 ++++++++++++++++++ 7 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls diff --git a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala index 47f25711b..e74385b55 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/semantics/Desugarer.scala @@ -410,9 +410,7 @@ class Desugarer(tl: TraceLogger, elaborator: Elaborator) // 2. A variable number of middle patterns indicated by `..`. // 3. A fixed number of trailing patterns. val (lead, rest) = args.foldLeft[(Ls[Tree], Opt[(Opt[Tree], Ls[Tree])])]((Nil, N)): - case ((lead, N), Jux(Ident(".."), pat)) => (lead, S((S(pat), Nil))) - case ((lead, N), App(Ident(".."), TyTup(tys))) => (lead, S((S(Tup(tys)), Nil))) - case ((lead, N), Ident("..")) => (lead, S((N, Nil))) + case ((lead, N), Spread(_, _, patOpt)) => (lead, S((patOpt, Nil))) case ((lead, N), pat) => (lead :+ pat, N) case ((lead, S((rest, last))), pat) => (lead, S((rest, last :+ pat))) // Some helper functions. TODO: deduplicate diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala index 81f7f44f5..aea392e28 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Keyword.scala @@ -57,6 +57,8 @@ object Keyword: val ascPrec = nextPrec // * `x => x : T` should parsed as `x => (x : T)` val `=` = Keyword("=", eqPrec, eqPrec) val `:` = Keyword(":", ascPrec, eqPrec) + val `..` = Keyword("..", N, N) + val `...` = Keyword("...", N, N) // val `;` = Keyword(";", ascPrec, eqPrec) val `if` = Keyword("if", N, nextPrec) @@ -121,6 +123,8 @@ object Keyword: type Infix = `and`.type | `or`.type | `then`.type | `else`.type | `is`.type | `:`.type | `->`.type | `=>`.type | `extends`.type | `restricts`.type | `as`.type + + type Ellipsis = `...`.type | `..`.type type letLike = `let`.type | `set`.type diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala index 9130c84da..c6d4af4f5 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Lexer.scala @@ -194,7 +194,8 @@ class Lexer(origin: Origin, dbg: Bool)(using raise: Raise): def loc(start: Int, end: Int): Loc = Loc(start, end, origin) def mkSymIdent(nme: Str) = nme match - case "..." => SUSPENSION + case ".." => SUSPENSION(false) + case "..." => SUSPENSION(true) case _ => IDENT(nme, true) @tailrec final @@ -369,9 +370,9 @@ class Lexer(origin: Origin, dbg: Bool)(using raise: Raise): import BracketKind._ def go(toks: Ls[Token -> Loc], canStartAngles: Bool, stack: Ls[BracketKind -> Loc -> Ls[Stroken -> Loc]], acc: Ls[Stroken -> Loc]): Ls[Stroken -> Loc] = toks match - case (SUSPENSION, l0) :: Nil => + case (SUSPENSION(true), l0) :: Nil => go(OPEN_BRACKET(Indent) -> l0 :: LITVAL(Tree.UnitLit(true)) -> l0 :: Nil, false, stack, acc) - case (SUSPENSION, l0) :: (NEWLINE, l1) :: rest => + case (SUSPENSION(true), l0) :: (NEWLINE, l1) :: rest => go(OPEN_BRACKET(Indent) -> (l0 ++ l1) :: rest, false, stack, acc) case (QUOTE, l0) :: (IDENT("<", true), l1) :: rest => go(rest, false, stack, (IDENT("<", true), l1) :: (QUOTE, l0) :: acc) @@ -526,7 +527,8 @@ object Lexer: case (BRACKETS(k, contents), _) => k.beg + printTokens(contents) + k.end case (COMMENT(text: String), _) => "/*" + text + "*/" - case (SUSPENSION, _) => "..." + case (SUSPENSION(true), _) => "..." + case (SUSPENSION(false), _) => ".." def printTokens(ts: Ls[TokLoc]): Str = ts.iterator.map(printToken).mkString("|", "|", "|") diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala index 0b37b50db..d464d94e7 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Parser.scala @@ -14,6 +14,7 @@ import scala.annotation.tailrec import Keyword.`let` import hkmc2.syntax.ParseRule.prefixRules import hkmc2.syntax.ParseRule.infixRules +import hkmc2.syntax.Keyword.Ellipsis object Parser: @@ -563,6 +564,12 @@ abstract class Parser( case (BRACKETS(Indent | Curly, _), loc) :: _ => err((msg"Expected an expression; found block instead" -> lastLoc :: Nil)) errExpr + case (SUSPENSION(dotDotDot), loc) :: _ => + consume + val bod = yeetSpaces match + case Nil | (COMMA, _) :: _ => N + case _ => S(simpleExprImpl(prec)) + Spread(if dotDotDot then Keyword.`...` else Keyword.`..`, S(loc), bod) case (tok, loc) :: _ => TODO(tok) case Nil => diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala index 0bae7a329..dcbf786ce 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Token.scala @@ -27,7 +27,8 @@ sealed abstract class Token: case BRACKETS(BracketKind.Indent, contents) => s"indented block" case BRACKETS(k, contents) => s"${k.name} section" case COMMENT(text) => "comment" - case SUSPENSION => "'...' suspension" + case SUSPENSION(true) => "'...' ellipsis" + case SUSPENSION(false) => "'..' ellipsis" /** Type of 'Structured Tokens' aka 'Strokens', * which use a `BRACKETS` construct instead of `OPEN_BRACKET`/`CLOSE_BRACKET` and `INDENT`/`DEINDENT` */ @@ -49,7 +50,7 @@ final case class OPEN_BRACKET(k: BracketKind) extends Token final case class CLOSE_BRACKET(k: BracketKind) extends Token final case class BRACKETS(k: BracketKind, contents: Ls[Stroken -> Loc])(val innerLoc: Loc) extends Token with Stroken final case class COMMENT(text: String) extends Token with Stroken -object SUSPENSION extends Token with Stroken +final case class SUSPENSION(dotDotDot: Bool) extends Token with Stroken sealed abstract class BracketKind: diff --git a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala index 9ff1c96fc..cfffdaa11 100644 --- a/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala +++ b/hkmc2/shared/src/main/scala/hkmc2/syntax/Tree.scala @@ -71,6 +71,7 @@ enum Tree extends AutoLocated: case Region(name: Tree, body: Tree) case RegRef(reg: Tree, value: Tree) case Effectful(eff: Tree, body: Tree) + case Spread(kw: Keyword.Ellipsis, kwLoc: Opt[Loc], body: Opt[Tree]) def children: Ls[Tree] = this match case _: Empty | _: Error | _: Ident | _: Literal => Nil @@ -102,6 +103,7 @@ enum Tree extends AutoLocated: case Sel(prefix, name) => prefix :: Nil case Open(bod) => bod :: Nil case Def(lhs, rhs) => lhs :: rhs :: Nil + case Spread(_, _, body) => body.toList def describe: Str = this match case Empty() => "empty" @@ -135,6 +137,7 @@ enum Tree extends AutoLocated: case Effectful(eff, body) => "effectful" case Handle(_, _, _, _) => "handle" case Def(lhs, rhs) => "defining assignment" + case Spread(_, _, _) => "spread" def showDbg: Str = toString // TODO diff --git a/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls new file mode 100644 index 000000000..e30bb660d --- /dev/null +++ b/hkmc2/shared/src/test/mlscript/ucs/syntax/TupleRest.mls @@ -0,0 +1,127 @@ +:parseOnly +:pt + +fun f(xs) = if xs is + [..xs] then 0 + [...xs] then 1 + [..[]] then 2 + [...[]] then 3 + [..Cons(x, xs)] then 4 + [...Cons(x, xs)] then 5 + [..] then 6 + [...] then 7 + [.., x] then 8 + [..., x] then 9 + [... , x] then 10 +//│ Parsed tree: +//│ TermDef: +//│ k = Fun +//│ head = App: +//│ lhs = Ident of "f" +//│ rhs = Tup of Ls of +//│ Ident of "xs" +//│ rhs = S of IfLike: +//│ kw = keyword 'if' +//│ split = InfixApp: +//│ lhs = Ident of "xs" +//│ kw = keyword 'is' +//│ rhs = Block of Ls of +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '..' +//│ kwLoc = S of Loc at :2:4-2:6 +//│ body = S of Ident of "xs" +//│ kw = keyword 'then' +//│ rhs = IntLit of 0 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :3:4-3:7 +//│ body = S of Ident of "xs" +//│ kw = keyword 'then' +//│ rhs = IntLit of 1 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '..' +//│ kwLoc = S of Loc at :4:4-4:6 +//│ body = S of Tup of Nil +//│ kw = keyword 'then' +//│ rhs = IntLit of 2 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :5:4-5:7 +//│ body = S of Tup of Nil +//│ kw = keyword 'then' +//│ rhs = IntLit of 3 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '..' +//│ kwLoc = S of Loc at :6:4-6:6 +//│ body = S of App: +//│ lhs = Ident of "Cons" +//│ rhs = Tup of Ls of +//│ Ident of "x" +//│ Ident of "xs" +//│ kw = keyword 'then' +//│ rhs = IntLit of 4 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :7:4-7:7 +//│ body = S of App: +//│ lhs = Ident of "Cons" +//│ rhs = Tup of Ls of +//│ Ident of "x" +//│ Ident of "xs" +//│ kw = keyword 'then' +//│ rhs = IntLit of 5 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '..' +//│ kwLoc = S of Loc at :8:4-8:6 +//│ body = N +//│ kw = keyword 'then' +//│ rhs = IntLit of 6 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :9:4-9:7 +//│ body = N +//│ kw = keyword 'then' +//│ rhs = IntLit of 7 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '..' +//│ kwLoc = S of Loc at :10:4-10:6 +//│ body = N +//│ Ident of "x" +//│ kw = keyword 'then' +//│ rhs = IntLit of 8 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :11:4-11:7 +//│ body = N +//│ Ident of "x" +//│ kw = keyword 'then' +//│ rhs = IntLit of 9 +//│ InfixApp: +//│ lhs = Tup of Ls of +//│ Spread: +//│ kw = keyword '...' +//│ kwLoc = S of Loc at :12:4-12:7 +//│ body = N +//│ Ident of "x" +//│ kw = keyword 'then' +//│ rhs = IntLit of 10