This commit is contained in:
Dima 2021-08-18 13:06:14 +03:00 committed by GitHub
parent f59a93ac27
commit 296c64836d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 192 additions and 243 deletions

View File

@ -56,6 +56,7 @@ lazy val cli = crossProject(JSPlatform, JVMPlatform)
lazy val cliJS = cli.js
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true
)

View File

@ -1,48 +0,0 @@
package aqua
import aqua.backend.Backend
import aqua.backend.ts.TypeScriptBackend
import aqua.compiler.{AquaCompiler, AquaError, AquaSources}
import aqua.model.transform.TransformConfig
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.data.*
import cats.syntax.functor.*
import cats.syntax.applicative.*
import cats.{Applicative, Monad, Show}
import aqua.parser.lift.FileSpan
object JsApp {
trait AquaJsError {}
case class FileId(name: String)
class Sources[F[_]: Applicative] extends AquaSources[F, AquaJsError, FileId] {
def sources: F[ValidatedNec[AquaJsError, Chain[(FileId, String)]]] =
Validated.Valid(Chain.empty).pure[F]
// Resolve id of the imported imp string from I file
def resolveImport(from: FileId, imp: String): F[ValidatedNec[AquaJsError, FileId]] =
Validated.Valid(FileId("")).pure[F]
// Load file by its resolved I
def load(file: FileId): F[ValidatedNec[AquaJsError, String]] = Validated.Valid("").pure[F]
}
def main(args: Array[String]): Unit = {
// test code
// val sources = new Sources[IO]()
// val bodyConfig = GenerationConfig()
// val b = AquaCompiler
// .compileTo[IO, AquaJsError, FileId, FileSpan.F, String](
// sources,
// (fmid, src) => FileSpan.fileSpanLiftParser(fmid.name, src),
// TypeScriptBackend,
// bodyConfig,
// (a) => ???
// )
println("Hello world!")
}
}

View File

@ -6,11 +6,11 @@ import cats.data.Chain
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import fs2.text
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
import fs2.io.file.{Files, Path}
import org.scalatest.flatspec.AsyncFlatSpec
class SourcesSpec extends AnyFlatSpec with Matchers {
class SourcesSpec extends AsyncFlatSpec with Matchers {
implicit val aquaIO: AquaIO[IO] = AquaFilesIO.summon[IO]
"AquaFileSources" should "generate correct fileId with imports" in {
@ -18,25 +18,25 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
val importPath = path.resolve("imports")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
sourceGen.sources.map { result =>
result.isValid shouldBe true
val result = sourceGen.sources.unsafeRunSync()
result.isValid shouldBe true
val listResult = result
.getOrElse(Chain.empty)
.toList
.map { case (fid, s) =>
(fid.file.toString.split("/").last, s)
}
.sortBy(_._1) // sort cause different systems have different order of file reading
val listResult = result
.getOrElse(Chain.empty)
.toList
.map { case (fid, s) =>
(fid.file.toString.split("/").last, s)
}
.sortBy(_._1) // sort cause different systems have different order of file reading
val (id, importFile) = listResult(1)
id shouldBe "index.aqua"
importFile.nonEmpty shouldBe true
val (id, importFile) = listResult(1)
id shouldBe "index.aqua"
importFile.nonEmpty shouldBe true
val (importNearId, importFileNear) = listResult.head
importNearId shouldBe "importNear.aqua"
importFileNear.nonEmpty shouldBe true
val (importNearId, importFileNear) = listResult.head
importNearId shouldBe "importNear.aqua"
importFileNear.nonEmpty shouldBe true
}.unsafeToFuture()
}
"AquaFileSources" should "throw an error if a source file doesn't exist" in {
@ -44,8 +44,7 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
val sourceGen = new AquaFileSources[IO](path, Nil)
val result = sourceGen.sources.unsafeRunSync()
result.isInvalid shouldBe true
sourceGen.sources.map(result => result.isInvalid shouldBe true).unsafeToFuture()
}
"AquaFileSources" should "throw an error if there is no import that is indicated in a source" in {
@ -53,9 +52,10 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
val importPath = path.resolve("random/import/path")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
val result =
sourceGen.resolveImport(FileModuleId(path.resolve("no-file.aqua")), "no/file").unsafeRunSync()
result.isInvalid shouldBe true
sourceGen
.resolveImport(FileModuleId(path.resolve("no-file.aqua")), "no/file")
.map(result => result.isInvalid shouldBe true)
.unsafeToFuture()
}
"AquaFileSources" should "find correct imports" in {
@ -64,36 +64,33 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
val sourceGen = new AquaFileSources[IO](srcPath, importPath :: Nil)
// should be found in importPath
val result =
sourceGen
.resolveImport(FileModuleId(srcPath), "imports/import.aqua")
.unsafeRunSync()
(for {
// should be found in importPath
result <- sourceGen.resolveImport(FileModuleId(srcPath), "imports/import.aqua")
exists <- {
result.isValid shouldBe true
val file = result.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file)
}
_ = exists shouldBe true
result.isValid shouldBe true
val file = result.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file).unsafeRunSync() shouldBe true
// should be found near src file
val result2 =
sourceGen
.resolveImport(FileModuleId(srcPath), "importNear.aqua")
.unsafeRunSync()
result2.isValid shouldBe true
val file2 = result2.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file2).unsafeRunSync() shouldBe true
// near src file but in another directory
val sourceGen2 = new AquaFileSources[IO](srcPath, Nil)
val result3 =
sourceGen2
.resolveImport(FileModuleId(srcPath), "imports/import.aqua")
.unsafeRunSync()
result3.isValid shouldBe true
val file3 = result3.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file3).unsafeRunSync() shouldBe true
// should be found near src file
result2 <- sourceGen.resolveImport(FileModuleId(srcPath), "importNear.aqua")
exists2 <- {
result2.isValid shouldBe true
val file2 = result2.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file2)
}
_ = exists2 shouldBe true
// near src file but in another directory
sourceGen2 = new AquaFileSources[IO](srcPath, Nil)
result3 <- sourceGen2.resolveImport(FileModuleId(srcPath), "imports/import.aqua")
exists3 <- {
result3.isValid shouldBe true
val file3 = result3.getOrElse(FileModuleId(Path("/some/random"))).file
Files[IO].exists(file3)
}
} yield { exists3 shouldBe true }).unsafeToFuture()
}
"AquaFileSources" should "resolve correct path for target" in {
@ -106,11 +103,15 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
val suffix = "_custom.super"
val resolved = sourceGen.resolveTargetPath(filePath, targetPath, suffix).unsafeRunSync()
resolved.isValid shouldBe true
sourceGen
.resolveTargetPath(filePath, targetPath, suffix)
.map { resolved =>
resolved.isValid shouldBe true
val targetFilePath = resolved.toOption.get
targetFilePath.toString shouldBe "/target/dir/some-dir/file_custom.super"
val targetFilePath = resolved.toOption.get
targetFilePath.toString shouldBe "/target/dir/some-dir/file_custom.super"
}
.unsafeToFuture()
}
"AquaFileSources" should "write correct file with correct path" in {
@ -121,38 +122,36 @@ class SourcesSpec extends AnyFlatSpec with Matchers {
// clean up
val resultPath = Path("cli/.jvm/src/test/test-dir/target/imports/import_hey.custom")
Files[IO].deleteIfExists(resultPath).unsafeRunSync()
val sourceGen = new AquaFileSources[IO](path, Nil)
val content = "some random content"
val compiled = AquaCompiled[FileModuleId](
FileModuleId(filePath),
Seq(Generated("_hey.custom", content))
)
val resolved = sourceGen.write(targetPath)(compiled).unsafeRunSync()
resolved.size shouldBe 1
resolved.head.isValid shouldBe true
Files[IO].exists(resultPath).unsafeRunSync() shouldBe true
val resultText = Files[IO]
.readAll(resultPath)
.fold(
Vector
.empty[Byte]
)((acc, b) => acc :+ b)
.flatMap(fs2.Stream.emits)
.through(text.utf8.decode)
.attempt
.compile
.last
.unsafeRunSync()
.get
.right
.get
resultText shouldBe content
Files[IO].deleteIfExists(resultPath).unsafeRunSync()
(for {
_ <- Files[IO].deleteIfExists(resultPath)
sourceGen = new AquaFileSources[IO](path, Nil)
content = "some random content"
compiled = AquaCompiled[FileModuleId](
FileModuleId(filePath),
Seq(Generated("_hey.custom", content))
)
resolved <- sourceGen.write(targetPath)(compiled)
_ = {
resolved.size shouldBe 1
resolved.head.isValid shouldBe true
}
exists <- Files[IO].exists(resultPath)
_ = exists shouldBe true
result <- Files[IO]
.readAll(resultPath)
.fold(
Vector
.empty[Byte]
)((acc, b) => acc :+ b)
.flatMap(fs2.Stream.emits)
.through(text.utf8.decode)
.attempt
.compile
.last
resultText = result.get.right.get
_ <- Files[IO].deleteIfExists(resultPath)
} yield {
resultText shouldBe content
}).unsafeToFuture()
}
}

View File

@ -1,16 +1,19 @@
package aqua
import aqua.backend.Version
import aqua.model.LiteralModel
import aqua.model.transform.TransformConfig
import aqua.parser.expr.ConstantExpr
import aqua.parser.lift.LiftParser
import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import cats.effect.{ExitCode, IO, unsafe}
import cats.data.{NonEmptyList, Validated, ValidatedNec, ValidatedNel}
import cats.effect.{ExitCode, IO}
import cats.effect.std.Console
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{Comonad, Functor}
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.{Comonad, Functor, Monad}
import com.monovore.decline.Opts.help
import com.monovore.decline.{Opts, Visibility}
import scribe.Level
@ -41,84 +44,56 @@ object AppOps {
)
}
def checkPath(implicit runtime: unsafe.IORuntime): String => ValidatedNel[String, Path] = {
pathStr =>
Validated
.fromEither(Validated.catchNonFatal {
val p = Path(pathStr)
Files[IO]
.exists(p)
.flatMap { exists =>
if (exists)
Files[IO].isRegularFile(p).map { isFile =>
if (isFile) {
val filename = p.fileName.toString
val ext = Option(filename)
.filter(_.contains("."))
.map(f => f.substring(f.lastIndexOf(".") + 1))
.getOrElse("")
if (ext != "aqua") Left("File must be with 'aqua' extension")
else Right(p)
} else
Right(p)
}
else
IO(Left(s"There is no path '${p.toString}'"))
}
// TODO: make it with correct effects
.unsafeRunSync()
}.toEither.left.map(t => s"An error occurred on imports reading: ${t.getMessage}").flatten)
.toValidatedNel
def checkPath[F[_]: Monad: Files](pathStr: String): F[ValidatedNec[String, Path]] = {
val p = Path(pathStr)
Files[F]
.exists(p)
.flatMap { exists =>
if (exists)
Files[F].isRegularFile(p).map { isFile =>
if (isFile) {
if (p.extName != ".aqua") Validated.invalidNec("File must be with 'aqua' extension")
else Validated.validNec(p)
} else
Validated.validNec(p)
}
else
Validated.invalidNec(s"There is no path '${p.toString}'").pure[F]
}
}
def inputOpts(implicit runtime: unsafe.IORuntime): Opts[Path] =
def inputOpts[F[_]: Monad: Files]: Opts[F[ValidatedNec[String, Path]]] =
Opts
.option[String](
"input",
"Path to an aqua file or an input directory that contains your .aqua files",
"i"
)
.mapValidated(checkPath)
.map(s => checkPath[F](s))
def outputOpts(implicit runtime: unsafe.IORuntime): Opts[Path] =
Opts.option[String]("output", "Path to the output directory", "o").mapValidated(checkPath)
def outputOpts[F[_]: Monad: Files]: Opts[F[ValidatedNec[String, Path]]] =
Opts.option[String]("output", "Path to the output directory", "o").map(s => checkPath[F](s))
def importOpts(implicit runtime: unsafe.IORuntime): Opts[List[Path]] =
def importOpts[F[_]: Monad: Files]: Opts[F[ValidatedNec[String, List[Path]]]] =
Opts
.options[String]("import", "Path to the directory to import from", "m")
.mapValidated { ps =>
val checked = ps
.map(pStr => {
Validated.catchNonFatal {
val p = Path(pStr)
(for {
exists <- Files[IO].exists(p)
isDir <- Files[IO].isDirectory(p)
} yield {
if (exists && isDir) Right(p)
else Left(s"There is no path ${p.toString} or it is not a directory")
})
// TODO: make it with correct effects
.unsafeRunSync()
}
.map { ps =>
val checked: List[F[ValidatedNec[String, Path]]] = ps.toList.map { pStr =>
val p = Path(pStr)
(for {
exists <- Files[F].exists(p)
isDir <- Files[F].isDirectory(p)
} yield {
if (exists && isDir) Validated.validNec[String, Path](p)
else
Validated.invalidNec[String, Path](
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)
}
checked.sequence.map(_.sequence)
}
.withDefault(List.empty)
def constantOpts[F[_]: LiftParser: Comonad]: Opts[List[TransformConfig.Const]] =
Opts
@ -168,7 +143,7 @@ object AppOps {
.withDefault(false)
lazy val versionStr: String =
Option(getClass.getPackage.getImplementationVersion).filter(_.nonEmpty).getOrElse("no version")
Version.version
def versionAndExit[F[_]: Console: Functor]: F[ExitCode] = Console[F]
.println(versionStr)

View File

@ -7,19 +7,21 @@ import aqua.backend.ts.TypeScriptBackend
import aqua.files.AquaFilesIO
import aqua.model.transform.TransformConfig
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.Id
import cats.data.Validated
import cats.{Functor, Id, Monad}
import cats.data.{Chain, NonEmptyList, Validated, ValidatedNec, ValidatedNel}
import cats.effect.*
import cats.effect.std.Console as ConsoleEff
import cats.syntax.apply.*
import cats.syntax.functor.*
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import com.monovore.decline.Opts
import com.monovore.decline.effect.CommandIOApp
import fs2.io.file.Files
import scribe.Logging
object AquaCli extends IOApp with Logging {
import AppOps._
import AppOps.*
sealed trait CompileTarget
case object TypescriptTarget extends CompileTarget
@ -45,9 +47,9 @@ object AquaCli extends IOApp with Logging {
) orElse helpOpt.as(
helpAndExit
) orElse (
inputOpts,
importOpts,
outputOpts,
inputOpts[F],
importOpts[F],
outputOpts[F],
compileToAir,
compileToJs,
noRelay,
@ -57,7 +59,7 @@ object AquaCli extends IOApp with Logging {
logLevelOpt,
constantOpts[Id]
).mapN {
case (input, imports, output, toAir, toJs, noRelay, noXor, h, v, logLevel, constants) =>
case (inputF, importsF, outputF, toAir, toJs, noRelay, noXor, h, v, logLevel, constants) =>
scribe.Logger.root
.clearHandlers()
.clearModifiers()
@ -78,21 +80,35 @@ object AquaCli extends IOApp with Logging {
bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay))
}
logger.info(s"Aqua Compiler ${versionStr}")
AquaPathCompiler
.compileFilesTo[F](
input,
imports,
output,
targetToBackend(target),
bc
)
.map {
(inputF, outputF, importsF).mapN {(i, o, imp) =>
i.andThen { input =>
o.andThen { output =>
imp.map { imports =>
AquaPathCompiler
.compileFilesTo[F](
input,
imports,
output,
targetToBackend(target),
bc
)
}
}
}
}.flatMap {
case Validated.Invalid(errs) =>
errs.map(System.out.println)
ExitCode.Error
case Validated.Valid(results) =>
results.map(logger.info(_))
ExitCode.Success
errs.map(logger.error(_))
ExitCode.Error.pure[F]
case Validated.Valid(result) =>
result.map {
case Validated.Invalid(errs) =>
errs.map(logger.error(_))
ExitCode.Error
case Validated.Valid(results) =>
results.map(logger.info(_))
ExitCode.Success
}
}
}
}

View File

@ -1,6 +1,6 @@
package aqua
import aqua.compiler._
import aqua.compiler.*
import aqua.files.FileModuleId
import aqua.io.AquaFileError
import aqua.parser.lift.FileSpan

View File

@ -12,13 +12,14 @@ import cats.syntax.functor.*
import cats.syntax.monad.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.util.Try
class AquaFileSources[F[_]: AquaIO: Monad: Files: Functor](
sourcesPath: Path,
importFrom: List[Path]
) extends AquaSources[F, AquaFileError, FileModuleId] {
) extends AquaSources[F, AquaFileError, FileModuleId] with Logging {
private val filesIO = implicitly[AquaIO[F]]
override def sources: F[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] =

View File

@ -7,15 +7,16 @@ import aqua.model.transform.TransformConfig
import aqua.model.transform.res.AquaRes
import aqua.parser.lift.LiftParser
import aqua.semantics.Semantics
import cats.data.Validated.{Invalid, Valid, validNec}
import cats.data.Validated.{validNec, Invalid, Valid}
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{Comonad, Monad}
import scribe.Logging
object AquaCompiler {
object AquaCompiler extends Logging {
def compile[F[_]: Monad, E, I, S[_]: Comonad](
sources: AquaSources[F, E, I],
@ -26,10 +27,13 @@ object AquaCompiler {
import config.aquaContextMonoid
type Err = AquaError[I, E, S]
new AquaParser[F, E, I, S](sources, liftI)
.resolve[ValidatedNec[Err, AquaContext]](ast =>
context =>
context.andThen(ctx => Semantics.process(ast, ctx).leftMap(_.map[Err](CompileError(_))))
)
.resolve[ValidatedNec[Err, AquaContext]] { ast => context =>
context.andThen { ctx =>
Semantics
.process(ast, ctx)
.leftMap(_.map[Err](CompileError(_)))
}
}
.map {
case Valid(modules) =>
Linker.link[I, AquaError[I, E, S], ValidatedNec[Err, AquaContext]](
@ -59,11 +63,11 @@ object AquaCompiler {
}
def compileTo[F[_]: Monad, E, I, S[_]: Comonad, T](
sources: AquaSources[F, E, I],
liftI: (I, String) => LiftParser[S],
backend: Backend,
config: TransformConfig,
write: AquaCompiled[I] => F[Seq[Validated[E, T]]]
sources: AquaSources[F, E, I],
liftI: (I, String) => LiftParser[S],
backend: Backend,
config: TransformConfig,
write: AquaCompiled[I] => F[Seq[Validated[E, T]]]
): F[ValidatedNec[AquaError[I, E, S], Chain[T]]] =
compile[F, E, I, S](sources, liftI, backend, config).flatMap {
case Valid(compiled) =>

View File

@ -5,17 +5,18 @@ import aqua.parser.Ast
import aqua.parser.head.ImportExpr
import aqua.parser.lift.LiftParser
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.syntax.applicative._
import cats.syntax.flatMap._
import cats.syntax.functor._
import cats.syntax.traverse._
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{Comonad, Monad}
import scribe.Logging
// TODO: add tests
class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
sources: AquaSources[F, E, I],
liftI: (I, String) => LiftParser[S]
) {
) extends Logging {
type Body = Ast[S]
type Err = AquaError[I, E, S]