mirror of
https://github.com/fluencelabs/aqua.git
synced 2025-04-25 06:52:13 +00:00
Cli/compiler refactoring (#184)
This commit is contained in:
parent
5e1ef6e227
commit
f15bd0558b
19
build.sbt
19
build.sbt
@ -20,6 +20,8 @@ val declineV = "2.0.0-RC1" // Scala3 issue: https://github.com/bkirwi/decline/is
|
|||||||
val declineEnumV = "1.3.0"
|
val declineEnumV = "1.3.0"
|
||||||
|
|
||||||
val airframeLog = "org.wvlet.airframe" %% "airframe-log" % airframeLogV
|
val airframeLog = "org.wvlet.airframe" %% "airframe-log" % airframeLogV
|
||||||
|
val catsEffect = "org.typelevel" %% "cats-effect" % catsEffectV
|
||||||
|
val fs2Io = "co.fs2" %% "fs2-io" % fs2V
|
||||||
|
|
||||||
name := "aqua-hll"
|
name := "aqua-hll"
|
||||||
|
|
||||||
@ -46,16 +48,14 @@ lazy val cli = project
|
|||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"com.monovore" %% "decline" % declineV,
|
"com.monovore" %% "decline" % declineV,
|
||||||
"com.monovore" %% "decline-effect" % declineV,
|
"com.monovore" %% "decline-effect" % declineV,
|
||||||
"org.typelevel" %% "cats-effect" % catsEffectV,
|
catsEffect,
|
||||||
"co.fs2" %% "fs2-core" % fs2V,
|
|
||||||
"co.fs2" %% "fs2-io" % fs2V,
|
|
||||||
"org.typelevel" %% "log4cats-slf4j" % log4catsV,
|
"org.typelevel" %% "log4cats-slf4j" % log4catsV,
|
||||||
"com.beachape" %% "enumeratum" % enumeratumV,
|
"com.beachape" %% "enumeratum" % enumeratumV,
|
||||||
"org.slf4j" % "slf4j-jdk14" % slf4jV,
|
"org.slf4j" % "slf4j-jdk14" % slf4jV,
|
||||||
"com.monovore" %% "decline-enumeratum" % declineEnumV
|
"com.monovore" %% "decline-enumeratum" % declineEnumV
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.dependsOn(semantics, `backend-air`, `backend-ts`, `backend-js`, linker, backend)
|
.dependsOn(compiler, `backend-air`, `backend-ts`, `backend-js`)
|
||||||
|
|
||||||
lazy val types = project
|
lazy val types = project
|
||||||
.settings(commons)
|
.settings(commons)
|
||||||
@ -108,6 +108,17 @@ lazy val semantics = project
|
|||||||
)
|
)
|
||||||
.dependsOn(model, `test-kit` % Test, parser)
|
.dependsOn(model, `test-kit` % Test, parser)
|
||||||
|
|
||||||
|
lazy val compiler = project
|
||||||
|
.in(file("compiler"))
|
||||||
|
.settings(commons: _*)
|
||||||
|
.settings(
|
||||||
|
libraryDependencies ++= Seq(
|
||||||
|
catsEffect,
|
||||||
|
fs2Io
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.dependsOn(model, semantics, linker, backend)
|
||||||
|
|
||||||
lazy val backend = project
|
lazy val backend = project
|
||||||
.in(file("backend"))
|
.in(file("backend"))
|
||||||
.settings(commons: _*)
|
.settings(commons: _*)
|
||||||
|
@ -61,7 +61,7 @@ object AppOps {
|
|||||||
val outputOpts: Opts[Path] =
|
val outputOpts: Opts[Path] =
|
||||||
Opts.option[Path]("output", "Path to the output directory", "o").mapValidated(checkPath)
|
Opts.option[Path]("output", "Path to the output directory", "o").mapValidated(checkPath)
|
||||||
|
|
||||||
val importOpts: Opts[LazyList[Path]] =
|
val importOpts: Opts[List[Path]] =
|
||||||
Opts
|
Opts
|
||||||
.options[Path]("import", "Path to the directory to import from", "m")
|
.options[Path]("import", "Path to the directory to import from", "m")
|
||||||
.mapValidated { ps =>
|
.mapValidated { ps =>
|
||||||
@ -91,9 +91,9 @@ object AppOps {
|
|||||||
}.traverse {
|
}.traverse {
|
||||||
case Valid(a) => Validated.validNel(a)
|
case Valid(a) => Validated.validNel(a)
|
||||||
case Invalid(e) => Validated.invalidNel(e)
|
case Invalid(e) => Validated.invalidNel(e)
|
||||||
}.map(_.to(LazyList))
|
|
||||||
}
|
}
|
||||||
.withDefault(LazyList.empty)
|
}
|
||||||
|
.withDefault(List.empty)
|
||||||
|
|
||||||
def constantOpts[F[_]: LiftParser: Comonad]: Opts[List[Constant]] =
|
def constantOpts[F[_]: LiftParser: Comonad]: Opts[List[Constant]] =
|
||||||
Opts
|
Opts
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
package aqua
|
|
||||||
|
|
||||||
import aqua.parser.lift.{FileSpan, LiftParser, Span}
|
|
||||||
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
|
|
||||||
import cats.Eval
|
|
||||||
import cats.data.ValidatedNec
|
|
||||||
import cats.parse.LocationMap
|
|
||||||
|
|
||||||
object Aqua {
|
|
||||||
|
|
||||||
def parseFileString(name: String, input: String): ValidatedNec[AquaError, Ast[FileSpan.F]] = {
|
|
||||||
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
|
|
||||||
Ast
|
|
||||||
.fromString[FileSpan.F](input)
|
|
||||||
.leftMap(_.map {
|
|
||||||
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
|
|
||||||
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
|
|
||||||
case LexerError(pe) =>
|
|
||||||
val fileSpan =
|
|
||||||
FileSpan(
|
|
||||||
name,
|
|
||||||
input,
|
|
||||||
Eval.later(LocationMap(input)),
|
|
||||||
Span(pe.failedAtOffset, pe.failedAtOffset + 1)
|
|
||||||
)
|
|
||||||
SyntaxError(fileSpan, pe.expected)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,5 +1,11 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
|
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.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
|
||||||
import cats.Id
|
import cats.Id
|
||||||
@ -28,6 +34,17 @@ object CustomLogFormatter extends LogFormatter {
|
|||||||
object AquaCli extends IOApp with LogSupport {
|
object AquaCli extends IOApp with LogSupport {
|
||||||
import AppOps._
|
import AppOps._
|
||||||
|
|
||||||
|
def targetToBackend(target: CompileTarget): Backend = {
|
||||||
|
target match {
|
||||||
|
case TypescriptTarget =>
|
||||||
|
TypeScriptBackend
|
||||||
|
case JavaScriptTarget =>
|
||||||
|
JavaScriptBackend
|
||||||
|
case AirTarget =>
|
||||||
|
AirBackend
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
|
def main[F[_]: Concurrent: Files: ConsoleEff: Logger]: Opts[F[ExitCode]] = {
|
||||||
versionOpt
|
versionOpt
|
||||||
.as(
|
.as(
|
||||||
@ -68,7 +85,7 @@ object AquaCli extends IOApp with LogSupport {
|
|||||||
input,
|
input,
|
||||||
imports,
|
imports,
|
||||||
output,
|
output,
|
||||||
target,
|
targetToBackend(target),
|
||||||
bc
|
bc
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package aqua
|
package aqua
|
||||||
|
|
||||||
|
import aqua.backend.ts.TypeScriptBackend
|
||||||
|
import aqua.compiler.AquaCompiler
|
||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import cats.data.Validated
|
import cats.data.Validated
|
||||||
import cats.effect.{IO, IOApp, Sync}
|
import cats.effect.{IO, IOApp, Sync}
|
||||||
@ -17,9 +19,9 @@ object Test extends IOApp.Simple {
|
|||||||
AquaCompiler
|
AquaCompiler
|
||||||
.compileFilesTo[IO](
|
.compileFilesTo[IO](
|
||||||
Paths.get("./aqua-src"),
|
Paths.get("./aqua-src"),
|
||||||
LazyList(Paths.get("./aqua")),
|
List(Paths.get("./aqua")),
|
||||||
Paths.get("./target"),
|
Paths.get("./target"),
|
||||||
AquaCompiler.TypescriptTarget,
|
TypeScriptBackend,
|
||||||
BodyConfig()
|
BodyConfig()
|
||||||
)
|
)
|
||||||
.map {
|
.map {
|
||||||
|
@ -1,98 +0,0 @@
|
|||||||
package aqua.io
|
|
||||||
|
|
||||||
import aqua.Aqua
|
|
||||||
import aqua.linker.AquaModule
|
|
||||||
import aqua.parser.Ast
|
|
||||||
import aqua.parser.head.ImportExpr
|
|
||||||
import aqua.parser.lift.FileSpan
|
|
||||||
import cats.data.{EitherT, NonEmptyChain}
|
|
||||||
import cats.effect.Concurrent
|
|
||||||
import cats.syntax.apply._
|
|
||||||
import cats.syntax.functor._
|
|
||||||
import fs2.io.file.Files
|
|
||||||
|
|
||||||
import java.nio.file.{Path, Paths}
|
|
||||||
|
|
||||||
case class AquaFile(
|
|
||||||
id: FileModuleId,
|
|
||||||
imports: Map[String, FileSpan.Focus],
|
|
||||||
source: String,
|
|
||||||
ast: Ast[FileSpan.F]
|
|
||||||
) {
|
|
||||||
|
|
||||||
def module[F[_]: Concurrent, T](
|
|
||||||
transpile: Ast[FileSpan.F] => T => T,
|
|
||||||
importFrom: LazyList[Path]
|
|
||||||
): AquaFiles.ETC[F, AquaModule[FileModuleId, AquaFileError, T]] =
|
|
||||||
imports.map { case (k, v) =>
|
|
||||||
FileModuleId.resolve(v, Paths.get(k), id.file.getParent +: importFrom).map(_ -> v)
|
|
||||||
}.foldLeft[AquaFiles.ETC[F, AquaModule[FileModuleId, AquaFileError, T]]](
|
|
||||||
EitherT.rightT(
|
|
||||||
AquaModule(
|
|
||||||
id,
|
|
||||||
Map(),
|
|
||||||
transpile(ast)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) { case (modF, nextF) =>
|
|
||||||
EitherT((modF.value, nextF.value).mapN {
|
|
||||||
case (moduleV, Right(dependency)) =>
|
|
||||||
moduleV.map(m =>
|
|
||||||
m.copy(dependsOn =
|
|
||||||
m.dependsOn + dependency.map(FileNotFound(_, dependency._1.file, importFrom))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case (Right(_), Left(err)) =>
|
|
||||||
Left(NonEmptyChain(err))
|
|
||||||
case (Left(errs), Left(err)) =>
|
|
||||||
Left(errs.append(err))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
object AquaFile {
|
|
||||||
|
|
||||||
def readAst[F[_]: Files: Concurrent](
|
|
||||||
file: Path
|
|
||||||
): fs2.Stream[F, Either[AquaFileError, (String, Ast[FileSpan.F])]] =
|
|
||||||
FileOps
|
|
||||||
.readSourceText[F](file)
|
|
||||||
.map {
|
|
||||||
_.left
|
|
||||||
.map(t => FileSystemError(t))
|
|
||||||
}
|
|
||||||
.map(
|
|
||||||
_.flatMap(source =>
|
|
||||||
Aqua
|
|
||||||
.parseFileString(file.toString, source)
|
|
||||||
.map(source -> _)
|
|
||||||
.toEither
|
|
||||||
.left
|
|
||||||
.map(AquaScriptErrors(_))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def read[F[_]: Files: Concurrent](file: Path): EitherT[F, AquaFileError, AquaFile] =
|
|
||||||
EitherT(readAst[F](file).compile.last.map(_.getOrElse(Left(EmptyFileError(file))))).map {
|
|
||||||
case (source, ast) =>
|
|
||||||
AquaFile(
|
|
||||||
FileModuleId(file.toAbsolutePath.normalize()),
|
|
||||||
ast.head.tailForced
|
|
||||||
.map(_.head)
|
|
||||||
.collect { case ImportExpr(filename) =>
|
|
||||||
val fn = filename.value.drop(1).dropRight(1)
|
|
||||||
val focus = filename.unit._1.focus(1)
|
|
||||||
fn -> focus
|
|
||||||
}
|
|
||||||
.collect { case (a, Some(b)) =>
|
|
||||||
a -> b
|
|
||||||
}
|
|
||||||
.toList
|
|
||||||
.toMap,
|
|
||||||
source,
|
|
||||||
ast
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +1,7 @@
|
|||||||
import aqua.AquaCompiler
|
import aqua.backend.air.AirBackend
|
||||||
|
import aqua.backend.js.JavaScriptBackend
|
||||||
|
import aqua.backend.ts.TypeScriptBackend
|
||||||
|
import aqua.compiler.AquaCompiler
|
||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
import cats.effect.IO
|
import cats.effect.IO
|
||||||
import cats.effect.unsafe.implicits.global
|
import cats.effect.unsafe.implicits.global
|
||||||
@ -16,7 +19,7 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
|||||||
|
|
||||||
val bc = BodyConfig()
|
val bc = BodyConfig()
|
||||||
AquaCompiler
|
AquaCompiler
|
||||||
.compileFilesTo[IO](src, LazyList.empty, targetTs, AquaCompiler.TypescriptTarget, bc)
|
.compileFilesTo[IO](src, List.empty, targetTs, TypeScriptBackend, bc)
|
||||||
.unsafeRunSync()
|
.unsafeRunSync()
|
||||||
.leftMap { err =>
|
.leftMap { err =>
|
||||||
println(err)
|
println(err)
|
||||||
@ -28,7 +31,7 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
|||||||
Files.deleteIfExists(targetTsFile)
|
Files.deleteIfExists(targetTsFile)
|
||||||
|
|
||||||
AquaCompiler
|
AquaCompiler
|
||||||
.compileFilesTo[IO](src, LazyList.empty, targetJs, AquaCompiler.JavaScriptTarget, bc)
|
.compileFilesTo[IO](src, List.empty, targetJs, JavaScriptBackend, bc)
|
||||||
.unsafeRunSync()
|
.unsafeRunSync()
|
||||||
.leftMap { err =>
|
.leftMap { err =>
|
||||||
println(err)
|
println(err)
|
||||||
@ -40,7 +43,7 @@ class WriteFileSpec extends AnyFlatSpec with Matchers {
|
|||||||
Files.deleteIfExists(targetJsFile)
|
Files.deleteIfExists(targetJsFile)
|
||||||
|
|
||||||
AquaCompiler
|
AquaCompiler
|
||||||
.compileFilesTo[IO](src, LazyList.empty, targetAir, AquaCompiler.AirTarget, bc)
|
.compileFilesTo[IO](src, List.empty, targetAir, AirBackend, bc)
|
||||||
.unsafeRunSync()
|
.unsafeRunSync()
|
||||||
.leftMap { err =>
|
.leftMap { err =>
|
||||||
println(err)
|
println(err)
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package aqua
|
package aqua.compiler
|
||||||
|
|
||||||
import aqua.backend.Backend
|
import aqua.backend.Backend
|
||||||
import aqua.backend.air.AirBackend
|
import aqua.compiler.io._
|
||||||
import aqua.backend.js.JavaScriptBackend
|
|
||||||
import aqua.backend.ts.TypeScriptBackend
|
|
||||||
import aqua.io._
|
|
||||||
import aqua.linker.Linker
|
import aqua.linker.Linker
|
||||||
import aqua.model.AquaContext
|
import aqua.model.AquaContext
|
||||||
import aqua.model.transform.BodyConfig
|
import aqua.model.transform.BodyConfig
|
||||||
@ -61,14 +58,14 @@ object AquaCompiler extends LogSupport {
|
|||||||
*/
|
*/
|
||||||
def prepareFiles[F[_]: Files: Concurrent](
|
def prepareFiles[F[_]: Files: Concurrent](
|
||||||
srcPath: Path,
|
srcPath: Path,
|
||||||
imports: LazyList[Path],
|
imports: List[Path],
|
||||||
targetPath: Path
|
targetPath: Path
|
||||||
)(implicit aqum: Monoid[AquaContext]): F[ValidatedNec[String, Chain[Prepared]]] =
|
)(implicit aqum: Monoid[AquaContext]): F[ValidatedNec[String, Chain[Prepared]]] =
|
||||||
AquaFiles
|
AquaFiles
|
||||||
.readAndResolve[F, ValidatedNec[SemanticError[FileSpan.F], AquaContext]](
|
.readAndResolve[F, ValidatedNec[SemanticError[FileSpan.F], AquaContext]](
|
||||||
srcPath,
|
srcPath,
|
||||||
imports,
|
imports,
|
||||||
ast => _.andThen(ctx => Semantics.process(ast, ctx))
|
ast => context => context.andThen(ctx => Semantics.process(ast, ctx))
|
||||||
)
|
)
|
||||||
.value
|
.value
|
||||||
.map {
|
.map {
|
||||||
@ -104,17 +101,6 @@ object AquaCompiler extends LogSupport {
|
|||||||
"Semantic error"
|
"Semantic error"
|
||||||
}
|
}
|
||||||
|
|
||||||
def targetToBackend(target: CompileTarget): Backend = {
|
|
||||||
target match {
|
|
||||||
case TypescriptTarget =>
|
|
||||||
TypeScriptBackend
|
|
||||||
case JavaScriptTarget =>
|
|
||||||
JavaScriptBackend
|
|
||||||
case AirTarget =>
|
|
||||||
AirBackend
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def gatherResults[F[_]: Monad](
|
private def gatherResults[F[_]: Monad](
|
||||||
results: List[EitherT[F, String, Unit]]
|
results: List[EitherT[F, String, Unit]]
|
||||||
): F[Validated[NonEmptyChain[String], Chain[String]]] = {
|
): F[Validated[NonEmptyChain[String], Chain[String]]] = {
|
||||||
@ -138,15 +124,15 @@ object AquaCompiler extends LogSupport {
|
|||||||
|
|
||||||
def compileFilesTo[F[_]: Files: Concurrent](
|
def compileFilesTo[F[_]: Files: Concurrent](
|
||||||
srcPath: Path,
|
srcPath: Path,
|
||||||
imports: LazyList[Path],
|
imports: List[Path],
|
||||||
targetPath: Path,
|
targetPath: Path,
|
||||||
compileTo: CompileTarget,
|
backend: Backend,
|
||||||
bodyConfig: BodyConfig
|
bodyConfig: BodyConfig
|
||||||
): F[ValidatedNec[String, Chain[String]]] = {
|
): F[ValidatedNec[String, Chain[String]]] = {
|
||||||
import bodyConfig.aquaContextMonoid
|
import bodyConfig.aquaContextMonoid
|
||||||
prepareFiles(srcPath, imports, targetPath)
|
prepareFiles(srcPath, imports, targetPath)
|
||||||
.map(_.map(_.filter { p =>
|
.map(_.map(_.filter { p =>
|
||||||
val hasOutput = p.hasOutput(compileTo)
|
val hasOutput = p.hasOutput
|
||||||
if (!hasOutput) info(s"Source ${p.srcFile}: compilation OK (nothing to emit)")
|
if (!hasOutput) info(s"Source ${p.srcFile}: compilation OK (nothing to emit)")
|
||||||
hasOutput
|
hasOutput
|
||||||
}))
|
}))
|
||||||
@ -154,7 +140,6 @@ object AquaCompiler extends LogSupport {
|
|||||||
case Validated.Invalid(e) =>
|
case Validated.Invalid(e) =>
|
||||||
Applicative[F].pure(Validated.invalid(e))
|
Applicative[F].pure(Validated.invalid(e))
|
||||||
case Validated.Valid(preps) =>
|
case Validated.Valid(preps) =>
|
||||||
val backend = targetToBackend(compileTo)
|
|
||||||
val results = preps.toList
|
val results = preps.toList
|
||||||
.flatMap(p =>
|
.flatMap(p =>
|
||||||
backend.generate(p.context, bodyConfig).map { compiled =>
|
backend.generate(p.context, bodyConfig).map { compiled =>
|
@ -1,4 +1,4 @@
|
|||||||
package aqua
|
package aqua.compiler
|
||||||
|
|
||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import cats.data.NonEmptyList
|
import cats.data.NonEmptyList
|
@ -1,6 +1,5 @@
|
|||||||
package aqua
|
package aqua.compiler
|
||||||
|
|
||||||
import aqua.AquaCompiler.CompileTarget
|
|
||||||
import aqua.model.AquaContext
|
import aqua.model.AquaContext
|
||||||
import cats.data.Validated
|
import cats.data.Validated
|
||||||
|
|
||||||
@ -46,9 +45,7 @@ object Prepared {
|
|||||||
*/
|
*/
|
||||||
case class Prepared private (targetDir: Path, srcFile: Path, context: AquaContext) {
|
case class Prepared private (targetDir: Path, srcFile: Path, context: AquaContext) {
|
||||||
|
|
||||||
def hasOutput(target: CompileTarget): Boolean = target match {
|
def hasOutput: Boolean = context.funcs.nonEmpty
|
||||||
case _ => context.funcs.nonEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
def targetPath(fileName: String): Validated[Throwable, Path] =
|
def targetPath(fileName: String): Validated[Throwable, Path] =
|
||||||
Validated.catchNonFatal {
|
Validated.catchNonFatal {
|
131
compiler/src/main/scala/aqua/compiler/io/AquaFile.scala
Normal file
131
compiler/src/main/scala/aqua/compiler/io/AquaFile.scala
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
package aqua.compiler.io
|
||||||
|
|
||||||
|
import aqua.compiler.io.AquaFiles.ETC
|
||||||
|
import aqua.compiler.{CustomSyntaxError, SyntaxError}
|
||||||
|
import aqua.linker.AquaModule
|
||||||
|
import aqua.parser.head.ImportExpr
|
||||||
|
import aqua.parser.lift.FileSpan.F
|
||||||
|
import aqua.parser.lift.{FileSpan, LiftParser, Span}
|
||||||
|
import aqua.parser.{Ast, BlockIndentError, FuncReturnError, LexerError}
|
||||||
|
import cats.Eval
|
||||||
|
import cats.data.{EitherT, NonEmptyChain}
|
||||||
|
import cats.effect.Concurrent
|
||||||
|
import cats.parse.LocationMap
|
||||||
|
import cats.syntax.apply._
|
||||||
|
import cats.syntax.functor._
|
||||||
|
import fs2.io.file.Files
|
||||||
|
|
||||||
|
import java.nio.file.{Path, Paths}
|
||||||
|
import scala.collection.immutable
|
||||||
|
|
||||||
|
case class AquaFile(
|
||||||
|
id: FileModuleId,
|
||||||
|
imports: Map[String, FileSpan.Focus],
|
||||||
|
source: String,
|
||||||
|
ast: Ast[FileSpan.F]
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gathers all errors and results
|
||||||
|
*/
|
||||||
|
private def gatherResolvedResults[F[_]: Concurrent](
|
||||||
|
results: immutable.Iterable[EitherT[F, AquaFileError, (FileModuleId, FileNotFound)]]
|
||||||
|
): ETC[F, Map[FileModuleId, AquaFileError]] = {
|
||||||
|
results
|
||||||
|
.foldLeft[AquaFiles.ETC[F, Map[FileModuleId, AquaFileError]]](EitherT.rightT(Map())) {
|
||||||
|
case (files, nextFile) =>
|
||||||
|
EitherT((files.value, nextFile.value).mapN {
|
||||||
|
case (files, Right(resolvedImport)) =>
|
||||||
|
files.map(_ + resolvedImport)
|
||||||
|
case (Right(_), Left(err)) =>
|
||||||
|
Left(NonEmptyChain(err))
|
||||||
|
case (Left(errs), Left(err)) =>
|
||||||
|
Left(errs.append(err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def createModule[F[_]: Concurrent, T](
|
||||||
|
transpile: Ast[FileSpan.F] => T => T,
|
||||||
|
importFrom: List[Path]
|
||||||
|
): AquaFiles.ETC[F, AquaModule[FileModuleId, AquaFileError, T]] = {
|
||||||
|
val resolvedImports = imports.map { case (pathString, focus) =>
|
||||||
|
FileModuleId
|
||||||
|
.resolve(focus, Paths.get(pathString), id.file.getParent +: importFrom)
|
||||||
|
// 'FileNotFound' will be used later if there will be problems in compilation
|
||||||
|
.map(id => (id -> FileNotFound(focus, id.file, importFrom)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
importsWithInfo <- gatherResolvedResults(resolvedImports)
|
||||||
|
} yield AquaModule(
|
||||||
|
id,
|
||||||
|
importsWithInfo,
|
||||||
|
transpile(ast)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object AquaFile {
|
||||||
|
|
||||||
|
def parseAst(name: String, input: String): Either[AquaFileError, Ast[F]] = {
|
||||||
|
implicit val fileLift: LiftParser[FileSpan.F] = FileSpan.fileSpanLiftParser(name, input)
|
||||||
|
Ast
|
||||||
|
.fromString[FileSpan.F](input)
|
||||||
|
.leftMap(_.map {
|
||||||
|
case BlockIndentError(indent, message) => CustomSyntaxError(indent._1, message)
|
||||||
|
case FuncReturnError(point, message) => CustomSyntaxError(point._1, message)
|
||||||
|
case LexerError(pe) =>
|
||||||
|
val fileSpan =
|
||||||
|
FileSpan(
|
||||||
|
name,
|
||||||
|
input,
|
||||||
|
Eval.later(LocationMap(input)),
|
||||||
|
Span(pe.failedAtOffset, pe.failedAtOffset + 1)
|
||||||
|
)
|
||||||
|
SyntaxError(fileSpan, pe.expected)
|
||||||
|
})
|
||||||
|
.toEither
|
||||||
|
.left
|
||||||
|
.map(AquaScriptErrors(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
def read[F[_]: Files: Concurrent](file: Path): EitherT[F, AquaFileError, AquaFile] = {
|
||||||
|
for {
|
||||||
|
sourceOp <- EitherT.right(
|
||||||
|
FileOps
|
||||||
|
.readSourceText[F](file)
|
||||||
|
.map {
|
||||||
|
_.left
|
||||||
|
.map(t => FileSystemError(t))
|
||||||
|
}
|
||||||
|
.compile
|
||||||
|
.last
|
||||||
|
)
|
||||||
|
source <- EitherT.fromEither(sourceOp.getOrElse(Left(EmptyFileError(file))))
|
||||||
|
_ <- EitherT.fromEither(
|
||||||
|
if (source.isEmpty) Left(EmptyFileError(file): AquaFileError) else Right(())
|
||||||
|
)
|
||||||
|
ast <- EitherT.fromEither(parseAst(file.toString, source))
|
||||||
|
imports = ast.head.tailForced
|
||||||
|
.map(_.head)
|
||||||
|
.collect { case ImportExpr(filename) =>
|
||||||
|
val path = filename.value.drop(1).dropRight(1)
|
||||||
|
val focus = filename.unit._1.focus(1)
|
||||||
|
path -> focus
|
||||||
|
}
|
||||||
|
.collect { case (path, Some(focus)) =>
|
||||||
|
path -> focus
|
||||||
|
}
|
||||||
|
.toList
|
||||||
|
.toMap
|
||||||
|
} yield {
|
||||||
|
AquaFile(
|
||||||
|
FileModuleId(file.toAbsolutePath.normalize()),
|
||||||
|
imports,
|
||||||
|
source,
|
||||||
|
ast
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package aqua.io
|
package aqua.compiler.io
|
||||||
|
|
||||||
import aqua.AquaError
|
import aqua.compiler.AquaError
|
||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import cats.data.NonEmptyChain
|
import cats.data.NonEmptyChain
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package aqua.io
|
package aqua.compiler.io
|
||||||
|
|
||||||
import aqua.linker.Modules
|
import aqua.linker.Modules
|
||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
@ -58,13 +58,13 @@ object AquaFiles {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
def sourceModules[F[_]: Concurrent, T](
|
def createModules[F[_]: Concurrent, T](
|
||||||
sources: Chain[AquaFile],
|
sources: Chain[AquaFile],
|
||||||
importFromPaths: LazyList[Path],
|
importFromPaths: List[Path],
|
||||||
transpile: Ast[FileSpan.F] => T => T
|
transpile: Ast[FileSpan.F] => T => T
|
||||||
): ETC[F, Mods[T]] =
|
): ETC[F, Mods[T]] =
|
||||||
sources
|
sources
|
||||||
.map(_.module(transpile, importFromPaths))
|
.map(_.createModule(transpile, importFromPaths))
|
||||||
.foldLeft[ETC[F, Mods[T]]](
|
.foldLeft[ETC[F, Mods[T]]](
|
||||||
EitherT.rightT(Modules())
|
EitherT.rightT(Modules())
|
||||||
) { case (modulesF, modF) =>
|
) { case (modulesF, modF) =>
|
||||||
@ -76,14 +76,14 @@ object AquaFiles {
|
|||||||
|
|
||||||
def resolveModules[F[_]: Files: Concurrent, T](
|
def resolveModules[F[_]: Files: Concurrent, T](
|
||||||
modules: Modules[FileModuleId, AquaFileError, T],
|
modules: Modules[FileModuleId, AquaFileError, T],
|
||||||
importFromPaths: LazyList[Path],
|
importFromPaths: List[Path],
|
||||||
transpile: Ast[FileSpan.F] => T => T
|
transpile: Ast[FileSpan.F] => T => T
|
||||||
): ETC[F, Mods[T]] =
|
): ETC[F, Mods[T]] =
|
||||||
modules.dependsOn.map { case (moduleId, unresolvedErrors) =>
|
modules.dependsOn.map { case (moduleId, unresolvedErrors) =>
|
||||||
AquaFile
|
AquaFile
|
||||||
.read[F](moduleId.file)
|
.read[F](moduleId.file)
|
||||||
.leftMap(unresolvedErrors.prepend)
|
.leftMap(unresolvedErrors.prepend)
|
||||||
.flatMap(_.module(transpile, importFromPaths))
|
.flatMap(_.createModule(transpile, importFromPaths))
|
||||||
|
|
||||||
}.foldLeft[ETC[F, Mods[T]]](
|
}.foldLeft[ETC[F, Mods[T]]](
|
||||||
EitherT.rightT(modules)
|
EitherT.rightT(modules)
|
||||||
@ -100,13 +100,13 @@ object AquaFiles {
|
|||||||
|
|
||||||
def readAndResolve[F[_]: Files: Concurrent, T](
|
def readAndResolve[F[_]: Files: Concurrent, T](
|
||||||
sourcePath: Path,
|
sourcePath: Path,
|
||||||
importFromPaths: LazyList[Path],
|
importFromPaths: List[Path],
|
||||||
transpile: Ast[FileSpan.F] => T => T
|
transpile: Ast[FileSpan.F] => T => T
|
||||||
): ETC[F, Mods[T]] =
|
): ETC[F, Mods[T]] =
|
||||||
for {
|
for {
|
||||||
srcs <- readSources(sourcePath)
|
sources <- readSources(sourcePath)
|
||||||
srcMods <- sourceModules(srcs, importFromPaths, transpile)
|
sourceModules <- createModules(sources, importFromPaths, transpile)
|
||||||
resMods <- resolveModules(srcMods, importFromPaths, transpile)
|
resolvedModules <- resolveModules(sourceModules, importFromPaths, transpile)
|
||||||
} yield resMods
|
} yield resolvedModules
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package aqua.io
|
package aqua.compiler.io
|
||||||
|
|
||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
@ -7,12 +7,12 @@ import cats.syntax.applicative._
|
|||||||
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
case class FileModuleId(file: Path) {}
|
case class FileModuleId(file: Path)
|
||||||
|
|
||||||
object FileModuleId {
|
object FileModuleId {
|
||||||
|
|
||||||
private def findFirstF[F[_]: Concurrent](
|
private def findFirstF[F[_]: Concurrent](
|
||||||
in: LazyList[Path],
|
in: List[Path],
|
||||||
notFound: EitherT[F, AquaFileError, FileModuleId]
|
notFound: EitherT[F, AquaFileError, FileModuleId]
|
||||||
): EitherT[F, AquaFileError, FileModuleId] =
|
): EitherT[F, AquaFileError, FileModuleId] =
|
||||||
in.headOption.fold(notFound)(p =>
|
in.headOption.fold(notFound)(p =>
|
||||||
@ -31,10 +31,13 @@ object FileModuleId {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a file existed in the list of possible paths
|
||||||
|
*/
|
||||||
def resolve[F[_]: Concurrent](
|
def resolve[F[_]: Concurrent](
|
||||||
focus: FileSpan.Focus,
|
focus: FileSpan.Focus,
|
||||||
src: Path,
|
src: Path,
|
||||||
imports: LazyList[Path]
|
imports: List[Path]
|
||||||
): EitherT[F, AquaFileError, FileModuleId] =
|
): EitherT[F, AquaFileError, FileModuleId] =
|
||||||
findFirstF(
|
findFirstF(
|
||||||
imports
|
imports
|
@ -1,4 +1,4 @@
|
|||||||
package aqua.io
|
package aqua.compiler.io
|
||||||
|
|
||||||
import cats.data.EitherT
|
import cats.data.EitherT
|
||||||
import cats.effect.Concurrent
|
import cats.effect.Concurrent
|
@ -1,3 +1,8 @@
|
|||||||
package aqua.linker
|
package aqua.linker
|
||||||
|
|
||||||
|
// HACK: here E is a FileNotFound error with Focus that the code will 'throw'
|
||||||
|
// if not found it in the list of loaded modules in `Modules` class.
|
||||||
|
// Essentially this error is a container with import information
|
||||||
|
// and a future error if the file for this import is not found
|
||||||
|
// TODO: fix it
|
||||||
case class AquaModule[I, E, T](id: I, dependsOn: Map[I, E], body: T => T)
|
case class AquaModule[I, E, T](id: I, dependsOn: Map[I, E], body: T => T)
|
||||||
|
@ -26,12 +26,13 @@ object Linker extends LogSupport {
|
|||||||
Left(cycleError(postpone))
|
Left(cycleError(postpone))
|
||||||
else {
|
else {
|
||||||
val folded = canHandle.foldLeft(proc) { case (acc, m) =>
|
val folded = canHandle.foldLeft(proc) { case (acc, m) =>
|
||||||
debug(m.id + " dependsOn " + m.dependsOn.keySet)
|
val importKeys = m.dependsOn.keySet
|
||||||
|
debug(m.id + " dependsOn " + importKeys)
|
||||||
val deps: T => T =
|
val deps: T => T =
|
||||||
m.dependsOn.keySet.map(acc).foldLeft[T => T](identity) { case (fAcc, f) =>
|
importKeys.map(acc).foldLeft[T => T](identity) { case (fAcc, f) =>
|
||||||
debug("COMBINING ONE TIME ")
|
debug("COMBINING ONE TIME ")
|
||||||
t => {
|
t => {
|
||||||
debug(s"call combine ${t}")
|
debug(s"call combine $t")
|
||||||
fAcc(t) |+| f(t)
|
fAcc(t) |+| f(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,17 +9,18 @@ case class Modules[I, E, T](
|
|||||||
exports: Set[I] = Set.empty[I]
|
exports: Set[I] = Set.empty[I]
|
||||||
) {
|
) {
|
||||||
|
|
||||||
def add(m: AquaModule[I, E, T], export: Boolean = false): Modules[I, E, T] =
|
def add(aquaModule: AquaModule[I, E, T], export: Boolean = false): Modules[I, E, T] =
|
||||||
if (loaded.contains(m.id)) this
|
if (loaded.contains(aquaModule.id)) this
|
||||||
else
|
else
|
||||||
copy(
|
copy(
|
||||||
loaded = loaded + (m.id -> m),
|
loaded = loaded + (aquaModule.id -> aquaModule),
|
||||||
dependsOn = m.dependsOn.foldLeft(dependsOn - m.id) {
|
dependsOn = aquaModule.dependsOn.foldLeft(dependsOn - aquaModule.id) {
|
||||||
case (deps, (mId, _)) if loaded.contains(mId) || mId == m.id => deps
|
case (deps, (moduleId, _)) if loaded.contains(moduleId) || moduleId == aquaModule.id =>
|
||||||
case (deps, (mId, err)) =>
|
deps
|
||||||
deps.updatedWith(mId)(_.fold(NonEmptyChain.one(err))(_.append(err)).some)
|
case (deps, (moduleId, err)) =>
|
||||||
|
deps.updatedWith(moduleId)(_.fold(NonEmptyChain.one(err))(_.append(err)).some)
|
||||||
},
|
},
|
||||||
exports = if (export) exports + m.id else exports
|
exports = if (export) exports + aquaModule.id else exports
|
||||||
)
|
)
|
||||||
|
|
||||||
def isResolved: Boolean = dependsOn.isEmpty
|
def isResolved: Boolean = dependsOn.isEmpty
|
||||||
|
@ -1 +1 @@
|
|||||||
sbt.version=1.5.1
|
sbt.version=1.5.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user