Removing IO from compiler submodule (#186)

* Removing IO from compiler submodule (wip)

* move targets to cli
This commit is contained in:
Dmitry Kurinskiy
2021-06-30 09:21:40 +03:00
committed by GitHub
parent f15bd0558b
commit d24e77b5e3
12 changed files with 240 additions and 199 deletions

View File

@ -4,8 +4,7 @@ import aqua.backend.Backend
import aqua.backend.air.AirBackend
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.compiler.AquaCompiler
import aqua.compiler.AquaCompiler.{AirTarget, CompileTarget, JavaScriptTarget, TypescriptTarget}
import aqua.compiler.{AquaCompiler, AquaIO}
import aqua.model.transform.BodyConfig
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import cats.Id
@ -34,6 +33,11 @@ object CustomLogFormatter extends LogFormatter {
object AquaCli extends IOApp with LogSupport {
import AppOps._
sealed trait CompileTarget
case object TypescriptTarget extends CompileTarget
case object JavaScriptTarget extends CompileTarget
case object AirTarget extends CompileTarget
def targetToBackend(target: CompileTarget): Backend = {
target match {
case TypescriptTarget =>
@ -68,13 +72,15 @@ object AquaCli extends IOApp with LogSupport {
WLogger.setDefaultLogLevel(LogLevel.toLogLevel(logLevel))
WLogger.setDefaultFormatter(CustomLogFormatter)
implicit val aio: AquaIO[F] = new AquaFilesIO[F]
// if there is `--help` or `--version` flag - show help and version
// otherwise continue program execution
h.map(_ => helpAndExit) orElse v.map(_ => versionAndExit) getOrElse {
val target =
if (toAir) AquaCompiler.AirTarget
else if (toJs) AquaCompiler.JavaScriptTarget
else AquaCompiler.TypescriptTarget
if (toAir) AirTarget
else if (toJs) JavaScriptTarget
else TypescriptTarget
val bc = {
val bc = BodyConfig(wrapWithXor = !noXor, constants = constants)
bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay))

View File

@ -0,0 +1,141 @@
package aqua
import aqua.compiler.AquaIO
import aqua.compiler.io.{
AquaFileError,
EmptyFileError,
FileNotFound,
FileSystemError,
FileWriteError
}
import aqua.parser.lift.FileSpan
import cats.data.Validated.{Invalid, Valid}
import cats.data.{Chain, EitherT, NonEmptyChain, Validated, ValidatedNec}
import cats.syntax.functor._
import cats.syntax.either._
import cats.effect.kernel.Concurrent
import fs2.io.file.Files
import fs2.text
import cats.syntax.applicative._
import cats.syntax.flatMap._
import cats.syntax.apply._
import java.nio.file.Path
import scala.util.Try
class AquaFilesIO[F[_]: Files: Concurrent] extends AquaIO[F] {
override def readFile(file: Path): EitherT[F, AquaFileError, String] =
EitherT(
Files[F]
.readAll(file, 4096)
.fold(Vector.empty[Byte])((acc, b) => acc :+ b)
// TODO fix for comment on last line in air
// TODO should be fixed by parser
.map(_.appendedAll("\n\r".getBytes))
.flatMap(fs2.Stream.emits)
.through(text.utf8Decode)
.attempt
.compile
.last
.map(
_.fold((EmptyFileError(file): AquaFileError).asLeft[String])(_.left.map(FileSystemError))
)
)
private def findFirstF(
in: List[Path],
notFound: EitherT[F, AquaFileError, Path]
): EitherT[F, AquaFileError, Path] =
in.headOption.fold(notFound)(p =>
EitherT(
Concurrent[F].attempt(p.toFile.isFile.pure[F])
)
.leftMap[AquaFileError](FileSystemError)
.recover({ case _ => false })
.flatMap {
case true =>
EitherT(
Concurrent[F].attempt(p.toAbsolutePath.normalize().pure[F])
).leftMap[AquaFileError](FileSystemError)
case false =>
findFirstF(in.tail, notFound)
}
)
/**
* Checks if a file exists in the list of possible paths
*/
def resolve(
focus: FileSpan.Focus,
src: Path,
imports: List[Path]
): EitherT[F, AquaFileError, Path] =
findFirstF(
imports
.map(_.resolve(src)),
EitherT.leftT(FileNotFound(focus, src, imports))
)
override def listAqua(folder: Path): F[ValidatedNec[AquaFileError, Chain[Path]]] =
Validated
.fromTry(
Try {
val f = folder.toFile
if (f.isDirectory) {
f.listFiles().toList
} else {
f :: Nil
}
}
)
.leftMap[AquaFileError](FileSystemError)
.leftMap(NonEmptyChain.one)
.pure[F]
.flatMap {
case Valid(files) =>
files.collect {
case f if f.isFile && f.getName.endsWith(".aqua") =>
Validated
.fromTry(
Try(Chain.one(f.toPath.toAbsolutePath.normalize()))
)
.leftMap(FileSystemError)
.leftMap(NonEmptyChain.one)
.pure[F]
case f if f.isDirectory =>
listAqua(f.toPath)
}.foldLeft(Validated.validNec[AquaFileError, Chain[Path]](Chain.nil).pure[F]) {
case (acc, v) =>
(acc, v).mapN(_ combine _)
}
case Invalid(errs) =>
Validated.invalid[NonEmptyChain[AquaFileError], Chain[Path]](errs).pure[F]
}
override def writeFile(file: Path, content: String): EitherT[F, AquaFileError, Unit] =
EitherT
.right[AquaFileError](Files[F].deleteIfExists(file))
.flatMap(_ =>
EitherT[F, AquaFileError, Unit](
fs2.Stream
.emit(
content
)
.through(text.utf8Encode)
.through(Files[F].writeAll(file))
.attempt
.map { e =>
e.left
.map(t => FileWriteError(file, t))
}
.compile
.drain
.map(_ => Right(()))
)
)
}
object AquaFilesIO {
implicit def summon[F[_]: Files: Concurrent]: AquaIO[F] = new AquaFilesIO[F]
}

View File

@ -1,7 +1,7 @@
package aqua
import aqua.backend.ts.TypeScriptBackend
import aqua.compiler.AquaCompiler
import aqua.compiler.{AquaCompiler, AquaIO}
import aqua.model.transform.BodyConfig
import cats.data.Validated
import cats.effect.{IO, IOApp, Sync}
@ -15,6 +15,8 @@ object Test extends IOApp.Simple {
implicit def logger[F[_]: Sync]: SelfAwareStructuredLogger[F] =
Slf4jLogger.getLogger[F]
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
override def run: IO[Unit] =
AquaCompiler
.compileFilesTo[IO](

View File

@ -17,6 +17,8 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
val targetJs = Files.createTempDirectory("js")
val targetAir = Files.createTempDirectory("air")
import aqua.AquaFilesIO.summon
val bc = BodyConfig()
AquaCompiler
.compileFilesTo[IO](src, List.empty, targetTs, TypeScriptBackend, bc)