diff --git a/parser/src/main/scala/aqua/parser/head/ModuleExpr.scala b/parser/src/main/scala/aqua/parser/head/ModuleExpr.scala index 3b4be3e4..f2943f90 100644 --- a/parser/src/main/scala/aqua/parser/head/ModuleExpr.scala +++ b/parser/src/main/scala/aqua/parser/head/ModuleExpr.scala @@ -10,8 +10,10 @@ import aqua.parser.lift.Span.{P0ToSpan, PToSpan} import cats.Comonad import cats.parse.Parser +import cats.syntax.applicative.* import cats.syntax.comonad.* import cats.syntax.functor.* +import cats.syntax.option.* import cats.~> case class ModuleExpr[F[_]]( @@ -69,7 +71,7 @@ object ModuleExpr extends HeaderExpr.Companion { comma[NameOrAb[Span.S]](nameOrAb).map(_.toList) private val nameOrAbListOrAll: Parser[Either[List[NameOrAb[Span.S]], Token[Span.S]]] = - nameOrAbList.map(Left(_)) | `star`.lift.map(Token.lift(_)).map(Right(_)) + nameOrAbList.map(Left(_)) | (`star` <* ` *`).lift.map(Token.lift(_)).map(Right(_)) private val moduleWord: Parser[Word[Span.S]] = (`module`.as(Word.Kind.Module).lift.backtrack | @@ -79,7 +81,9 @@ object ModuleExpr extends HeaderExpr.Companion { ( (` *`.with1 *> moduleWord) ~ (` ` *> Ability.dotted) ~ - (` declares ` *> nameOrAbListOrAll).? + (` declares ` *> nameOrAbListOrAll).backtrack + .map(_.some) + .orElse(` *`.as(none)) // Allow trailing spaces ).map { case ((word, name), None) => ModuleExpr(word, name, None, Nil, Nil) diff --git a/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala b/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala index fead0754..dfefbfa3 100644 --- a/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala +++ b/parser/src/test/scala/aqua/parser/head/ModuleSpec.scala @@ -13,17 +13,62 @@ import org.scalatest.matchers.should.Matchers class ModuleSpec extends AnyFlatSpec with Matchers with AquaSpec { import AquaSpec.* - "module header" should "be parsed" in { - ModuleExpr.p.parseAll("aqua MyModule").value.mapK(spanToId) should be( - ModuleExpr( - ModuleExpr.Word[Id](Id(ModuleExpr.Word.Kind.Aqua)), - toAb("MyModule"), - None, - Nil, - Nil - ) + val myModule = ModuleExpr( + ModuleExpr.Word[Id](Id(ModuleExpr.Word.Kind.Aqua)), + toAb("MyModule"), + None, + Nil, + Nil + ) + + val declaresAll = myModule.copy( + declareAll = Some(Token.lift[Id, Unit](())) + ) + + def declares(symbols: List[String]) = + myModule.copy( + declareNames = symbols.filter(_.headOption.exists(_.isLower)).map(toName), + declareCustom = symbols.filter(_.headOption.exists(_.isUpper)).map(toAb) ) + def parseModuleExpr(expr: String): ModuleExpr[Id] = + ModuleExpr.p + .parseAll(expr) + .value + .mapK(spanToId) + + "module expr" should "be parsed" in { + parseModuleExpr("aqua MyModule") should be(myModule) + } + + it should "be parsed with spaces in the end" in { + (0 to 10).foreach(sp => parseModuleExpr("aqua MyModule" + " ".repeat(sp)) should be(myModule)) + } + + it should "be parsed with spaces in the beginning" in { + (0 to 10).foreach(sp => parseModuleExpr(" ".repeat(sp) + "aqua MyModule") should be(myModule)) + } + + it should "be parsed with `declares *`" in { + parseModuleExpr("aqua MyModule declares *") should be(declaresAll) + } + + it should "be parsed with `declares *` with spaces in the end" in { + (0 to 10).foreach(sp => + parseModuleExpr("aqua MyModule declares *" + " ".repeat(sp)) should be(declaresAll) + ) + } + + it should "be parsed with `declares`" in { + List("a", "myFunc", "MyService", "MyAbility", "CONST").inits.takeWhile(_.nonEmpty).foreach { + decl => + parseModuleExpr(s"aqua MyModule declares " + decl.mkString(", ")) should be( + declares(decl) + ) + } + } + + "module header" should "be parsed" in { Header.p .parseAll(s"""aqua MyModule declares * |""".stripMargin) @@ -31,15 +76,7 @@ class ModuleSpec extends AnyFlatSpec with Matchers with AquaSpec { .headers .headOption .get - .mapK(spanToId) should be( - ModuleExpr( - ModuleExpr.Word[Id](Id(ModuleExpr.Word.Kind.Aqua)), - toAb("MyModule"), - Some(Token.lift[Id, Unit](())), - Nil, - Nil - ) - ) + .mapK(spanToId) should be(declaresAll) } }