From 9f5c0d64ed9b627cf4e85d030c012b7ae19d993d Mon Sep 17 00:00:00 2001 From: Dima Date: Mon, 24 May 2021 11:00:45 +0300 Subject: [PATCH] Bug fixes (#127) --- aqua-src/error.aqua | 2 +- build.sbt | 34 +++-- cli/src/main/scala/aqua/AppOps.scala | 117 ++++++++++++++++++ cli/src/main/scala/aqua/AquaCli.scala | 89 ++++++------- cli/src/main/scala/aqua/AquaCompiler.scala | 8 +- cli/src/main/scala/aqua/LogLevel.scala | 30 +++++ cli/src/main/scala/aqua/Test.scala | 7 +- .../src/main/scala/aqua/linker/Linker.scala | 35 +++--- .../scala/aqua/parser/expr/RootExpr.scala | 6 +- 9 files changed, 245 insertions(+), 83 deletions(-) create mode 100644 cli/src/main/scala/aqua/AppOps.scala create mode 100644 cli/src/main/scala/aqua/LogLevel.scala diff --git a/aqua-src/error.aqua b/aqua-src/error.aqua index 0ae61611..753a988f 100644 --- a/aqua-src/error.aqua +++ b/aqua-src/error.aqua @@ -20,4 +20,4 @@ func betterMessage(relay: string): par on "quray": Peer.is_connected("qurara") if isOnline: - Test.doSomething() \ No newline at end of file + Test.doSomething() diff --git a/build.sbt b/build.sbt index 6258d900..fed55e51 100644 --- a/build.sbt +++ b/build.sbt @@ -12,15 +12,23 @@ val monocleV = "3.0.0-M5" val scalaTestV = "3.2.7" // TODO update version for scala 3-RC3 val fs2V = "3.0.2" val catsEffectV = "3.1.0" +val airframeLogV = "21.5.4" +val log4catsV = "2.1.1" +val enumeratumV = "1.6.1" +val slf4jV = "1.7.25" val declineV = "2.0.0-RC1" // Scala3 issue: https://github.com/bkirwi/decline/issues/260 +val declineEnumV = "1.3.0" name := "aqua-hll" val commons = Seq( - baseAquaVersion := "0.1.1", - version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), - scalaVersion := dottyVersion, - libraryDependencies += "org.scalatest" %% "scalatest" % scalaTestV % Test, + baseAquaVersion := "0.1.1", + version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"), + scalaVersion := dottyVersion, + libraryDependencies ++= Seq( + "org.typelevel" %% "log4cats-core" % "2.1.1", + "org.scalatest" %% "scalatest" % scalaTestV % Test + ), addCompilerPlugin("org.typelevel" %% "kind-projector" % "0.11.3" cross CrossVersion.full) ) @@ -33,11 +41,16 @@ lazy val cli = project assembly / mainClass := Some("aqua.AquaCli"), assembly / assemblyJarName := "aqua-cli-" + version.value + ".jar", libraryDependencies ++= Seq( - "com.monovore" %% "decline" % declineV, - "com.monovore" %% "decline-effect" % declineV, - "org.typelevel" %% "cats-effect" % catsEffectV, - "co.fs2" %% "fs2-core" % fs2V, - "co.fs2" %% "fs2-io" % fs2V + "com.monovore" %% "decline" % declineV, + "com.monovore" %% "decline-effect" % declineV, + "org.typelevel" %% "cats-effect" % catsEffectV, + "co.fs2" %% "fs2-core" % fs2V, + "co.fs2" %% "fs2-io" % fs2V, + "org.typelevel" %% "log4cats-slf4j" % log4catsV, + "org.wvlet.airframe" %% "airframe-log" % airframeLogV, + "com.beachape" %% "enumeratum" % enumeratumV, + "org.slf4j" % "slf4j-jdk14" % slf4jV, + "com.monovore" %% "decline-enumeratum" % declineEnumV ) ) .dependsOn(semantics, `backend-air`, `backend-ts`, linker) @@ -63,6 +76,9 @@ lazy val parser = project lazy val linker = project .settings(commons: _*) .settings( + libraryDependencies ++= Seq( + "org.wvlet.airframe" %% "airframe-log" % airframeLogV + ) ) .dependsOn(parser) diff --git a/cli/src/main/scala/aqua/AppOps.scala b/cli/src/main/scala/aqua/AppOps.scala new file mode 100644 index 00000000..34d81fdf --- /dev/null +++ b/cli/src/main/scala/aqua/AppOps.scala @@ -0,0 +1,117 @@ +package aqua + +import cats.Functor +import cats.data.Validated.{Invalid, Valid} +import cats.data.{Validated, ValidatedNel} +import cats.effect.ExitCode +import cats.effect.std.Console +import cats.syntax.functor._ +import cats.syntax.traverse._ +import com.monovore.decline.Opts.help +import com.monovore.decline.enumeratum._ +import com.monovore.decline.{Opts, Visibility} + +import java.nio.file.Path + +object AppOps { + + val helpOpt: Opts[Unit] = + Opts.flag("help", help = "Display this help text", "h", Visibility.Partial).asHelp.as(()) + + val versionOpt: Opts[Unit] = + Opts.flag("version", help = "Show version", "v", Visibility.Partial) + + val logLevelOpt: Opts[LogLevel] = + Opts.option[LogLevel]("log-level", help = "Set log level").withDefault(LogLevel.Info) + + def checkPath: Path => ValidatedNel[String, Path] = { p => + Validated + .fromEither(Validated.catchNonFatal { + val f = p.toFile + if (f.exists() && f.isDirectory) { + Right(p) + } else { + Left(s"There is no path '${p.toString}' or it is not a directory") + } + }.toEither.left.map(t => s"An error occurred on imports reading: ${t.getMessage}").flatten) + .toValidatedNel + } + + val inputOpts: Opts[Path] = + Opts + .option[Path]( + "input", + "Path to the input directory that contains your .aqua files. It can only be a directory", + "i" + ) + .mapValidated(checkPath) + + val outputOpts: Opts[Path] = + Opts.option[Path]("output", "Path to the output directory", "o").mapValidated(checkPath) + + val importOpts: Opts[LazyList[Path]] = + Opts + .options[Path]("import", "Path to the directory to import from", "m") + .mapValidated { ps => + val checked = ps + .map(p => { + Validated.catchNonFatal { + val f = p.toFile + if (f.exists() && f.isDirectory) { + Right(p) + } else { + Left(s"There is no path ${p.toString} or it is not a directory") + } + } + }) + .toList + + checked.map { + case Validated.Valid(pE) => + pE match { + case Right(p) => + Validated.Valid(p) + case Left(e) => + Validated.Invalid(e) + } + case Validated.Invalid(e) => + Validated.Invalid(s"Error occurred on imports reading: ${e.getMessage}") + }.traverse { + case Valid(a) => Validated.validNel(a) + case Invalid(e) => Validated.invalidNel(e) + }.map(_.to(LazyList)) + } + .withDefault(LazyList.empty) + + val compileToAir: Opts[Boolean] = + Opts + .flag("air", "Generate .air file instead of typescript", "a") + .map(_ => true) + .withDefault(false) + + val noRelay: Opts[Boolean] = + Opts + .flag("no-relay", "Do not generate a pass through the relay node") + .map(_ => true) + .withDefault(false) + + val noXorWrapper: Opts[Boolean] = + Opts + .flag("no-xor", "Do not generate a wrapper that catches and displays errors") + .map(_ => true) + .withDefault(false) + + lazy val versionStr: String = + Option(getClass.getPackage.getImplementationVersion).filter(_.nonEmpty).getOrElse("no version") + + def versionAndExit[F[_]: Console: Functor]: F[ExitCode] = Console[F] + .println(versionStr) + .as(ExitCode.Success) + + def helpAndExit[F[_]: Console: Functor]: F[ExitCode] = Console[F] + .println(help) + .as(ExitCode.Success) + + def wrapWithOption[A](opt: Opts[A]): Opts[Option[A]] = + opt.map(v => Some(v)).withDefault(None) +} diff --git a/cli/src/main/scala/aqua/AquaCli.scala b/cli/src/main/scala/aqua/AquaCli.scala index 2c053fcf..8a8020a8 100644 --- a/cli/src/main/scala/aqua/AquaCli.scala +++ b/cli/src/main/scala/aqua/AquaCli.scala @@ -2,51 +2,42 @@ package aqua import aqua.model.transform.BodyConfig import cats.data.Validated -import cats.effect.{Concurrent, ExitCode, IO, IOApp} +import cats.effect._ import cats.effect.std.Console -import com.monovore.decline.Opts -import com.monovore.decline.effect.CommandIOApp import cats.syntax.apply._ import cats.syntax.functor._ +import com.monovore.decline.Opts +import com.monovore.decline.effect.CommandIOApp import fs2.io.file.Files +import org.typelevel.log4cats.slf4j.Slf4jLogger +import org.typelevel.log4cats.{Logger, SelfAwareStructuredLogger} +import wvlet.log.{LogSupport, Logger => WLogger} -import java.nio.file.Path +object AquaCli extends IOApp with LogSupport { + import AppOps._ -object AquaCli extends IOApp { + def main[F[_]: Concurrent: Files: Console: Logger]: Opts[F[ExitCode]] = { + versionOpt + .as( + versionAndExit + ) orElse helpOpt.as( + helpAndExit + ) orElse ( + inputOpts, + importOpts, + outputOpts, + compileToAir, + noRelay, + noXorWrapper, + wrapWithOption(helpOpt), + wrapWithOption(versionOpt), + logLevelOpt + ).mapN { case (input, imports, output, toAir, noRelay, noXor, h, v, logLevel) => + WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel)) - val inputOpts: Opts[Path] = - Opts.option[Path]("input", "Path to the input directory that contains your .aqua files", "i") - - val outputOpts: Opts[Path] = - Opts.option[Path]("output", "Path to the output directory", "o") - - val importOpts: Opts[LazyList[Path]] = - Opts - .options[Path]("import", "Path to the directory to import from", "m") - .map(_.toList.to(LazyList)) - .withDefault(LazyList.empty) - - val compileToAir: Opts[Boolean] = - Opts - .flag("air", "Generate .air file instead of typescript", "a") - .map(_ => true) - .withDefault(false) - - val noRelay: Opts[Boolean] = - Opts - .flag("no-relay", "Do not generate a pass through the relay node") - .map(_ => true) - .withDefault(false) - - val noXorWrapper: Opts[Boolean] = - Opts - .flag("no-xor", "Do not generate a wrapper that catches and displays errors") - .map(_ => true) - .withDefault(false) - - def mainOpts[F[_]: Console: Concurrent: Files]: Opts[F[ExitCode]] = - (inputOpts, importOpts, outputOpts, compileToAir, noRelay, noXorWrapper).mapN { - case (input, imports, output, toAir, noRelay, noXor) => + // if there is `--help` or `--version` flag - show help and version + // otherwise continue program execution + h.map(_ => helpAndExit) orElse v.map(_ => versionAndExit) getOrElse AquaCompiler .compileFilesTo[F]( input, @@ -66,21 +57,21 @@ object AquaCli extends IOApp { ExitCode.Success } } + } + + override def run(args: List[String]): IO[ExitCode] = { + + implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] = + Slf4jLogger.getLogger[F] - override def run(args: List[String]): IO[ExitCode] = CommandIOApp.run[IO]( "aqua-c", "Aquamarine compiler", - helpFlag = true, - Option(getClass.getPackage.getImplementationVersion).filter(_.nonEmpty) + helpFlag = false, + None )( - mainOpts[IO], - // Weird ugly hack: in case version flag or help flag is present, ignore other options, - // be it correct or not - args match { - case _ if args.contains("-v") || args.contains("--version") => "-v" :: Nil - case _ if args.contains("-h") || args.contains("--help") => "-h" :: Nil - case _ => args - } + main[IO], + args ) + } } diff --git a/cli/src/main/scala/aqua/AquaCompiler.scala b/cli/src/main/scala/aqua/AquaCompiler.scala index 06cf361e..65f1a45d 100644 --- a/cli/src/main/scala/aqua/AquaCompiler.scala +++ b/cli/src/main/scala/aqua/AquaCompiler.scala @@ -10,13 +10,13 @@ import aqua.parser.lexer.Token import aqua.parser.lift.FileSpan import aqua.semantics.{CompilerState, Semantics} import cats.Applicative -import cats.data.{Chain, EitherT, NonEmptyChain, Validated, ValidatedNec} +import cats.data._ import cats.effect.kernel.Concurrent -import fs2.io.file.Files -import cats.syntax.monoid._ -import cats.syntax.functor._ import cats.syntax.flatMap._ +import cats.syntax.functor._ +import cats.syntax.monoid._ import cats.syntax.show._ +import fs2.io.file.Files import fs2.text import java.nio.file.Path diff --git a/cli/src/main/scala/aqua/LogLevel.scala b/cli/src/main/scala/aqua/LogLevel.scala new file mode 100644 index 00000000..94c0b523 --- /dev/null +++ b/cli/src/main/scala/aqua/LogLevel.scala @@ -0,0 +1,30 @@ +package aqua + +import enumeratum._ +import wvlet.log.{LogLevel => WLogLevel} + +sealed trait LogLevel extends EnumEntry with EnumEntry.Lowercase + +object LogLevel extends Enum[LogLevel] { + case object Debug extends LogLevel + case object Trace extends LogLevel + case object Info extends LogLevel + case object Off extends LogLevel + case object Warn extends LogLevel + case object Error extends LogLevel + case object All extends LogLevel + + val values = findValues + + def toLogLevel(logLevel: LogLevel): WLogLevel = { + logLevel match { + case LogLevel.Debug => WLogLevel.DEBUG + case LogLevel.Trace => WLogLevel.TRACE + case LogLevel.Info => WLogLevel.INFO + case LogLevel.Off => WLogLevel.OFF + case LogLevel.Warn => WLogLevel.WARN + case LogLevel.Error => WLogLevel.ERROR + case LogLevel.All => WLogLevel.ALL + } + } +} diff --git a/cli/src/main/scala/aqua/Test.scala b/cli/src/main/scala/aqua/Test.scala index 875d9f9d..135374e7 100644 --- a/cli/src/main/scala/aqua/Test.scala +++ b/cli/src/main/scala/aqua/Test.scala @@ -1,13 +1,18 @@ package aqua import aqua.model.transform.BodyConfig -import cats.effect.{IO, IOApp} import cats.data.Validated +import cats.effect.{IO, IOApp, Sync} +import org.typelevel.log4cats.SelfAwareStructuredLogger +import org.typelevel.log4cats.slf4j.Slf4jLogger import java.nio.file.Paths object Test extends IOApp.Simple { + implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] = + Slf4jLogger.getLogger[F] + override def run: IO[Unit] = AquaCompiler .compileFilesTo[IO]( diff --git a/linker/src/main/scala/aqua/linker/Linker.scala b/linker/src/main/scala/aqua/linker/Linker.scala index 3a3e8d70..45858711 100644 --- a/linker/src/main/scala/aqua/linker/Linker.scala +++ b/linker/src/main/scala/aqua/linker/Linker.scala @@ -3,10 +3,11 @@ package aqua.linker import cats.data.{NonEmptyChain, Validated, ValidatedNec} import cats.kernel.{Monoid, Semigroup} import cats.syntax.monoid._ +import wvlet.log.LogSupport import scala.annotation.tailrec -object Linker { +object Linker extends LogSupport { @tailrec def iter[I, E, T: Semigroup]( @@ -18,29 +19,31 @@ object Linker { case Nil => Right(proc) case _ => val (canHandle, postpone) = mods.partition(_.dependsOn.keySet.forall(proc.contains)) - println("ITERATE, can handle: " + canHandle.map(_.id)) - println(s"proc = ${proc.keySet}") + debug("ITERATE, can handle: " + canHandle.map(_.id)) + debug(s"proc = ${proc.keySet}") if (canHandle.isEmpty && postpone.nonEmpty) Left(cycleError(postpone)) - else + else { + val folded = canHandle.foldLeft(proc) { case (acc, m) => + debug(m.id + " dependsOn " + m.dependsOn.keySet) + val deps: T => T = + m.dependsOn.keySet.map(acc).foldLeft[T => T](identity) { case (fAcc, f) => + debug("COMBINING ONE TIME ") + t => { + debug(s"call combine ${t}") + fAcc(t) |+| f(t) + } + } + acc + (m.id -> m.body.compose(deps)) + } iter( postpone, // TODO can be done in parallel - canHandle.foldLeft(proc) { case (acc, m) => - println(m.id + " dependsOn " + m.dependsOn.keySet) - val deps: T => T = - m.dependsOn.keySet.map(acc).foldLeft[T => T](identity) { case (fAcc, f) => - println("COMBINING ONE TIME ") - t => { - println(s"call combine ${t}") - fAcc(t) |+| f(t) - } - } - acc + (m.id -> m.body.compose(deps)) - }, + folded, cycleError ) + } } def apply[I, E, T: Monoid]( diff --git a/parser/src/main/scala/aqua/parser/expr/RootExpr.scala b/parser/src/main/scala/aqua/parser/expr/RootExpr.scala index 84f48578..4e11e959 100644 --- a/parser/src/main/scala/aqua/parser/expr/RootExpr.scala +++ b/parser/src/main/scala/aqua/parser/expr/RootExpr.scala @@ -1,13 +1,13 @@ package aqua.parser.expr import aqua.parser.Ast.Tree -import aqua.parser.lexer.Token.` \n+` -import aqua.parser.{Expr, ParserError} +import aqua.parser.lexer.Token._ import aqua.parser.lift.LiftParser -import cats.{Comonad, Eval} +import aqua.parser.{Expr, ParserError} import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec} import cats.free.Cofree import cats.parse.{Parser => P} +import cats.{Comonad, Eval} case class RootExpr[F[_]]() extends Expr[F](RootExpr)