diff --git a/.gitignore b/.gitignore index 3688df3e61..c919276342 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ build/ out/ project/target/ +project/project target/ drafts/ node_modules/ @@ -12,6 +13,7 @@ licenses/ .bsp/ bin .metals/ +project/metals.sbt .vscode/ .bloop/ *.tgz diff --git a/effekt/jvm/src/test/scala/effekt/ParserTests.scala b/effekt/jvm/src/test/scala/effekt/ParserTests.scala index bb0527258d..174c7b1b8a 100644 --- a/effekt/jvm/src/test/scala/effekt/ParserTests.scala +++ b/effekt/jvm/src/test/scala/effekt/ParserTests.scala @@ -623,6 +623,24 @@ class ParserTests extends munit.FunSuite { parseMatchPattern("_"), IgnorePattern(Span.missing)) parseMatchPattern("Cons(x, Cons(x, Nil()))") + parseMatchPattern("[]") + parseMatchPattern("[1]") + parseMatchPattern("[_, 2, (3, 4)]") + parseMatchPattern("[[1, 2], [3, [4, (6, [7, 8])]]]") + parseMatchPattern("[x, [..y], [z, ..v]]") + + { + def cons(head: MatchPattern, tail: MatchPattern) = + TagPattern(IdRef(Nil, "Cons", Span.missing), List(head, tail), Span.missing) + def nil = TagPattern(IdRef(Nil, "Nil", Span.missing), Nil, Span.missing) + def lit(l: Int) = + LiteralPattern(Literal(l, symbols.builtins.TInt, Span.missing), Span.missing) + + assertEqualModuloSpans( + parseMatchPattern("[1, [2, 3, 4], [5]]"), + cons(lit(1), cons(cons(lit(2), cons(lit(3), cons(lit(4), nil))), cons(cons(lit(5), nil), nil))) + ) + } { val (source, pos) = diff --git a/effekt/shared/src/main/scala/effekt/Parser.scala b/effekt/shared/src/main/scala/effekt/Parser.scala index d73a164ed4..be717a8ef6 100644 --- a/effekt/shared/src/main/scala/effekt/Parser.scala +++ b/effekt/shared/src/main/scala/effekt/Parser.scala @@ -1005,22 +1005,54 @@ class Parser(tokens: Seq[Token], source: Source) { nonterminal: peek.kind match { case `__` => skip(); IgnorePattern(span()) - case _ if isVariable => - idRef() match { - case id if peek(`(`) => TagPattern(id, many(matchPattern, `(`, `,`, `)`).unspan, span()) - case IdRef(Nil, name, span) => AnyPattern(IdDef(name, span), span) - case IdRef(_, name, _) => fail("Cannot use qualified names to bind a pattern variable") - } - case _ if isVariable => - AnyPattern(idDef(), span()) + case _ if isVariable => idRef() match { + case id if peek(`(`) => TagPattern(id, many(matchPattern, `(`, `,`, `)`).unspan, span()) + case IdRef(Nil, name, span) => AnyPattern(IdDef(name, span), span) + case IdRef(_, name, _) => fail("Cannot use qualified names to bind a pattern variable") + } case _ if isLiteral => LiteralPattern(literal(), span()) case `(` => some(matchPattern, `(`, `,`, `)`) match { case Many(p :: Nil , _) => fail("Pattern matching on tuples requires more than one element") case Many(ps, _) => TagPattern(IdRef(List("effekt"), s"Tuple${ps.size}", span().synthesized), ps, span()) } + case `[` => listPattern() case k => fail("pattern", k) } + private def listPattern(): MatchPattern = { + val components: ListBuffer[MatchPattern] = ListBuffer.empty + var lastPattern = NilPattern + + consume(`[`) + peek.kind match { + case `]` | `,` | `..` => ; + case _ => components += matchPattern() + } + + while (peek(`,`)) { + consume(`,`) + if(!peek(`]`) && !peek(`..`)) { + components += matchPattern() + } + } + if (peek(`..`)) { + consume(`..`) + lastPattern = peek.kind match { + case `]` => IgnorePattern(span()) + case _ => idDef() match { case id: IdDef => AnyPattern(id, id.span) } + } + } + consume(`]`) + + components.toList.foldRight(lastPattern)(ConsPattern) + } + + private def NilPattern: MatchPattern = + TagPattern(IdRef(Nil, "Nil", span().synthesized), Nil, span()) + + private def ConsPattern(head: MatchPattern, tail: MatchPattern): MatchPattern = + TagPattern(IdRef(Nil, "Cons", span().synthesized), List(head, tail), span()) + def matchExpr(scrutinee: Term): Term = val clauses = `match` ~> braces { manyWhile(matchClause(), `case`) } val default = when(`else`) { Some(stmt()) } { None } @@ -1891,27 +1923,20 @@ class Parser(tokens: Seq[Token], source: Source) { inline def manyTrailing[T](p: () => T, before: TokenKind, sep: TokenKind, after: TokenKind): List[T] = + val components: ListBuffer[T] = ListBuffer.empty consume(before) - if (peek(after)) { - consume(after) - Nil - } else if (peek(sep)) { - consume(sep) - consume(after) - Nil - } else { - val components: ListBuffer[T] = ListBuffer.empty + if (!peek(after) && !peek(sep)) { components += p() - while (peek(sep)) { - consume(sep) - - if (!peek(after)) { - components += p() - } + } + + while (peek(sep)) { + consume(sep) + if(!peek(after)) { + components += p() } - consume(after) - components.toList } + consume(after) + components.toList // Positions diff --git a/examples/neg/parsing/invalid-lit-list-pattern.check b/examples/neg/parsing/invalid-lit-list-pattern.check new file mode 100644 index 0000000000..6ebb122d6d --- /dev/null +++ b/examples/neg/parsing/invalid-lit-list-pattern.check @@ -0,0 +1,3 @@ +[error] examples/neg/parsing/invalid-lit-list-pattern.effekt:3:17: Expected ] but got ) + case [1, [2, 3)] => () + ^ \ No newline at end of file diff --git a/examples/neg/parsing/invalid-lit-list-pattern.effekt b/examples/neg/parsing/invalid-lit-list-pattern.effekt new file mode 100644 index 0000000000..96a0d0e27c --- /dev/null +++ b/examples/neg/parsing/invalid-lit-list-pattern.effekt @@ -0,0 +1,5 @@ +val _ = [] match { + case [] => () + case [1, [2, 3)] => () + case _ => () +} \ No newline at end of file diff --git a/examples/neg/parsing/tail-pattern-too-early.check b/examples/neg/parsing/tail-pattern-too-early.check new file mode 100644 index 0000000000..5eb6cc5bf0 --- /dev/null +++ b/examples/neg/parsing/tail-pattern-too-early.check @@ -0,0 +1,3 @@ +[error] examples/neg/parsing/tail-pattern-too-early.effekt:2:12: Expected ] but got , + case [..x, y] => 3 + ^ \ No newline at end of file diff --git a/examples/neg/parsing/tail-pattern-too-early.effekt b/examples/neg/parsing/tail-pattern-too-early.effekt new file mode 100644 index 0000000000..bd2385455c --- /dev/null +++ b/examples/neg/parsing/tail-pattern-too-early.effekt @@ -0,0 +1,3 @@ +val _ = [1, 2] match { + case [..x, y] => 3 +} \ No newline at end of file diff --git a/examples/pos/patternmatching/matching-list.check b/examples/pos/patternmatching/matching-list.check new file mode 100644 index 0000000000..e4cdce2261 --- /dev/null +++ b/examples/pos/patternmatching/matching-list.check @@ -0,0 +1,13 @@ +hello +4 +Cons(5, Cons(6, Nil())) +don't care +Nil() +--- +world +world +helloSome(4)5 +--- +42 +--- +6 diff --git a/examples/pos/patternmatching/matching-list.effekt b/examples/pos/patternmatching/matching-list.effekt new file mode 100644 index 0000000000..712cb6a00b --- /dev/null +++ b/examples/pos/patternmatching/matching-list.effekt @@ -0,0 +1,43 @@ +def ex1(lst: List[Int]) = lst match { + case [1, 2] => "hello" + case [42, x, 2] => x.show + case [3, 4, ..y] => y.show + case [_, ..] => "don't care" + case [..x] => x.show +} + +def ex2(lst: List[Option[Int]]) = { + if(lst is [head, Some(x)]) { + "hello" ++ head.show ++ x.show + } else { + "world" + } +} + +def ex3(lst: List[List[Int]]) = { + val [fst, [_, x]] = lst else panic("Ohno") + x +} + +def foo(lst: List[Int]) { f: List[Int] => Int } = f(lst) + +def ex4(lst: List[Int]) = foo(lst) { + case [a, b, c] => a + b + c + case _ => panic("Ohno") +} + +def main() = { + println(ex1([1, 2])) + println(ex1([42, 4, 2])) + println(ex1([3, 4, 5, 6])) + println(ex1([44, 444])) + println(ex1([])) + println("---") + println(ex2([Some(2), None()])) + println(ex2([Some(2), Some(3), Some(3)])) + println(ex2([Some(4), Some(5)])) + println("---") + println(ex3([[3], [2, 42]])) + println("---") + println(ex4([1, 2, 3])) +} \ No newline at end of file