diff --git a/aqua-src/import-empty.aqua b/aqua-src/import-empty.aqua new file mode 100644 index 00000000..ede10f3c --- /dev/null +++ b/aqua-src/import-empty.aqua @@ -0,0 +1,9 @@ +-- import.aqua +module Import.Test +import foobar from "export.aqua" + +use foo as f from "export.aqua" as Exp + +use "export.aqua" + +export foobar as barfoo \ No newline at end of file diff --git a/backend/.js/src/main/scala/aqua/backend/Version.scala b/backend/.js/src/main/scala/aqua/backend/Version.scala index ebe28fc3..fba5f4bb 100644 --- a/backend/.js/src/main/scala/aqua/backend/Version.scala +++ b/backend/.js/src/main/scala/aqua/backend/Version.scala @@ -1,7 +1,5 @@ package aqua.backend object Version { - - // TODO: get version for JS compiler - lazy val version = "Unknown (JS)" + lazy val version = BuildInfo.version } diff --git a/backend/.jvm/src/main/scala/aqua/backend/Version.scala b/backend/.jvm/src/main/scala/aqua/backend/Version.scala index 0468e0a8..e274d616 100644 --- a/backend/.jvm/src/main/scala/aqua/backend/Version.scala +++ b/backend/.jvm/src/main/scala/aqua/backend/Version.scala @@ -2,7 +2,5 @@ package aqua.backend object Version { - lazy val version = Option(getClass.getPackage.getImplementationVersion) - .filter(_.nonEmpty) - .getOrElse("Unknown") + lazy val version = BuildInfo.version } diff --git a/build.sbt b/build.sbt index cb016350..99182168 100644 --- a/build.sbt +++ b/build.sbt @@ -146,6 +146,11 @@ lazy val backend = crossProject(JVMPlatform, JSPlatform) .crossType(CrossType.Pure) .in(file("backend")) .settings(commons: _*) + .enablePlugins(BuildInfoPlugin) + .settings( + buildInfoKeys := Seq[BuildInfoKey](version), + buildInfoPackage := "aqua.backend" + ) .dependsOn(transform) lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform) diff --git a/cli/.jvm/src/main/scala/aqua/Test.scala b/cli/.jvm/src/main/scala/aqua/Test.scala index e6065358..949abb6d 100644 --- a/cli/.jvm/src/main/scala/aqua/Test.scala +++ b/cli/.jvm/src/main/scala/aqua/Test.scala @@ -12,8 +12,9 @@ object Test extends IOApp.Simple { implicit val aio: AquaIO[IO] = new AquaFilesIO[IO] override def run: IO[Unit] = - IO.println("Start ms: " + System.currentTimeMillis()) *> - AquaPathCompiler + for { + start <- IO(System.currentTimeMillis()) + _ <- AquaPathCompiler .compileFilesTo[IO]( Path("./aqua-src"), List(Path("./aqua")), @@ -26,6 +27,8 @@ object Test extends IOApp.Simple { errs.map(System.err.println): Unit case Validated.Valid(res) => res.map(println): Unit - } <* IO.println("End ms : " + System.currentTimeMillis()) + } + _ <- IO.println("Compilation ends in : " + (System.currentTimeMillis() - start) + " ms") + } yield () } diff --git a/parser/src/main/scala/aqua/parser/Parser.scala b/parser/src/main/scala/aqua/parser/Parser.scala index a6f4580f..02d3a2a2 100644 --- a/parser/src/main/scala/aqua/parser/Parser.scala +++ b/parser/src/main/scala/aqua/parser/Parser.scala @@ -2,14 +2,16 @@ package aqua.parser import cats.data.{Validated, ValidatedNec} import aqua.parser.Ast +import aqua.parser.Ast.Tree import aqua.parser.ParserError import aqua.parser.LexerError import aqua.parser.expr.RootExpr import aqua.parser.head.HeadExpr +import aqua.parser.lexer.Token import aqua.parser.lift.{FileSpan, LiftParser, Span} import cats.{Comonad, Eval, ~>} import cats.parse.LocationMap -import cats.parse.Parser0 as P0 +import cats.parse.{Parser as P, Parser0 as P0} import cats.Id import aqua.parser.lift.LiftParser.LiftErrorOps @@ -22,7 +24,7 @@ object Parser { lazy val idParser = parserSchema[Id]() def parserSchema[S[_] : LiftParser : Comonad](): P0[ValidatedNec[ParserError[S], Ast[S]]] = - (HeadExpr.ast[S].with1 ~ RootExpr.ast[S]()).map { case (head, bodyMaybe) => + (HeadExpr.ast[S] ~ RootExpr.ast0[S]()).map { case (head, bodyMaybe) => bodyMaybe.map(Ast(head, _)) } diff --git a/parser/src/main/scala/aqua/parser/expr/RootExpr.scala b/parser/src/main/scala/aqua/parser/expr/RootExpr.scala index d1c0d664..90ec919f 100644 --- a/parser/src/main/scala/aqua/parser/expr/RootExpr.scala +++ b/parser/src/main/scala/aqua/parser/expr/RootExpr.scala @@ -2,13 +2,13 @@ package aqua.parser.expr import aqua.parser.Ast.Tree import aqua.parser.lexer.Token -import aqua.parser.lexer.Token._ +import aqua.parser.lexer.Token.* import aqua.parser.lift.LiftParser -import aqua.parser.lift.LiftParser._ +import aqua.parser.lift.LiftParser.* import aqua.parser.{Expr, ParserError} -import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} +import cats.data.{Chain, NonEmptyChain, NonEmptyList, Validated, ValidatedNec} import cats.free.Cofree -import cats.parse.{Parser => P} +import cats.parse.{Parser0 as P0, Parser as P} import cats.{Comonad, Eval} import cats.~> @@ -23,16 +23,38 @@ object RootExpr extends Expr.Companion { def validChildren: List[Expr.Lexem] = ServiceExpr :: AliasExpr :: DataStructExpr :: ConstantExpr :: FuncExpr :: Nil + private def gatherResults[F[_]: LiftParser: Comonad](results: NonEmptyList[ValidatedNec[ParserError[F], Tree[F]]]): (Chain[ParserError[F]], Chain[Tree[F]]) = { + results.foldLeft[(Chain[ParserError[F]], Chain[Tree[F]])](Chain.empty -> Chain.empty) { + case ((errs, trees), Validated.Valid(tree)) => (errs, trees :+ tree) + case ((errs, trees), Validated.Invalid(err)) => (errs ++ err.toChain, trees) + } + } + + private def linesParser[F[_]: LiftParser: Comonad](): P[NonEmptyList[ValidatedNec[ParserError[F], Tree[F]]]] = + P.repSep( + P.oneOf(RootExpr.validChildren.map(_.ast[F]())), + ` \n+` + ).surroundedBy(` \n+`.?) + + private def rootToken[F[_]: LiftParser: Comonad]: P0[Token[F]] = + P.unit.lift0.map(Token.lift[F, Unit](_)) + + private def parserSchema[F[_]: LiftParser: Comonad](): P[(Token[F], (Chain[ParserError[F]], Chain[Tree[F]]))] = + rootToken.with1 ~ + linesParser().map(l => gatherResults(l)) + + def empty[F[_] : LiftParser : Comonad](): P0[ValidatedNec[ParserError[F], Tree[F]]] = + (rootToken <* (Token.` \n*` *> Token.` `.? *> P.end)) + .map(point => Validated.validNec(Cofree(RootExpr[F](point), Eval.now(Chain.empty)))) + + // Could handle empty body + def ast0[F[_]: LiftParser: Comonad](): P0[ValidatedNec[ParserError[F], Tree[F]]] = + // `empty` is first to handle errors from `ast` at a first place + empty().backtrack | ast() + override def ast[F[_]: LiftParser: Comonad](): P[ValidatedNec[ParserError[F], Tree[F]]] = - (P.unit.lift0.map(Token.lift[F, Unit](_)).with1 ~ - P.repSep( - P.oneOf(RootExpr.validChildren.map(_.ast[F]())), - ` \n+` - ).surroundedBy(` \n+`.?) - .map(_.foldLeft[(Chain[ParserError[F]], Chain[Tree[F]])](Chain.empty -> Chain.empty) { - case ((errs, trees), Validated.Valid(tree)) => (errs, trees :+ tree) - case ((errs, trees), Validated.Invalid(err)) => (errs ++ err.toChain, trees) - })).map { case (point, (errs, trees)) => + parserSchema() + .map { case (point, (errs, trees)) => NonEmptyChain .fromChain(errs) .fold[ValidatedNec[ParserError[F], Tree[F]]]( diff --git a/parser/src/main/scala/aqua/parser/lexer/Token.scala b/parser/src/main/scala/aqua/parser/lexer/Token.scala index 82964ea2..26ba235e 100644 --- a/parser/src/main/scala/aqua/parser/lexer/Token.scala +++ b/parser/src/main/scala/aqua/parser/lexer/Token.scala @@ -71,6 +71,7 @@ object Token { (` `.?.void *> (`--` *> P.charsWhile0(_ != '\n')).?.void).with1 *> `\n` val ` \n+` : P[Unit] = P.repAs[Unit, Unit](` \n`.backtrack, 1)(Accumulator0.unitAccumulator0) + val ` \n*` : P0[Unit] = P.repAs0[Unit, Unit](` \n`.backtrack)(Accumulator0.unitAccumulator0) val ` : \n+` : P[Unit] = ` `.?.with1 *> `:` *> ` \n+` val `,` : P[Unit] = P.char(',') <* ` `.? val `.` : P[Unit] = P.char('.') diff --git a/project/plugins.sbt b/project/plugins.sbt index ffb6c88d..b0c2426b 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,3 +1,4 @@ addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0") addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0") addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.1.0") +addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") diff --git a/semantics/src/main/scala/aqua/semantics/Semantics.scala b/semantics/src/main/scala/aqua/semantics/Semantics.scala index 140da5bf..58a7901d 100644 --- a/semantics/src/main/scala/aqua/semantics/Semantics.scala +++ b/semantics/src/main/scala/aqua/semantics/Semantics.scala @@ -1,26 +1,21 @@ package aqua.semantics import aqua.model.func.raw.FuncOp -import aqua.model.{AquaContext, Model, ScriptModel} +import aqua.model.{AquaContext, EmptyModel, Model, ScriptModel} import aqua.parser.lexer.Token import aqua.parser.{Ast, Expr} import aqua.semantics.rules.ReportError -import aqua.semantics.rules.abilities.{ - AbilitiesAlgebra, - AbilitiesInterpreter, - AbilitiesState, - AbilityOp -} +import aqua.semantics.rules.abilities.{AbilitiesAlgebra, AbilitiesInterpreter, AbilitiesState, AbilityOp} import aqua.semantics.rules.names.{NameOp, NamesAlgebra, NamesInterpreter, NamesState} import aqua.semantics.rules.types.{TypeOp, TypesAlgebra, TypesInterpreter, TypesState} import cats.Eval import cats.arrow.FunctionK import cats.data.Validated.{Invalid, Valid} -import cats.data._ +import cats.data.* import cats.free.Free import cats.kernel.Monoid -import cats.syntax.apply._ -import cats.syntax.semigroup._ +import cats.syntax.apply.* +import cats.syntax.semigroup.* import monocle.Lens import monocle.macros.GenLens import scribe.Logging @@ -96,7 +91,11 @@ object Semantics extends Logging { NonEmptyChain .fromChain(state.errors) .fold[ValidatedNec[SemanticError[S], AquaContext]](Valid(ctx))(Invalid(_)) - case (state, _) => + case (state, _: EmptyModel) => + NonEmptyChain + .fromChain(state.errors) + .fold[ValidatedNec[SemanticError[S], AquaContext]](Valid(init))(Invalid(_)) + case (state, m) => NonEmptyChain .fromChain(state.errors) .map(Invalid(_))