mirror of
https://github.com/fluencelabs/aqua.git
synced 2024-12-04 22:50:18 +00:00
Module and Use expressions (#245)
* Module and Use expressions * UseFromExpr * ImportFromExpr * PubExpr * Export, declares * Collecting all the needed info WIP * Got all the needed data * Tests fixed * HeaderSem * HeaderSem wip * Everything except `export`/`declares` should be working * Compile bug fixed * Fix readme: cli/assembly * Handle declares, exports * Compile only exports in AquaRes * Call services imported from modules * Import consts, types, services from modules * Resolve arrows from modules * Bugfix
This commit is contained in:
parent
296c64836d
commit
b9af20339b
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,8 @@
|
|||||||
.idea
|
.idea
|
||||||
.bsp
|
.bsp
|
||||||
|
.metals
|
||||||
|
.vscode
|
||||||
|
.bloop
|
||||||
|
metals.sbt
|
||||||
target
|
target
|
||||||
project/target
|
project/target
|
@ -59,3 +59,4 @@ rewrite {
|
|||||||
SortImports
|
SortImports
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
#runner.dialect = scala3
|
||||||
|
@ -16,7 +16,7 @@ Please refer to [Aqua Book](https://doc.fluence.dev/aqua-book/) to learn how to
|
|||||||
|
|
||||||
## Compiler CLI
|
## Compiler CLI
|
||||||
|
|
||||||
To build the Aqua compiler, clone the repo & run `sbt assembly`,
|
To build the Aqua compiler, clone the repo & run `sbt cli/assembly`,
|
||||||
or simply download the latest JAR file from the [releases](https://github.com/fluencelabs/aqua/releases) page.
|
or simply download the latest JAR file from the [releases](https://github.com/fluencelabs/aqua/releases) page.
|
||||||
|
|
||||||
It requires `java` to run Aqua compiler from the command line:
|
It requires `java` to run Aqua compiler from the command line:
|
||||||
|
@ -17,7 +17,7 @@ val declineV = "2.1.0"
|
|||||||
name := "aqua-hll"
|
name := "aqua-hll"
|
||||||
|
|
||||||
val commons = Seq(
|
val commons = Seq(
|
||||||
baseAquaVersion := "0.1.13",
|
baseAquaVersion := "0.1.14",
|
||||||
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
|
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
|
||||||
scalaVersion := dottyVersion,
|
scalaVersion := dottyVersion,
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
|
@ -5,7 +5,7 @@ import aqua.files.FileModuleId
|
|||||||
import aqua.io.AquaFileError
|
import aqua.io.AquaFileError
|
||||||
import aqua.parser.lift.FileSpan
|
import aqua.parser.lift.FileSpan
|
||||||
import aqua.parser.{BlockIndentError, FuncReturnError, LexerError}
|
import aqua.parser.{BlockIndentError, FuncReturnError, LexerError}
|
||||||
import aqua.semantics.{RulesViolated, WrongAST}
|
import aqua.semantics.{HeaderError, RulesViolated, WrongAST}
|
||||||
import cats.Show
|
import cats.Show
|
||||||
|
|
||||||
object ErrorRendering {
|
object ErrorRendering {
|
||||||
@ -61,6 +61,11 @@ object ErrorRendering {
|
|||||||
.focus(2)
|
.focus(2)
|
||||||
.map(_.toConsoleStr(message, Console.CYAN))
|
.map(_.toConsoleStr(message, Console.CYAN))
|
||||||
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
||||||
|
case HeaderError(token, message) =>
|
||||||
|
token.unit._1
|
||||||
|
.focus(2)
|
||||||
|
.map(_.toConsoleStr(message, Console.CYAN))
|
||||||
|
.getOrElse("(Dup error, but offset is beyond the script)") + "\n"
|
||||||
case WrongAST(ast) =>
|
case WrongAST(ast) =>
|
||||||
s"Semantic error"
|
s"Semantic error"
|
||||||
|
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package aqua.files
|
package aqua.files
|
||||||
|
|
||||||
import fs2.io.file.Path
|
import fs2.io.file.Path
|
||||||
|
import cats.Order
|
||||||
|
|
||||||
case class FileModuleId private (file: Path)
|
case class FileModuleId private (file: Path)
|
||||||
|
|
||||||
object FileModuleId {
|
object FileModuleId {
|
||||||
|
|
||||||
|
implicit object FileModuleIdOrder extends Order[FileModuleId] {
|
||||||
|
|
||||||
|
override def compare(x: FileModuleId, y: FileModuleId): Int =
|
||||||
|
x.file.toString.compareTo(y.file.toString)
|
||||||
|
}
|
||||||
|
|
||||||
def apply(file: Path): FileModuleId =
|
def apply(file: Path): FileModuleId =
|
||||||
new FileModuleId(file.absolute.normalize)
|
new FileModuleId(file.absolute.normalize)
|
||||||
}
|
}
|
||||||
|
@ -6,19 +6,22 @@ import aqua.model.AquaContext
|
|||||||
import aqua.model.transform.TransformConfig
|
import aqua.model.transform.TransformConfig
|
||||||
import aqua.model.transform.res.AquaRes
|
import aqua.model.transform.res.AquaRes
|
||||||
import aqua.parser.lift.LiftParser
|
import aqua.parser.lift.LiftParser
|
||||||
|
import aqua.parser.Ast
|
||||||
import aqua.semantics.Semantics
|
import aqua.semantics.Semantics
|
||||||
|
import aqua.semantics.header.HeaderSem
|
||||||
import cats.data.Validated.{validNec, Invalid, Valid}
|
import cats.data.Validated.{validNec, Invalid, Valid}
|
||||||
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
|
import cats.data.{Chain, NonEmptyChain, NonEmptyMap, Validated, ValidatedNec}
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
import cats.syntax.flatMap.*
|
import cats.syntax.flatMap.*
|
||||||
import cats.syntax.functor.*
|
import cats.syntax.functor.*
|
||||||
import cats.syntax.traverse.*
|
import cats.syntax.traverse.*
|
||||||
import cats.{Comonad, Monad}
|
import cats.syntax.monoid.*
|
||||||
|
import cats.{Comonad, Monad, Monoid, Order}
|
||||||
import scribe.Logging
|
import scribe.Logging
|
||||||
|
|
||||||
object AquaCompiler extends Logging {
|
object AquaCompiler extends Logging {
|
||||||
|
|
||||||
def compile[F[_]: Monad, E, I, S[_]: Comonad](
|
def compile[F[_]: Monad, E, I: Order, S[_]: Comonad](
|
||||||
sources: AquaSources[F, E, I],
|
sources: AquaSources[F, E, I],
|
||||||
liftI: (I, String) => LiftParser[S],
|
liftI: (I, String) => LiftParser[S],
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
@ -26,27 +29,56 @@ object AquaCompiler extends Logging {
|
|||||||
): F[ValidatedNec[AquaError[I, E, S], Chain[AquaCompiled[I]]]] = {
|
): F[ValidatedNec[AquaError[I, E, S], Chain[AquaCompiled[I]]]] = {
|
||||||
import config.aquaContextMonoid
|
import config.aquaContextMonoid
|
||||||
type Err = AquaError[I, E, S]
|
type Err = AquaError[I, E, S]
|
||||||
|
type Ctx = NonEmptyMap[I, AquaContext]
|
||||||
|
type ValidatedCtx = ValidatedNec[Err, Ctx]
|
||||||
|
|
||||||
new AquaParser[F, E, I, S](sources, liftI)
|
new AquaParser[F, E, I, S](sources, liftI)
|
||||||
.resolve[ValidatedNec[Err, AquaContext]] { ast => context =>
|
.resolve[ValidatedCtx](mod =>
|
||||||
context.andThen { ctx =>
|
context =>
|
||||||
Semantics
|
// Context with prepared imports
|
||||||
.process(ast, ctx)
|
context.andThen(ctx =>
|
||||||
.leftMap(_.map[Err](CompileError(_)))
|
// To manage imports, exports run HeaderSem
|
||||||
}
|
HeaderSem
|
||||||
}
|
.sem(
|
||||||
.map {
|
mod.imports.view
|
||||||
case Valid(modules) =>
|
.mapValues(ctx(_))
|
||||||
Linker.link[I, AquaError[I, E, S], ValidatedNec[Err, AquaContext]](
|
.collect { case (fn, Some(fc)) => fn -> fc }
|
||||||
modules,
|
.toMap,
|
||||||
cycle => CycleError[I, E, S](cycle.map(_.id))
|
mod.body.head
|
||||||
) match {
|
)
|
||||||
case Valid(filesWithContext) =>
|
.andThen { headerSem =>
|
||||||
|
// Analyze the body, with prepared initial context
|
||||||
|
Semantics
|
||||||
|
.process(
|
||||||
|
mod.body,
|
||||||
|
headerSem.initCtx
|
||||||
|
)
|
||||||
|
// Handle exports, declares – finalize the resulting context
|
||||||
|
.andThen(headerSem.finCtx)
|
||||||
|
.map(rc => NonEmptyMap.one(mod.id, rc))
|
||||||
|
}
|
||||||
|
// The whole chain returns a semantics error finally
|
||||||
|
.leftMap(_.map[Err](CompileError(_)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
_.andThen(modules =>
|
||||||
|
Linker
|
||||||
|
.link[I, AquaError[I, E, S], ValidatedCtx](
|
||||||
|
modules,
|
||||||
|
cycle => CycleError[I, E, S](cycle.map(_.id)),
|
||||||
|
// By default, provide an empty context for this module's id
|
||||||
|
i => validNec(NonEmptyMap.one(i, Monoid.empty[AquaContext]))
|
||||||
|
)
|
||||||
|
.andThen { filesWithContext =>
|
||||||
filesWithContext
|
filesWithContext
|
||||||
.foldLeft[ValidatedNec[Err, Chain[AquaProcessed[I]]]](
|
.foldLeft[ValidatedNec[Err, Chain[AquaProcessed[I]]]](
|
||||||
validNec(Chain.nil)
|
validNec(Chain.nil)
|
||||||
) {
|
) {
|
||||||
case (acc, (i, Valid(context))) =>
|
case (acc, (i, Valid(context))) =>
|
||||||
acc combine validNec(Chain.one(AquaProcessed(i, context)))
|
acc combine validNec(
|
||||||
|
Chain.fromSeq(context.toNel.toList.map { case (i, c) => AquaProcessed(i, c) })
|
||||||
|
)
|
||||||
case (acc, (_, Invalid(errs))) =>
|
case (acc, (_, Invalid(errs))) =>
|
||||||
acc combine Invalid(errs)
|
acc combine Invalid(errs)
|
||||||
}
|
}
|
||||||
@ -56,13 +88,12 @@ object AquaCompiler extends Logging {
|
|||||||
AquaCompiled(ap.id, compiled)
|
AquaCompiled(ap.id, compiled)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
case i @ Invalid(_) => i
|
}
|
||||||
}
|
)
|
||||||
case i @ Invalid(_) => i
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def compileTo[F[_]: Monad, E, I, S[_]: Comonad, T](
|
def compileTo[F[_]: Monad, E, I: Order, S[_]: Comonad, T](
|
||||||
sources: AquaSources[F, E, I],
|
sources: AquaSources[F, E, I],
|
||||||
liftI: (I, String) => LiftParser[S],
|
liftI: (I, String) => LiftParser[S],
|
||||||
backend: Backend,
|
backend: Backend,
|
||||||
|
@ -2,7 +2,7 @@ package aqua.compiler
|
|||||||
|
|
||||||
import aqua.linker.{AquaModule, Modules}
|
import aqua.linker.{AquaModule, Modules}
|
||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
import aqua.parser.head.ImportExpr
|
import aqua.parser.head.{FilenameExpr, ImportExpr}
|
||||||
import aqua.parser.lift.LiftParser
|
import aqua.parser.lift.LiftParser
|
||||||
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
|
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
|
||||||
import cats.syntax.applicative.*
|
import cats.syntax.applicative.*
|
||||||
@ -32,23 +32,37 @@ class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Resolve imports (not parse, just resolve) of the given file
|
// Resolve imports (not parse, just resolve) of the given file
|
||||||
def resolveImports(id: I, ast: Ast[S]): F[ValidatedNec[Err, Map[I, Err]]] =
|
def resolveImports(id: I, ast: Body): F[ValidatedNec[Err, AquaModule[I, Err, Body]]] =
|
||||||
ast.head.tailForced
|
ast.head.tailForced
|
||||||
.map(_.head)
|
.map(_.head)
|
||||||
.collect { case ImportExpr(filename) =>
|
.collect { case fe: FilenameExpr[F] =>
|
||||||
sources
|
sources
|
||||||
.resolveImport(id, filename.value.drop(1).dropRight(1))
|
.resolveImport(id, fe.fileValue)
|
||||||
.map(
|
.map(
|
||||||
_.bimap(
|
_.bimap(
|
||||||
_.map(ResolveImportsErr(id, filename, _)),
|
_.map[Err](ResolveImportsErr(id, fe.filename, _)),
|
||||||
importId => Chain.one[(I, Err)](importId -> ImportErr(filename))
|
importId =>
|
||||||
|
Chain.one[(I, (String, Err))](importId -> (fe.fileValue, ImportErr(fe.filename)))
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.traverse(identity)
|
.traverse(identity)
|
||||||
.map(
|
.map(
|
||||||
_.foldLeft(Validated.validNec[Err, Chain[(I, Err)]](Chain.nil))(_ combine _)
|
_.foldLeft(Validated.validNec[Err, Chain[(I, (String, Err))]](Chain.nil))(_ combine _).map {
|
||||||
.map(_.toList.toMap)
|
collected =>
|
||||||
|
AquaModule[I, Err, Body](
|
||||||
|
id,
|
||||||
|
// How filenames correspond to the resolved IDs
|
||||||
|
collected.map { case (i, (fn, _)) =>
|
||||||
|
fn -> i
|
||||||
|
}.toList.toMap[String, I],
|
||||||
|
// Resolved IDs to errors that point to the import in source code
|
||||||
|
collected.map { case (i, (_, err)) =>
|
||||||
|
i -> err
|
||||||
|
}.toList.toMap[I, Err],
|
||||||
|
ast
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Parse sources, convert to modules
|
// Parse sources, convert to modules
|
||||||
@ -56,7 +70,7 @@ class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
|
|||||||
parseSources.flatMap {
|
parseSources.flatMap {
|
||||||
case Validated.Valid(srcs) =>
|
case Validated.Valid(srcs) =>
|
||||||
srcs.traverse { case (id, ast) =>
|
srcs.traverse { case (id, ast) =>
|
||||||
resolveImports(id, ast).map(_.map(AquaModule(id, _, ast)).map(Chain.one))
|
resolveImports(id, ast).map(_.map(Chain.one))
|
||||||
}.map(
|
}.map(
|
||||||
_.foldLeft(Validated.validNec[Err, Chain[AquaModule[I, Err, Body]]](Chain.empty))(
|
_.foldLeft(Validated.validNec[Err, Chain[AquaModule[I, Err, Body]]](Chain.empty))(
|
||||||
_ combine _
|
_ combine _
|
||||||
@ -66,7 +80,7 @@ class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
|
|||||||
Validated.invalid[NonEmptyChain[Err], Chain[AquaModule[I, Err, Body]]](errs).pure[F]
|
Validated.invalid[NonEmptyChain[Err], Chain[AquaModule[I, Err, Body]]](errs).pure[F]
|
||||||
}.map(_.map(_.foldLeft(Modules[I, Err, Body]())(_.add(_, toExport = true))))
|
}.map(_.map(_.foldLeft(Modules[I, Err, Body]())(_.add(_, toExport = true))))
|
||||||
|
|
||||||
def loadModule(imp: I): F[ValidatedNec[Err, AquaModule[I, Err, Ast[S]]]] =
|
def loadModule(imp: I): F[ValidatedNec[Err, AquaModule[I, Err, Body]]] =
|
||||||
sources
|
sources
|
||||||
.load(imp)
|
.load(imp)
|
||||||
.map(_.leftMap(_.map[Err](SourcesErr(_))).andThen { src =>
|
.map(_.leftMap(_.map[Err](SourcesErr(_))).andThen { src =>
|
||||||
@ -75,7 +89,7 @@ class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
|
|||||||
})
|
})
|
||||||
.flatMap {
|
.flatMap {
|
||||||
case Validated.Valid(ast) =>
|
case Validated.Valid(ast) =>
|
||||||
resolveImports(imp, ast).map(_.map(AquaModule(imp, _, ast)))
|
resolveImports(imp, ast)
|
||||||
case Validated.Invalid(errs) =>
|
case Validated.Invalid(errs) =>
|
||||||
Validated.invalid[NonEmptyChain[Err], AquaModule[I, Err, Ast[S]]](errs).pure[F]
|
Validated.invalid[NonEmptyChain[Err], AquaModule[I, Err, Ast[S]]](errs).pure[F]
|
||||||
}
|
}
|
||||||
@ -106,7 +120,9 @@ class AquaParser[F[_]: Monad, E, I, S[_]: Comonad](
|
|||||||
case err => err.pure[F]
|
case err => err.pure[F]
|
||||||
}
|
}
|
||||||
|
|
||||||
def resolve[T](transpile: Ast[S] => T => T): F[ValidatedNec[Err, Modules[I, Err, T => T]]] =
|
def resolve[T](
|
||||||
resolveSources.map(_.map(_.map(transpile)))
|
transpile: AquaModule[I, Err, Body] => T => T
|
||||||
|
): F[ValidatedNec[Err, Modules[I, Err, T => T]]] =
|
||||||
|
resolveSources.map(_.map(_.mapModuleToBody(transpile)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@ package aqua.compiler
|
|||||||
import aqua.model.AquaContext
|
import aqua.model.AquaContext
|
||||||
|
|
||||||
case class AquaProcessed[I](id: I, context: AquaContext) {
|
case class AquaProcessed[I](id: I, context: AquaContext) {
|
||||||
def hasOutput: Boolean = context.funcs.nonEmpty
|
def hasOutput: Boolean = context.funcs.nonEmpty || context.services.nonEmpty
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package aqua.linker
|
package aqua.linker
|
||||||
|
|
||||||
case class AquaModule[I, E, T](id: I, dependsOn: Map[I, E], body: T) {
|
case class AquaModule[I, E, T](id: I, imports: Map[String, I], dependsOn: Map[I, E], body: T) {
|
||||||
def map[TT](f: T => TT): AquaModule[I, E, TT] = copy(body = f(body))
|
def map[TT](f: T => TT): AquaModule[I, E, TT] = copy(body = f(body))
|
||||||
|
|
||||||
|
def mapWithId[TT](f: (I, T) => TT): AquaModule[I, E, TT] = copy(body = f(id, body))
|
||||||
|
|
||||||
def mapErr[EE](f: E => EE): AquaModule[I, EE, T] =
|
def mapErr[EE](f: E => EE): AquaModule[I, EE, T] =
|
||||||
copy(dependsOn = dependsOn.view.mapValues(f).toMap)
|
copy(dependsOn = dependsOn.view.mapValues(f).toMap)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package aqua.linker
|
|||||||
|
|
||||||
import cats.data.{NonEmptyChain, Validated, ValidatedNec}
|
import cats.data.{NonEmptyChain, Validated, ValidatedNec}
|
||||||
import cats.kernel.{Monoid, Semigroup}
|
import cats.kernel.{Monoid, Semigroup}
|
||||||
import cats.syntax.monoid._
|
import cats.syntax.semigroup._
|
||||||
import scribe.Logging
|
import scribe.Logging
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
@ -50,9 +50,10 @@ object Linker extends Logging {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def link[I, E, T: Monoid](
|
def link[I, E, T: Semigroup](
|
||||||
modules: Modules[I, E, T => T],
|
modules: Modules[I, E, T => T],
|
||||||
cycleError: List[AquaModule[I, E, T => T]] => E
|
cycleError: List[AquaModule[I, E, T => T]] => E,
|
||||||
|
empty: I => T
|
||||||
): ValidatedNec[E, Map[I, T]] =
|
): ValidatedNec[E, Map[I, T]] =
|
||||||
if (modules.dependsOn.nonEmpty) Validated.invalid(modules.dependsOn.values.reduce(_ ++ _))
|
if (modules.dependsOn.nonEmpty) Validated.invalid(modules.dependsOn.values.reduce(_ ++ _))
|
||||||
else {
|
else {
|
||||||
@ -60,7 +61,7 @@ object Linker extends Logging {
|
|||||||
|
|
||||||
Validated.fromEither(
|
Validated.fromEither(
|
||||||
result
|
result
|
||||||
.map(_.view.filterKeys(modules.exports).mapValues(_.apply(Monoid[T].empty)).toMap)
|
.map(_.collect { case (i, f) if modules.exports(i) => i -> f(empty(i)) })
|
||||||
.left
|
.left
|
||||||
.map(NonEmptyChain.one)
|
.map(NonEmptyChain.one)
|
||||||
)
|
)
|
||||||
|
@ -28,6 +28,9 @@ case class Modules[I, E, T](
|
|||||||
def map[TT](f: T => TT): Modules[I, E, TT] =
|
def map[TT](f: T => TT): Modules[I, E, TT] =
|
||||||
copy(loaded = loaded.view.mapValues(_.map(f)).toMap)
|
copy(loaded = loaded.view.mapValues(_.map(f)).toMap)
|
||||||
|
|
||||||
|
def mapModuleToBody[TT](f: AquaModule[I, E, T] => TT): Modules[I, E, TT] =
|
||||||
|
copy(loaded = loaded.view.mapValues(v => v.map(_ => f(v))).toMap)
|
||||||
|
|
||||||
def mapErr[EE](f: E => EE): Modules[I, EE, T] =
|
def mapErr[EE](f: E => EE): Modules[I, EE, T] =
|
||||||
copy(
|
copy(
|
||||||
loaded = loaded.view.mapValues(_.mapErr(f)).toMap,
|
loaded = loaded.view.mapValues(_.mapErr(f)).toMap,
|
||||||
|
@ -15,6 +15,7 @@ class LinkerSpec extends AnyFlatSpec with Matchers {
|
|||||||
.add(
|
.add(
|
||||||
AquaModule[String, String, String => String](
|
AquaModule[String, String, String => String](
|
||||||
"mod1",
|
"mod1",
|
||||||
|
Map.empty,
|
||||||
Map("mod2" -> "unresolved mod2 in mod1"),
|
Map("mod2" -> "unresolved mod2 in mod1"),
|
||||||
_ ++ " | mod1"
|
_ ++ " | mod1"
|
||||||
),
|
),
|
||||||
@ -24,17 +25,19 @@ class LinkerSpec extends AnyFlatSpec with Matchers {
|
|||||||
|
|
||||||
Linker.link[String, String, String](
|
Linker.link[String, String, String](
|
||||||
withMod1,
|
withMod1,
|
||||||
cycle => cycle.map(_.id).mkString(" -> ")
|
cycle => cycle.map(_.id).mkString(" -> "),
|
||||||
|
_ => ""
|
||||||
) should be(Validated.invalidNec("unresolved mod2 in mod1"))
|
) should be(Validated.invalidNec("unresolved mod2 in mod1"))
|
||||||
|
|
||||||
val withMod2 =
|
val withMod2 =
|
||||||
withMod1.add(AquaModule("mod2", Map.empty, _ ++ " | mod2"))
|
withMod1.add(AquaModule("mod2", Map.empty, Map.empty, _ ++ " | mod2"))
|
||||||
|
|
||||||
withMod2.isResolved should be(true)
|
withMod2.isResolved should be(true)
|
||||||
|
|
||||||
Linker.link[String, String, String](
|
Linker.link[String, String, String](
|
||||||
withMod2,
|
withMod2,
|
||||||
cycle => cycle.map(_.id + "?").mkString(" -> ")
|
cycle => cycle.map(_.id + "?").mkString(" -> "),
|
||||||
|
_ => ""
|
||||||
) should be(Validated.validNec(Map("mod1" -> " | mod2 | mod1")))
|
) should be(Validated.validNec(Map("mod1" -> " | mod2 | mod1")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,6 +12,9 @@ import scribe.Logging
|
|||||||
import scala.collection.immutable.SortedMap
|
import scala.collection.immutable.SortedMap
|
||||||
|
|
||||||
case class AquaContext(
|
case class AquaContext(
|
||||||
|
module: Option[String],
|
||||||
|
declares: Set[String],
|
||||||
|
exports: Option[AquaContext],
|
||||||
funcs: Map[String, FuncCallable],
|
funcs: Map[String, FuncCallable],
|
||||||
types: Map[String, Type],
|
types: Map[String, Type],
|
||||||
values: Map[String, ValueModel],
|
values: Map[String, ValueModel],
|
||||||
@ -23,6 +26,26 @@ case class AquaContext(
|
|||||||
private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) =
|
private def prefixFirst[T](prefix: String, pair: (String, T)): (String, T) =
|
||||||
(prefix + pair._1, pair._2)
|
(prefix + pair._1, pair._2)
|
||||||
|
|
||||||
|
def pick(
|
||||||
|
name: String,
|
||||||
|
rename: Option[String],
|
||||||
|
declared: Boolean = module.nonEmpty
|
||||||
|
): Option[AquaContext] =
|
||||||
|
Option
|
||||||
|
.when(!declared || declares(name)) {
|
||||||
|
val targetName = rename.getOrElse(name)
|
||||||
|
def getter[T](g: AquaContext => Map[String, T]): Map[String, T] =
|
||||||
|
g(this).get(name).map(targetName -> _).map(Map(_)).getOrElse(Map.empty)
|
||||||
|
AquaContext.blank.copy(
|
||||||
|
funcs = getter(_.funcs),
|
||||||
|
types = getter(_.types),
|
||||||
|
values = getter(_.values),
|
||||||
|
abilities = getter(_.abilities),
|
||||||
|
services = getter(_.services)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.filter(_.`type`(name).nonEmpty)
|
||||||
|
|
||||||
def allTypes(prefix: String = ""): Map[String, Type] =
|
def allTypes(prefix: String = ""): Map[String, Type] =
|
||||||
abilities
|
abilities
|
||||||
.foldLeft(types) { case (ts, (k, v)) =>
|
.foldLeft(types) { case (ts, (k, v)) =>
|
||||||
@ -73,7 +96,8 @@ object AquaContext extends Logging {
|
|||||||
implicit val aquaContextMonoid: Monoid[AquaContext]
|
implicit val aquaContextMonoid: Monoid[AquaContext]
|
||||||
}
|
}
|
||||||
|
|
||||||
val blank: AquaContext = AquaContext(Map.empty, Map.empty, Map.empty, Map.empty, Map.empty)
|
val blank: AquaContext =
|
||||||
|
AquaContext(None, Set.empty, None, Map.empty, Map.empty, Map.empty, Map.empty, Map.empty)
|
||||||
|
|
||||||
def implicits(init: AquaContext): Implicits = new Implicits {
|
def implicits(init: AquaContext): Implicits = new Implicits {
|
||||||
|
|
||||||
@ -83,8 +107,15 @@ object AquaContext extends Logging {
|
|||||||
override def empty: AquaContext =
|
override def empty: AquaContext =
|
||||||
init
|
init
|
||||||
|
|
||||||
|
// TODO is it the right way?
|
||||||
override def combine(x: AquaContext, y: AquaContext): AquaContext =
|
override def combine(x: AquaContext, y: AquaContext): AquaContext =
|
||||||
AquaContext(
|
AquaContext(
|
||||||
|
x.module.orElse(y.module),
|
||||||
|
x.declares ++ y.declares,
|
||||||
|
x.exports
|
||||||
|
.flatMap(xe => y.exports.map(combine(xe, _)))
|
||||||
|
.orElse(x.exports)
|
||||||
|
.orElse(y.exports),
|
||||||
x.funcs ++ y.funcs,
|
x.funcs ++ y.funcs,
|
||||||
x.types ++ y.types,
|
x.types ++ y.types,
|
||||||
x.values ++ y.values,
|
x.values ++ y.values,
|
||||||
@ -96,6 +127,9 @@ object AquaContext extends Logging {
|
|||||||
|
|
||||||
def fromServiceModel(sm: ServiceModel, serviceId: ValueModel): AquaContext =
|
def fromServiceModel(sm: ServiceModel, serviceId: ValueModel): AquaContext =
|
||||||
AquaContext(
|
AquaContext(
|
||||||
|
module = Some(sm.name),
|
||||||
|
declares = sm.`type`.fields.toNel.map(_._1).toList.toSet,
|
||||||
|
exports = None,
|
||||||
funcs = sm.arrows.toSortedMap.map { case (fnName, arrowType) =>
|
funcs = sm.arrows.toSortedMap.map { case (fnName, arrowType) =>
|
||||||
val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType)
|
val (args, call, ret) = ArgsCall.arrowToArgsCallRet(arrowType)
|
||||||
fnName ->
|
fnName ->
|
||||||
@ -130,7 +164,7 @@ object AquaContext extends Logging {
|
|||||||
)
|
)
|
||||||
(ctx |+| add, exportContext |+| add)
|
(ctx |+| add, exportContext |+| add)
|
||||||
case ((ctx, exportContext), func: FuncModel) =>
|
case ((ctx, exportContext), func: FuncModel) =>
|
||||||
val fr = func.capture(ctx.funcs, ctx.values)
|
val fr = func.capture(ctx.allFuncs(), ctx.allValues())
|
||||||
val add =
|
val add =
|
||||||
Monoid.empty[AquaContext].copy(funcs = ctx.funcs.updated(func.name, fr))
|
Monoid.empty[AquaContext].copy(funcs = ctx.funcs.updated(func.name, fr))
|
||||||
(ctx |+| add, exportContext |+| add)
|
(ctx |+| add, exportContext |+| add)
|
||||||
|
@ -10,10 +10,16 @@ case class AquaRes(funcs: Chain[FuncRes], services: Chain[ServiceRes]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object AquaRes {
|
object AquaRes {
|
||||||
|
private val blank = AquaRes(Chain.nil, Chain.nil)
|
||||||
|
|
||||||
def fromContext(ctx: AquaContext, conf: TransformConfig): AquaRes =
|
def fromContext(ctx: AquaContext, conf: TransformConfig): AquaRes =
|
||||||
AquaRes(
|
ctx.exports
|
||||||
funcs = Chain.fromSeq(ctx.funcs.values.toSeq).map(Transform.fn(_, conf)),
|
.map(ex =>
|
||||||
services = Chain.fromSeq(ctx.services.values.toSeq).map(ServiceRes.fromModel(_))
|
AquaRes(
|
||||||
)
|
funcs = Chain.fromSeq(ex.funcs.values.toSeq).map(Transform.fn(_, conf)),
|
||||||
|
services = Chain.fromSeq(ex.services.values.toSeq).map(ServiceRes.fromModel(_))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.getOrElse(blank)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,26 +9,29 @@ import cats.free.Cofree
|
|||||||
import cats.parse.Parser0 as P0
|
import cats.parse.Parser0 as P0
|
||||||
import cats.{Comonad, Eval}
|
import cats.{Comonad, Eval}
|
||||||
|
|
||||||
case class Ast[F[_]](head: Ast.Head[F], tree: Ast.Tree[F]) {
|
case class Ast[S[_]](head: Ast.Head[S], tree: Ast.Tree[S]) {
|
||||||
|
|
||||||
def cata[T](folder: (Expr[F], Chain[T]) => Eval[T]): Eval[T] =
|
def cata[T](folder: (Expr[S], Chain[T]) => Eval[T]): Eval[T] =
|
||||||
Cofree.cata[Chain, Expr[F], T](tree)(folder)
|
Cofree.cata[Chain, Expr[S], T](tree)(folder)
|
||||||
|
|
||||||
|
def cataHead[T](folder: (HeaderExpr[S], Chain[T]) => Eval[T]): Eval[T] =
|
||||||
|
Cofree.cata[Chain, HeaderExpr[S], T](head)(folder)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Ast {
|
object Ast {
|
||||||
type Tree[F[_]] = Cofree[Chain, Expr[F]]
|
type Tree[S[_]] = Cofree[Chain, Expr[S]]
|
||||||
type Head[F[_]] = Cofree[Chain, HeaderExpr[F]]
|
type Head[S[_]] = Cofree[Chain, HeaderExpr[S]]
|
||||||
|
|
||||||
def parser[F[_]: LiftParser: Comonad](): P0[ValidatedNec[ParserError[F], Ast[F]]] =
|
def parser[S[_]: LiftParser: Comonad](): P0[ValidatedNec[ParserError[S], Ast[S]]] =
|
||||||
(HeadExpr.ast[F].with1 ~ RootExpr.ast[F]()).map { case (head, bodyMaybe) =>
|
(HeadExpr.ast[S].with1 ~ RootExpr.ast[S]()).map { case (head, bodyMaybe) =>
|
||||||
bodyMaybe.map(Ast(head, _))
|
bodyMaybe.map(Ast(head, _))
|
||||||
}
|
}
|
||||||
|
|
||||||
def fromString[F[_]: LiftParser: Comonad](script: String): ValidatedNec[ParserError[F], Ast[F]] =
|
def fromString[S[_]: LiftParser: Comonad](script: String): ValidatedNec[ParserError[S], Ast[S]] =
|
||||||
parser[F]()
|
parser[S]()
|
||||||
.parseAll(script) match {
|
.parseAll(script) match {
|
||||||
case Right(value) => value
|
case Right(value) => value
|
||||||
case Left(e) => Validated.invalidNec(LexerError[F](e.wrapErr))
|
case Left(e) => Validated.invalidNec(LexerError[S](e.wrapErr))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ case class AbilityIdExpr[F[_]](ability: Ability[F], id: Value[F])
|
|||||||
object AbilityIdExpr extends Expr.Leaf {
|
object AbilityIdExpr extends Expr.Leaf {
|
||||||
|
|
||||||
override def p[F[_]: LiftParser: Comonad]: P[AbilityIdExpr[F]] =
|
override def p[F[_]: LiftParser: Comonad]: P[AbilityIdExpr[F]] =
|
||||||
((Ability.ab[F] <* ` `) ~ Value.`value`).map { case (ability, id) =>
|
((Ability.dotted[F] <* ` `) ~ Value.`value`).map { case (ability, id) =>
|
||||||
AbilityIdExpr(ability, id)
|
AbilityIdExpr(ability, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ object CallArrowExpr extends Expr.Leaf {
|
|||||||
|
|
||||||
override def p[F[_]: LiftParser: Comonad]: P[CallArrowExpr[F]] =
|
override def p[F[_]: LiftParser: Comonad]: P[CallArrowExpr[F]] =
|
||||||
((comma(Name.p[F]) <* ` <- `).backtrack.?.with1 ~
|
((comma(Name.p[F]) <* ` <- `).backtrack.?.with1 ~
|
||||||
((Ability.ab[F] <* `.`).?.with1 ~
|
((Ability.dotted[F] <* `.`).?.with1 ~
|
||||||
Name.p[F] ~
|
Name.p[F] ~
|
||||||
comma0(Value.`value`[F].surroundedBy(`/s*`)).between(`(` <* `/s*`, `/s*` *> `)`))).map {
|
comma0(Value.`value`[F].surroundedBy(`/s*`)).between(`(` <* `/s*`, `/s*` *> `)`))).map {
|
||||||
case (variables, ((ability, funcName), args)) =>
|
case (variables, ((ability, funcName), args)) =>
|
||||||
|
20
parser/src/main/scala/aqua/parser/head/ExportExpr.scala
Normal file
20
parser/src/main/scala/aqua/parser/head/ExportExpr.scala
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token.*
|
||||||
|
import aqua.parser.lexer.{Literal, Value, Token}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.parse.Parser
|
||||||
|
import cats.syntax.either.*
|
||||||
|
|
||||||
|
case class ExportExpr[F[_]](pubs: NonEmptyList[FromExpr.NameOrAbAs[F]]) extends HeaderExpr[F] {
|
||||||
|
override def token: Token[F] =
|
||||||
|
pubs.head.bimap(_._1, _._1).fold(identity, identity)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ExportExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
override def p[F[_]: LiftParser: Comonad]: Parser[ExportExpr[F]] =
|
||||||
|
(`_export` *> ` `) *> comma(FromExpr.nameOrAbAs[F]).map(ExportExpr(_))
|
||||||
|
}
|
11
parser/src/main/scala/aqua/parser/head/FilenameExpr.scala
Normal file
11
parser/src/main/scala/aqua/parser/head/FilenameExpr.scala
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.{Literal, Token}
|
||||||
|
|
||||||
|
trait FilenameExpr[F[_]] extends HeaderExpr[F] {
|
||||||
|
def filename: Literal[F]
|
||||||
|
|
||||||
|
override def token: Token[F] = filename
|
||||||
|
|
||||||
|
def fileValue: String = filename.value.drop(1).dropRight(1)
|
||||||
|
}
|
22
parser/src/main/scala/aqua/parser/head/FromExpr.scala
Normal file
22
parser/src/main/scala/aqua/parser/head/FromExpr.scala
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token._
|
||||||
|
import aqua.parser.lexer.{Ability, Name}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.parse.Parser as P
|
||||||
|
|
||||||
|
trait FromExpr[F[_]] {
|
||||||
|
def imports: NonEmptyList[FromExpr.NameOrAbAs[F]]
|
||||||
|
}
|
||||||
|
|
||||||
|
object FromExpr {
|
||||||
|
type NameOrAbAs[F[_]] = Either[Name.As[F], Ability.As[F]]
|
||||||
|
|
||||||
|
def nameOrAbAs[F[_]: LiftParser: Comonad]: P[NameOrAbAs[F]] =
|
||||||
|
Name.nameAs[F].map(Left(_)) | Ability.abAs[F].map(Right(_))
|
||||||
|
|
||||||
|
def importFrom[F[_]: LiftParser: Comonad]: P[NonEmptyList[NameOrAbAs[F]]] =
|
||||||
|
comma[NameOrAbAs[F]](nameOrAbAs[F]) <* ` ` <* `from`
|
||||||
|
}
|
@ -3,25 +3,30 @@ package aqua.parser.head
|
|||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
import aqua.parser.lexer.Token.` \n+`
|
import aqua.parser.lexer.Token.` \n+`
|
||||||
import aqua.parser.lift.LiftParser
|
import aqua.parser.lift.LiftParser
|
||||||
|
import aqua.parser.lift.LiftParser.*
|
||||||
import cats.{Comonad, Eval}
|
import cats.{Comonad, Eval}
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.free.Cofree
|
import cats.free.Cofree
|
||||||
import cats.parse.{Parser => P, Parser0 => P0}
|
import cats.parse.{Parser => P, Parser0 => P0}
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
|
|
||||||
case class HeadExpr[F[_]]() extends HeaderExpr[F]
|
case class HeadExpr[S[_]](token: Token[S]) extends HeaderExpr[S]
|
||||||
|
|
||||||
object HeadExpr {
|
object HeadExpr {
|
||||||
|
|
||||||
def headExprs: List[HeaderExpr.Companion] =
|
def headExprs: List[HeaderExpr.Companion] =
|
||||||
ImportExpr :: Nil
|
UseFromExpr :: UseExpr :: ImportFromExpr :: ImportExpr :: ExportExpr :: Nil
|
||||||
|
|
||||||
def ast[F[_]: LiftParser: Comonad]: P0[Ast.Head[F]] =
|
def ast[S[_]: LiftParser: Comonad]: P0[Ast.Head[S]] =
|
||||||
P.repSep0(P.oneOf(headExprs.map(_.ast[F])), ` \n+`)
|
(P.unit.lift0.map(Token.lift) ~ ((ModuleExpr.p[S] <* ` \n+`).? ~
|
||||||
|
P.repSep0(P.oneOf(headExprs.map(_.ast[S].backtrack)), ` \n+`).map(Chain.fromSeq))
|
||||||
.surroundedBy(` \n+`.?)
|
.surroundedBy(` \n+`.?)
|
||||||
.?
|
.?).map {
|
||||||
.map {
|
case (p, Some((maybeMod, exprs))) =>
|
||||||
case Some(exprs) => Chain.fromSeq(exprs)
|
Cofree(
|
||||||
case None => Chain.empty[Ast.Head[F]]
|
maybeMod.getOrElse(HeadExpr[S](p)),
|
||||||
}
|
Eval.now(exprs)
|
||||||
.map(exprs => Cofree(HeadExpr[F](), Eval.now(exprs)))
|
)
|
||||||
|
case (p, None) => Cofree(HeadExpr[S](p), Eval.now(Chain.nil))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
package aqua.parser.head
|
package aqua.parser.head
|
||||||
|
|
||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
import aqua.parser.lift.LiftParser
|
import aqua.parser.lift.LiftParser
|
||||||
import cats.{Comonad, Eval}
|
import cats.{Comonad, Eval}
|
||||||
import cats.data.Chain
|
import cats.data.Chain
|
||||||
import cats.free.Cofree
|
import cats.free.Cofree
|
||||||
import cats.parse.{Parser => P}
|
import cats.parse.Parser as P
|
||||||
|
|
||||||
trait HeaderExpr[F[_]]
|
trait HeaderExpr[S[_]] {
|
||||||
|
def token: Token[S]
|
||||||
|
}
|
||||||
|
|
||||||
object HeaderExpr {
|
object HeaderExpr {
|
||||||
|
|
||||||
trait Companion {
|
trait Companion {
|
||||||
def p[F[_]: LiftParser: Comonad]: P[HeaderExpr[F]]
|
def p[S[_]: LiftParser: Comonad]: P[HeaderExpr[S]]
|
||||||
|
|
||||||
def ast[F[_]: LiftParser: Comonad]: P[Ast.Head[F]]
|
def ast[S[_]: LiftParser: Comonad]: P[Ast.Head[S]]
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class Leaf extends Companion {
|
abstract class Leaf extends Companion {
|
||||||
|
@ -6,7 +6,7 @@ import aqua.parser.lift.LiftParser
|
|||||||
import cats.Comonad
|
import cats.Comonad
|
||||||
import cats.parse.Parser
|
import cats.parse.Parser
|
||||||
|
|
||||||
case class ImportExpr[F[_]](filename: Literal[F]) extends HeaderExpr[F]
|
case class ImportExpr[F[_]](filename: Literal[F]) extends FilenameExpr[F]
|
||||||
|
|
||||||
object ImportExpr extends HeaderExpr.Leaf {
|
object ImportExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
21
parser/src/main/scala/aqua/parser/head/ImportFromExpr.scala
Normal file
21
parser/src/main/scala/aqua/parser/head/ImportFromExpr.scala
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token.*
|
||||||
|
import aqua.parser.lexer.{Literal, Value}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.parse.Parser
|
||||||
|
|
||||||
|
case class ImportFromExpr[F[_]](
|
||||||
|
imports: NonEmptyList[FromExpr.NameOrAbAs[F]],
|
||||||
|
filename: Literal[F]
|
||||||
|
) extends FilenameExpr[F] with FromExpr[F]
|
||||||
|
|
||||||
|
object ImportFromExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
override def p[F[_]: LiftParser: Comonad]: Parser[HeaderExpr[F]] =
|
||||||
|
(`import` *> FromExpr.importFrom[F].surroundedBy(` `) ~ Value.string[F]).map {
|
||||||
|
case (imports, filename) => ImportFromExpr(imports, filename)
|
||||||
|
}
|
||||||
|
}
|
54
parser/src/main/scala/aqua/parser/head/ModuleExpr.scala
Normal file
54
parser/src/main/scala/aqua/parser/head/ModuleExpr.scala
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token.*
|
||||||
|
import aqua.parser.lexer.Token
|
||||||
|
import aqua.parser.lexer.{Ability, Literal, Name, Value}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import aqua.parser.lift.LiftParser.*
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.parse.Parser
|
||||||
|
|
||||||
|
case class ModuleExpr[F[_]](
|
||||||
|
name: Ability[F],
|
||||||
|
declareAll: Option[Token[F]],
|
||||||
|
declareNames: List[Name[F]],
|
||||||
|
declareCustom: List[Ability[F]]
|
||||||
|
) extends HeaderExpr[F] {
|
||||||
|
override def token: Token[F] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
object ModuleExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
type NameOrAb[F[_]] = Either[Name[F], Ability[F]]
|
||||||
|
|
||||||
|
def nameOrAb[F[_]: LiftParser: Comonad]: Parser[NameOrAb[F]] =
|
||||||
|
Name.p[F].map(Left(_)) | Ability.ab[F].map(Right(_))
|
||||||
|
|
||||||
|
def nameOrAbList[F[_]: LiftParser: Comonad]: Parser[List[NameOrAb[F]]] =
|
||||||
|
comma[NameOrAb[F]](nameOrAb[F]).map(_.toList)
|
||||||
|
|
||||||
|
def nameOrAbListOrAll[F[_]: LiftParser: Comonad]: Parser[Either[List[NameOrAb[F]], Token[F]]] =
|
||||||
|
nameOrAbList[F].map(Left(_)) | `star`.lift.map(Token.lift(_)).map(Right(_))
|
||||||
|
|
||||||
|
override def p[F[_]: LiftParser: Comonad]: Parser[ModuleExpr[F]] =
|
||||||
|
(`module` *> ` ` *> Ability.ab[F] ~
|
||||||
|
(` declares ` *> nameOrAbListOrAll[F]).?).map {
|
||||||
|
case (name, None) =>
|
||||||
|
ModuleExpr(name, None, Nil, Nil)
|
||||||
|
case (name, Some(Left(exportMembers))) =>
|
||||||
|
ModuleExpr(
|
||||||
|
name,
|
||||||
|
None,
|
||||||
|
exportMembers.collect { case Left(x) => x },
|
||||||
|
exportMembers.collect { case Right(x) => x }
|
||||||
|
)
|
||||||
|
case (name, Some(Right(point))) =>
|
||||||
|
ModuleExpr(
|
||||||
|
name,
|
||||||
|
Some(point),
|
||||||
|
Nil,
|
||||||
|
Nil
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
parser/src/main/scala/aqua/parser/head/UseExpr.scala
Normal file
21
parser/src/main/scala/aqua/parser/head/UseExpr.scala
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token.*
|
||||||
|
import aqua.parser.lexer.{Ability, Literal, Value}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.parse.Parser
|
||||||
|
|
||||||
|
case class UseExpr[F[_]](
|
||||||
|
filename: Literal[F],
|
||||||
|
asModule: Option[Ability[F]]
|
||||||
|
) extends FilenameExpr[F]
|
||||||
|
|
||||||
|
object UseExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
override def p[F[_]: LiftParser: Comonad]: Parser[HeaderExpr[F]] =
|
||||||
|
(`use` *> Value
|
||||||
|
.string[F] ~ (` as ` *> Ability.ab[F]).?).map { case (filename, asModule) =>
|
||||||
|
UseExpr(filename, asModule)
|
||||||
|
}
|
||||||
|
}
|
23
parser/src/main/scala/aqua/parser/head/UseFromExpr.scala
Normal file
23
parser/src/main/scala/aqua/parser/head/UseFromExpr.scala
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package aqua.parser.head
|
||||||
|
|
||||||
|
import aqua.parser.lexer.Token.*
|
||||||
|
import aqua.parser.lexer.{Ability, Literal, Name, Value}
|
||||||
|
import aqua.parser.lift.LiftParser
|
||||||
|
import cats.Comonad
|
||||||
|
import cats.data.NonEmptyList
|
||||||
|
import cats.parse.Parser
|
||||||
|
|
||||||
|
case class UseFromExpr[F[_]](
|
||||||
|
imports: NonEmptyList[FromExpr.NameOrAbAs[F]],
|
||||||
|
filename: Literal[F],
|
||||||
|
asModule: Ability[F]
|
||||||
|
) extends FilenameExpr[F] with FromExpr[F]
|
||||||
|
|
||||||
|
object UseFromExpr extends HeaderExpr.Leaf {
|
||||||
|
|
||||||
|
override def p[F[_]: LiftParser: Comonad]: Parser[HeaderExpr[F]] =
|
||||||
|
(`use` *> FromExpr.importFrom[F].surroundedBy(` `) ~ Value
|
||||||
|
.string[F] ~ (` as ` *> Ability.ab[F])).map { case ((imports, filename), asModule) =>
|
||||||
|
UseFromExpr(imports, filename, asModule)
|
||||||
|
}
|
||||||
|
}
|
@ -15,7 +15,14 @@ case class Ability[F[_]: Comonad](name: F[String]) extends Token[F] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Ability {
|
object Ability {
|
||||||
|
type As[F[_]] = (Ability[F], Option[Ability[F]])
|
||||||
|
|
||||||
def ab[F[_]: LiftParser: Comonad]: P[Ability[F]] =
|
def ab[F[_]: LiftParser: Comonad]: P[Ability[F]] =
|
||||||
`Class`.lift.map(Ability(_))
|
`Class`.lift.map(Ability(_))
|
||||||
|
|
||||||
|
def dotted[F[_]: LiftParser: Comonad]: P[Ability[F]] =
|
||||||
|
P.repSep(`Class`, `.`).map(_.toList.mkString(".")).lift.map(Ability(_))
|
||||||
|
|
||||||
|
def abAs[F[_]: LiftParser: Comonad]: P[As[F]] =
|
||||||
|
asOpt(ab[F])
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,15 @@ case class Name[F[_]: Comonad](name: F[String]) extends Token[F] {
|
|||||||
|
|
||||||
object Name {
|
object Name {
|
||||||
|
|
||||||
|
type As[F[_]] = (Name[F], Option[Name[F]])
|
||||||
|
|
||||||
def p[F[_]: LiftParser: Comonad]: P[Name[F]] =
|
def p[F[_]: LiftParser: Comonad]: P[Name[F]] =
|
||||||
`name`.lift.map(Name(_))
|
`name`.lift.map(Name(_))
|
||||||
|
|
||||||
|
def dotted[F[_]: LiftParser: Comonad]: P[Name[F]] =
|
||||||
|
((`Class`.repSep(`.`).map(_.toList.mkString(".")) ~ `.`).?.with1 ~ `name`).string.lift
|
||||||
|
.map(Name(_))
|
||||||
|
|
||||||
|
def nameAs[F[_]: LiftParser: Comonad]: P[As[F]] =
|
||||||
|
asOpt(p[F])
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,17 @@ object Token {
|
|||||||
val `const`: P[Unit] = P.string("const")
|
val `const`: P[Unit] = P.string("const")
|
||||||
val `data`: P[Unit] = P.string("data")
|
val `data`: P[Unit] = P.string("data")
|
||||||
val `import`: P[Unit] = P.string("import")
|
val `import`: P[Unit] = P.string("import")
|
||||||
|
val `module`: P[Unit] = P.string("module")
|
||||||
|
val `declares`: P[Unit] = P.string("declares")
|
||||||
|
val ` declares ` : P[Unit] = `declares`.surroundedBy(` `)
|
||||||
|
val `declare`: P[Unit] = P.string("declare")
|
||||||
|
val `_export`: P[Unit] = P.string("export")
|
||||||
|
val `star`: P[Unit] = P.char('*')
|
||||||
val `use`: P[Unit] = P.string("use")
|
val `use`: P[Unit] = P.string("use")
|
||||||
|
val `from`: P[Unit] = P.string("from")
|
||||||
|
val ` from ` : P[Unit] = `from`.surroundedBy(` `)
|
||||||
val `as`: P[Unit] = P.string("as")
|
val `as`: P[Unit] = P.string("as")
|
||||||
|
val ` as ` : P[Unit] = `as`.surroundedBy(` `)
|
||||||
val `alias`: P[Unit] = P.string("alias")
|
val `alias`: P[Unit] = P.string("alias")
|
||||||
val `service`: P[Unit] = P.string("service")
|
val `service`: P[Unit] = P.string("service")
|
||||||
val `func`: P[Unit] = P.string("func")
|
val `func`: P[Unit] = P.string("func")
|
||||||
@ -64,7 +73,7 @@ object Token {
|
|||||||
val `.` : P[Unit] = P.char('.')
|
val `.` : P[Unit] = P.char('.')
|
||||||
val `"` : P[Unit] = P.char('"')
|
val `"` : P[Unit] = P.char('"')
|
||||||
val `*` : P[Unit] = P.char('*')
|
val `*` : P[Unit] = P.char('*')
|
||||||
val exclamation : P[Unit] = P.char('!')
|
val exclamation: P[Unit] = P.char('!')
|
||||||
val `[]` : P[Unit] = P.string("[]")
|
val `[]` : P[Unit] = P.string("[]")
|
||||||
val `⊤` : P[Unit] = P.char('⊤')
|
val `⊤` : P[Unit] = P.char('⊤')
|
||||||
val `⊥` : P[Unit] = P.char('⊥')
|
val `⊥` : P[Unit] = P.char('⊥')
|
||||||
@ -92,4 +101,7 @@ object Token {
|
|||||||
|
|
||||||
def comma0[T](p: P[T]): P0[List[T]] =
|
def comma0[T](p: P[T]): P0[List[T]] =
|
||||||
P.repSep0(p, `,` <* ` \n+`.rep0)
|
P.repSep0(p, `,` <* ` \n+`.rep0)
|
||||||
|
|
||||||
|
def asOpt[T](p: P[T]): P[(T, Option[T])] =
|
||||||
|
p ~ (` as ` *> p).?
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,12 @@ case class CustomTypeToken[F[_]: Comonad](name: F[String]) extends DataTypeToken
|
|||||||
}
|
}
|
||||||
|
|
||||||
object CustomTypeToken {
|
object CustomTypeToken {
|
||||||
def ct[F[_]: LiftParser: Comonad]: P[CustomTypeToken[F]] = `Class`.lift.map(CustomTypeToken(_))
|
|
||||||
|
def ct[F[_]: LiftParser: Comonad]: P[CustomTypeToken[F]] =
|
||||||
|
`Class`.lift.map(CustomTypeToken(_))
|
||||||
|
|
||||||
|
def dotted[F[_]: LiftParser: Comonad]: P[CustomTypeToken[F]] =
|
||||||
|
`Class`.repSep(`.`).string.lift.map(CustomTypeToken(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
case class BasicTypeToken[F[_]: Comonad](scalarType: F[ScalarType]) extends DataTypeToken[F] {
|
case class BasicTypeToken[F[_]: Comonad](scalarType: F[ScalarType]) extends DataTypeToken[F] {
|
||||||
@ -115,7 +120,7 @@ object DataTypeToken {
|
|||||||
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer(
|
P.defer(`arraytypedef`[F]) :: P.defer(StreamTypeToken.`streamtypedef`) :: P.defer(
|
||||||
OptionTypeToken.`optiontypedef`
|
OptionTypeToken.`optiontypedef`
|
||||||
) :: BasicTypeToken
|
) :: BasicTypeToken
|
||||||
.`basictypedef`[F] :: CustomTypeToken.ct[F] :: Nil
|
.`basictypedef`[F] :: CustomTypeToken.dotted[F] :: Nil
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ case class Literal[F[_]: Comonad](valueToken: F[String], ts: LiteralType) extend
|
|||||||
object Value {
|
object Value {
|
||||||
|
|
||||||
def varLambda[F[_]: LiftParser: Comonad]: P[VarLambda[F]] =
|
def varLambda[F[_]: LiftParser: Comonad]: P[VarLambda[F]] =
|
||||||
(Name.p[F] ~ LambdaOp.ops[F].?).map { case (n, l) ⇒
|
(Name.dotted[F] ~ LambdaOp.ops[F].?).map { case (n, l) ⇒
|
||||||
VarLambda(n, l.fold[List[LambdaOp[F]]](Nil)(_.toList))
|
VarLambda(n, l.fold[List[LambdaOp[F]]](Nil)(_.toList))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,26 +3,26 @@ package aqua.parser.lift
|
|||||||
import cats.Id
|
import cats.Id
|
||||||
import cats.parse.{Parser, Parser0}
|
import cats.parse.{Parser, Parser0}
|
||||||
|
|
||||||
trait LiftParser[F[_]] {
|
trait LiftParser[S[_]] {
|
||||||
def lift[T](p: Parser[T]): Parser[F[T]]
|
def lift[T](p: Parser[T]): Parser[S[T]]
|
||||||
|
|
||||||
def lift0[T](p0: Parser0[T]): Parser0[F[T]]
|
def lift0[T](p0: Parser0[T]): Parser0[S[T]]
|
||||||
|
|
||||||
def wrapErr(e: Parser.Error): F[Parser.Error]
|
def wrapErr(e: Parser.Error): S[Parser.Error]
|
||||||
}
|
}
|
||||||
|
|
||||||
object LiftParser {
|
object LiftParser {
|
||||||
|
|
||||||
implicit class LiftErrorOps[F[_]: LiftParser, T](e: Parser.Error) {
|
implicit class LiftErrorOps[S[_]: LiftParser, T](e: Parser.Error) {
|
||||||
def wrapErr: F[Parser.Error] = implicitly[LiftParser[F]].wrapErr(e)
|
def wrapErr: S[Parser.Error] = implicitly[LiftParser[S]].wrapErr(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit class LiftParserOps[F[_]: LiftParser, T](parser: Parser[T]) {
|
implicit class LiftParserOps[S[_]: LiftParser, T](parser: Parser[T]) {
|
||||||
def lift: Parser[F[T]] = implicitly[LiftParser[F]].lift(parser)
|
def lift: Parser[S[T]] = implicitly[LiftParser[S]].lift(parser)
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit class LiftParser0Ops[F[_]: LiftParser, T](parser0: Parser0[T]) {
|
implicit class LiftParser0Ops[S[_]: LiftParser, T](parser0: Parser0[T]) {
|
||||||
def lift0: Parser0[F[T]] = implicitly[LiftParser[F]].lift0(parser0)
|
def lift0: Parser0[S[T]] = implicitly[LiftParser[S]].lift0(parser0)
|
||||||
}
|
}
|
||||||
|
|
||||||
object Implicits {
|
object Implicits {
|
||||||
|
@ -3,6 +3,7 @@ package aqua.semantics
|
|||||||
import aqua.parser.Ast
|
import aqua.parser.Ast
|
||||||
import aqua.parser.lexer.Token
|
import aqua.parser.lexer.Token
|
||||||
|
|
||||||
sealed trait SemanticError[F[_]]
|
sealed trait SemanticError[S[_]]
|
||||||
case class RulesViolated[F[_]](token: Token[F], message: String) extends SemanticError[F]
|
case class RulesViolated[S[_]](token: Token[S], message: String) extends SemanticError[S]
|
||||||
case class WrongAST[F[_]](ast: Ast[F]) extends SemanticError[F]
|
case class HeaderError[S[_]](token: Token[S], message: String) extends SemanticError[S]
|
||||||
|
case class WrongAST[S[_]](ast: Ast[S]) extends SemanticError[S]
|
||||||
|
@ -27,13 +27,13 @@ import scribe.Logging
|
|||||||
|
|
||||||
object Semantics extends Logging {
|
object Semantics extends Logging {
|
||||||
|
|
||||||
def folder[F[_], G[_]](implicit
|
def folder[S[_], G[_]](implicit
|
||||||
A: AbilitiesAlgebra[F, G],
|
A: AbilitiesAlgebra[S, G],
|
||||||
N: NamesAlgebra[F, G],
|
N: NamesAlgebra[S, G],
|
||||||
T: TypesAlgebra[F, G]
|
T: TypesAlgebra[S, G]
|
||||||
): (Expr[F], Chain[Free[G, Model]]) => Eval[Free[G, Model]] = { case (expr, inners) =>
|
): (Expr[S], Chain[Free[G, Model]]) => Eval[Free[G, Model]] = { case (expr, inners) =>
|
||||||
Eval later ExprSem
|
Eval later ExprSem
|
||||||
.getProg[F, G](expr)
|
.getProg[S, G](expr)
|
||||||
.apply(
|
.apply(
|
||||||
// TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones
|
// TODO instead of foldRight, do slidingWindow for 2 elements, merge right associative ones
|
||||||
// Then foldLeft just like now
|
// Then foldLeft just like now
|
||||||
@ -50,57 +50,58 @@ object Semantics extends Logging {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Alg0[F[_], A] = EitherK[AbilityOp[F, *], NameOp[F, *], A]
|
type Alg0[S[_], A] = EitherK[AbilityOp[S, *], NameOp[S, *], A]
|
||||||
type Alg[F[_], A] = EitherK[TypeOp[F, *], Alg0[F, *], A]
|
type Alg[S[_], A] = EitherK[TypeOp[S, *], Alg0[S, *], A]
|
||||||
|
|
||||||
def transpile[F[_]](ast: Ast[F]): Free[Alg[F, *], Model] =
|
def transpile[S[_]](ast: Ast[S]): Free[Alg[S, *], Model] =
|
||||||
ast.cata(folder[F, Alg[F, *]]).value
|
ast.cata(folder[S, Alg[S, *]]).value
|
||||||
|
|
||||||
def interpret[F[_]](free: Free[Alg[F, *], Model]): State[CompilerState[F], Model] = {
|
def interpret[S[_]](free: Free[Alg[S, *], Model]): State[CompilerState[S], Model] = {
|
||||||
import monocle.syntax.all._
|
import monocle.syntax.all._
|
||||||
|
|
||||||
implicit val re: ReportError[F, CompilerState[F]] =
|
implicit val re: ReportError[S, CompilerState[S]] =
|
||||||
(st: CompilerState[F], token: Token[F], hint: String) =>
|
(st: CompilerState[S], token: Token[S], hint: String) =>
|
||||||
st.focus(_.errors).modify(_.append(RulesViolated(token, hint)))
|
st.focus(_.errors).modify(_.append(RulesViolated(token, hint)))
|
||||||
|
|
||||||
implicit val ns: Lens[CompilerState[F], NamesState[F]] = GenLens[CompilerState[F]](_.names)
|
implicit val ns: Lens[CompilerState[S], NamesState[S]] = GenLens[CompilerState[S]](_.names)
|
||||||
|
|
||||||
val names = new NamesInterpreter[F, CompilerState[F]]()
|
val names = new NamesInterpreter[S, CompilerState[S]]()
|
||||||
|
|
||||||
implicit val as: Lens[CompilerState[F], AbilitiesState[F]] =
|
implicit val as: Lens[CompilerState[S], AbilitiesState[S]] =
|
||||||
GenLens[CompilerState[F]](_.abilities)
|
GenLens[CompilerState[S]](_.abilities)
|
||||||
|
|
||||||
val abilities = new AbilitiesInterpreter[F, CompilerState[F]]()
|
val abilities = new AbilitiesInterpreter[S, CompilerState[S]]()
|
||||||
|
|
||||||
implicit val ts: Lens[CompilerState[F], TypesState[F]] = GenLens[CompilerState[F]](_.types)
|
implicit val ts: Lens[CompilerState[S], TypesState[S]] = GenLens[CompilerState[S]](_.types)
|
||||||
|
|
||||||
val types = new TypesInterpreter[F, CompilerState[F]]()
|
val types = new TypesInterpreter[S, CompilerState[S]]()
|
||||||
|
|
||||||
val interpreter0: FunctionK[Alg0[F, *], State[CompilerState[F], *]] = abilities or names
|
val interpreter0: FunctionK[Alg0[S, *], State[CompilerState[S], *]] = abilities or names
|
||||||
val interpreter: FunctionK[Alg[F, *], State[CompilerState[F], *]] = types or interpreter0
|
val interpreter: FunctionK[Alg[S, *], State[CompilerState[S], *]] = types or interpreter0
|
||||||
|
|
||||||
free.foldMap[State[CompilerState[F], *]](interpreter)
|
free.foldMap[State[CompilerState[S], *]](interpreter)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def astToState[F[_]](ast: Ast[F]): State[CompilerState[F], Model] =
|
private def astToState[S[_]](ast: Ast[S]): State[CompilerState[S], Model] =
|
||||||
(transpile[F] _ andThen interpret[F])(ast)
|
(transpile[S] _ andThen interpret[S])(ast)
|
||||||
|
|
||||||
def process[F[_]](ast: Ast[F], init: AquaContext)(implicit
|
def process[S[_]](ast: Ast[S], init: AquaContext)(implicit
|
||||||
aqum: Monoid[AquaContext]
|
aqum: Monoid[AquaContext]
|
||||||
): ValidatedNec[SemanticError[F], AquaContext] =
|
): ValidatedNec[SemanticError[S], AquaContext] =
|
||||||
astToState[F](ast)
|
astToState[S](ast)
|
||||||
.run(CompilerState.init[F](init))
|
.run(CompilerState.init[S](init))
|
||||||
.map {
|
.map {
|
||||||
case (state, gen: ScriptModel) =>
|
case (state, gen: ScriptModel) =>
|
||||||
val ctx = AquaContext.fromScriptModel(gen, init)
|
val ctx = AquaContext.fromScriptModel(gen, init)
|
||||||
NonEmptyChain
|
NonEmptyChain
|
||||||
.fromChain(state.errors)
|
.fromChain(state.errors)
|
||||||
.fold[ValidatedNec[SemanticError[F], AquaContext]](Valid(ctx))(Invalid(_))
|
.fold[ValidatedNec[SemanticError[S], AquaContext]](Valid(ctx))(Invalid(_))
|
||||||
case (state, _) =>
|
case (state, _) =>
|
||||||
NonEmptyChain
|
NonEmptyChain
|
||||||
.fromChain(state.errors)
|
.fromChain(state.errors)
|
||||||
.map(Invalid(_))
|
.map(Invalid(_))
|
||||||
.getOrElse(Validated.invalidNec[SemanticError[F], AquaContext](WrongAST(ast)))
|
.getOrElse(Validated.invalidNec[SemanticError[S], AquaContext](WrongAST(ast)))
|
||||||
}
|
}
|
||||||
|
// TODO: return as Eval
|
||||||
.value
|
.value
|
||||||
}
|
}
|
||||||
|
@ -58,12 +58,12 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
|
|||||||
ability match {
|
ability match {
|
||||||
case Some(ab) =>
|
case Some(ab) =>
|
||||||
(A.getArrow(ab, funcName), A.getServiceId(ab)).mapN {
|
(A.getArrow(ab, funcName), A.getServiceId(ab)).mapN {
|
||||||
case (Some(at), Some(sid)) =>
|
case (Some(at), Right(sid)) =>
|
||||||
Option(at -> sid) // Here we assume that Ability is a Service that must be resolved
|
Some(callServiceTag(at, Option(sid)))
|
||||||
|
case (Some(at), Left(true)) =>
|
||||||
|
Some(callServiceTag(at, None))
|
||||||
case _ => None
|
case _ => None
|
||||||
}.flatMap(_.map { case (arrowType, serviceId) =>
|
}.flatMap(_.traverse(identity))
|
||||||
callServiceTag(arrowType, Option(serviceId))
|
|
||||||
}.traverse(identity))
|
|
||||||
case None =>
|
case None =>
|
||||||
N.readArrow(funcName)
|
N.readArrow(funcName)
|
||||||
.flatMap(_.map { arrowType =>
|
.flatMap(_.map { arrowType =>
|
||||||
@ -96,7 +96,7 @@ class CallArrowSem[F[_]](val expr: CallArrowExpr[F]) extends AnyVal {
|
|||||||
)
|
)
|
||||||
case None =>
|
case None =>
|
||||||
CallArrowTag(
|
CallArrowTag(
|
||||||
funcName = funcName.value,
|
funcName = ability.map(_.value + "." + funcName.value).getOrElse(funcName.value),
|
||||||
Call(argsResolved, maybeExport)
|
Call(argsResolved, maybeExport)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
201
semantics/src/main/scala/aqua/semantics/header/HeaderSem.scala
Normal file
201
semantics/src/main/scala/aqua/semantics/header/HeaderSem.scala
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package aqua.semantics.header
|
||||||
|
|
||||||
|
import aqua.model.AquaContext
|
||||||
|
import cats.data.Validated.{invalidNec, validNec, Invalid, Valid}
|
||||||
|
import cats.{Comonad, Eval, Monoid}
|
||||||
|
import cats.data.{Chain, NonEmptyChain, NonEmptyMap, Validated, ValidatedNec}
|
||||||
|
import aqua.parser.Ast
|
||||||
|
import aqua.parser.head.*
|
||||||
|
import aqua.parser.lexer.{Ability, Token}
|
||||||
|
import aqua.semantics.{HeaderError, SemanticError}
|
||||||
|
import cats.syntax.foldable.*
|
||||||
|
import cats.syntax.semigroup.*
|
||||||
|
import cats.instances.list.*
|
||||||
|
import cats.instances.option.*
|
||||||
|
import cats.free.Cofree
|
||||||
|
|
||||||
|
case class HeaderSem[S[_]](
|
||||||
|
initCtx: AquaContext,
|
||||||
|
finCtx: AquaContext => ValidatedNec[SemanticError[S], AquaContext]
|
||||||
|
)
|
||||||
|
|
||||||
|
object HeaderSem {
|
||||||
|
type Res[S[_]] = ValidatedNec[SemanticError[S], HeaderSem[S]]
|
||||||
|
type ResAC[S[_]] = ValidatedNec[SemanticError[S], AquaContext]
|
||||||
|
|
||||||
|
private implicit def headerSemMonoid[S[_]](implicit
|
||||||
|
acm: Monoid[AquaContext]
|
||||||
|
): Monoid[HeaderSem[S]] =
|
||||||
|
new Monoid[HeaderSem[S]] {
|
||||||
|
override def empty: HeaderSem[S] = HeaderSem(acm.empty, validNec(_))
|
||||||
|
|
||||||
|
override def combine(a: HeaderSem[S], b: HeaderSem[S]): HeaderSem[S] =
|
||||||
|
HeaderSem(
|
||||||
|
a.initCtx |+| b.initCtx,
|
||||||
|
a.finCtx.andThen(_.andThen(b.finCtx))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: monoidal combine of all the childrens after parent res
|
||||||
|
private def combineAnd[S[_]](children: Chain[Res[S]])(parent: Res[S])(implicit
|
||||||
|
acm: Monoid[AquaContext]
|
||||||
|
): Eval[Res[S]] =
|
||||||
|
Eval.later(parent |+| children.combineAll)
|
||||||
|
|
||||||
|
// Error generator with token pointer
|
||||||
|
private def error[S[_], T](token: Token[S], msg: String): ValidatedNec[SemanticError[S], T] =
|
||||||
|
invalidNec(HeaderError(token, msg))
|
||||||
|
|
||||||
|
def sem[S[_]: Comonad](imports: Map[String, AquaContext], header: Ast.Head[S])(implicit
|
||||||
|
acm: Monoid[AquaContext]
|
||||||
|
): Res[S] = {
|
||||||
|
// Resolve a filename from given imports or fail
|
||||||
|
def resolve(f: FilenameExpr[S]): ResAC[S] =
|
||||||
|
imports
|
||||||
|
.get(f.fileValue)
|
||||||
|
.fold[ResAC[S]](
|
||||||
|
error(f.token, "Cannot resolve the import")
|
||||||
|
)(validNec)
|
||||||
|
|
||||||
|
// Get part of the declared context (for import/use ... from ... expressions)
|
||||||
|
def getFrom(f: FromExpr[S], ctx: AquaContext): ResAC[S] =
|
||||||
|
f.imports
|
||||||
|
.map[ResAC[S]](
|
||||||
|
_.fold[ResAC[S]](
|
||||||
|
{ case (n, rn) =>
|
||||||
|
ctx
|
||||||
|
.pick(n.value, rn.map(_.value))
|
||||||
|
.map(validNec)
|
||||||
|
.getOrElse(error(n, s"Imported file has no ${n.value} declaration"))
|
||||||
|
},
|
||||||
|
{ case (n, rn) =>
|
||||||
|
ctx
|
||||||
|
.pick(n.value, rn.map(_.value))
|
||||||
|
.map(validNec)
|
||||||
|
.getOrElse(error(n, s"Imported file has no ${n.value} declaration"))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.reduce
|
||||||
|
|
||||||
|
// Convert an imported context into a module (ability)
|
||||||
|
def toModule(ctx: AquaContext, tkn: Token[S], rename: Option[Ability[S]]): ResAC[S] =
|
||||||
|
rename
|
||||||
|
.map(_.value)
|
||||||
|
.orElse(ctx.module)
|
||||||
|
.fold[ResAC[S]](
|
||||||
|
error(
|
||||||
|
tkn,
|
||||||
|
"Used module has no `module` header. Please add `module` header or use ... as ModuleName, or switch to import"
|
||||||
|
)
|
||||||
|
)(modName => validNec(acm.empty.copy(abilities = Map(modName -> ctx))))
|
||||||
|
|
||||||
|
// Handler for every header expression, will be combined later
|
||||||
|
val onExpr: PartialFunction[HeaderExpr[S], Res[S]] = {
|
||||||
|
// Module header, like `module A declares *`
|
||||||
|
case ModuleExpr(name, declareAll, declareNames, declareCustom) =>
|
||||||
|
validNec(
|
||||||
|
HeaderSem[S](
|
||||||
|
// Save module header info
|
||||||
|
acm.empty.copy(
|
||||||
|
module = Some(name.value),
|
||||||
|
declares = declareNames.map(_.value).toSet ++ declareCustom.map(_.value)
|
||||||
|
),
|
||||||
|
ctx =>
|
||||||
|
// When file is handled, check that all the declarations exists
|
||||||
|
if (declareAll.nonEmpty)
|
||||||
|
validNec(
|
||||||
|
ctx.copy(declares =
|
||||||
|
ctx.`type`("").map(_.fields.toNel.map(_._1).toList.toSet).getOrElse(Set.empty)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else
|
||||||
|
(
|
||||||
|
declareNames.map(n => n.value -> n) ::: declareCustom.map(a => a.value -> a)
|
||||||
|
).map[ValidatedNec[SemanticError[S], Int]] { case (n, t) =>
|
||||||
|
ctx
|
||||||
|
.pick(n, None)
|
||||||
|
// We just validate, nothing more
|
||||||
|
.map(_ => validNec(1))
|
||||||
|
.getOrElse(
|
||||||
|
error(
|
||||||
|
t,
|
||||||
|
s"`${n}` is expected to be declared, but declaration is not found in the file"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}.combineAll
|
||||||
|
.map(_ => ctx)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case f @ ImportExpr(_) =>
|
||||||
|
// Import everything from a file
|
||||||
|
resolve(f).map(fc => HeaderSem[S](fc, validNec(_)))
|
||||||
|
case f @ ImportFromExpr(_, _) =>
|
||||||
|
// Import, map declarations
|
||||||
|
resolve(f)
|
||||||
|
.andThen(getFrom(f, _))
|
||||||
|
.map(ctx => HeaderSem[S](ctx, validNec(_)))
|
||||||
|
|
||||||
|
case f @ UseExpr(_, asModule) =>
|
||||||
|
// Import, move into a module scope
|
||||||
|
resolve(f)
|
||||||
|
.andThen(toModule(_, f.token, asModule))
|
||||||
|
.map(fc => HeaderSem[S](fc, validNec(_)))
|
||||||
|
|
||||||
|
case f @ UseFromExpr(_, _, asModule) =>
|
||||||
|
// Import, cherry-pick declarations, move to a module scope
|
||||||
|
resolve(f)
|
||||||
|
.andThen(getFrom(f, _))
|
||||||
|
.andThen(toModule(_, f.token, Some(asModule)))
|
||||||
|
.map(fc => HeaderSem[S](fc, validNec(_)))
|
||||||
|
|
||||||
|
case ExportExpr(pubs) =>
|
||||||
|
// Save exports, finally handle them
|
||||||
|
validNec(
|
||||||
|
HeaderSem[S](
|
||||||
|
// Nothing there
|
||||||
|
acm.empty,
|
||||||
|
ctx =>
|
||||||
|
pubs
|
||||||
|
.map(
|
||||||
|
_.fold(
|
||||||
|
{ case (n, rn) =>
|
||||||
|
ctx
|
||||||
|
.pick(n.value, rn.map(_.value), declared = false)
|
||||||
|
.map(validNec)
|
||||||
|
.getOrElse(
|
||||||
|
error(n, s"File has no ${n.value} declaration or import, cannot export")
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{ case (n, rn) =>
|
||||||
|
ctx
|
||||||
|
.pick(n.value, rn.map(_.value), declared = false)
|
||||||
|
.map(validNec)
|
||||||
|
.getOrElse(
|
||||||
|
error(n, s"File has no ${n.value} declaration or import, cannot export")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.foldLeft[ResAC[S]](validNec(ctx.exports.getOrElse(acm.empty)))(_ |+| _)
|
||||||
|
.map(expCtx => ctx.copy(exports = Some(expCtx)))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case HeadExpr(token) =>
|
||||||
|
// Old file exports everything
|
||||||
|
validNec(HeaderSem[S](acm.empty, ctx => validNec(ctx.copy(exports = Some(ctx)))))
|
||||||
|
|
||||||
|
case f: FilenameExpr[S] =>
|
||||||
|
resolve(f).map(fc => HeaderSem[S](fc, validNec(_)))
|
||||||
|
}
|
||||||
|
|
||||||
|
Cofree
|
||||||
|
.cata[Chain, HeaderExpr[S], Res[S]](header) { case (expr, children) =>
|
||||||
|
onExpr.lift.apply(expr).fold(Eval.later(children.combineAll))(combineAnd(children)(_))
|
||||||
|
}
|
||||||
|
.value
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -37,7 +37,10 @@ class ValuesAlgebra[F[_], Alg[_]](implicit N: NamesAlgebra[F, Alg], T: TypesAlge
|
|||||||
case VarLambda(name, ops) =>
|
case VarLambda(name, ops) =>
|
||||||
N.read(name).flatMap {
|
N.read(name).flatMap {
|
||||||
case Some(t) =>
|
case Some(t) =>
|
||||||
T.resolveLambda(t, ops).map(Chain.fromSeq).map(VarModel(name.value, t, _)).map(Some(_))
|
T.resolveLambda(t, ops)
|
||||||
|
.map(Chain.fromSeq)
|
||||||
|
.map(VarModel(name.value.replace('.', '_'), t, _))
|
||||||
|
.map(Some(_))
|
||||||
case None =>
|
case None =>
|
||||||
Free.pure(None)
|
Free.pure(None)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class AbilitiesAlgebra[F[_], Alg[_]](implicit A: InjectK[AbilityOp[F, *], Alg])
|
|||||||
def setServiceId(name: Ability[F], id: Value[F], vm: ValueModel): Free[Alg, Boolean] =
|
def setServiceId(name: Ability[F], id: Value[F], vm: ValueModel): Free[Alg, Boolean] =
|
||||||
Free.liftInject[Alg](SetServiceId[F](name, id, vm))
|
Free.liftInject[Alg](SetServiceId[F](name, id, vm))
|
||||||
|
|
||||||
def getServiceId(name: Ability[F]): Free[Alg, Option[ValueModel]] =
|
def getServiceId(name: Ability[F]): Free[Alg, Either[Boolean, ValueModel]] =
|
||||||
Free.liftInject[Alg](GetServiceId[F](name))
|
Free.liftInject[Alg](GetServiceId[F](name))
|
||||||
|
|
||||||
def beginScope(token: Token[F]): Free[Alg, Unit] =
|
def beginScope(token: Token[F]): Free[Alg, Unit] =
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package aqua.semantics.rules.abilities
|
package aqua.semantics.rules.abilities
|
||||||
|
|
||||||
import aqua.model.{ServiceModel, ValueModel}
|
import aqua.model.{AquaContext, ServiceModel, ValueModel}
|
||||||
import aqua.parser.lexer.Name
|
import aqua.parser.lexer.Name
|
||||||
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
import aqua.semantics.rules.{ReportError, StackInterpreter}
|
||||||
import aqua.types.ArrowType
|
import aqua.types.ArrowType
|
||||||
import cats.data.{NonEmptyList, State}
|
import cats.data.{NonEmptyList, State}
|
||||||
import cats.syntax.functor._
|
import cats.syntax.functor.*
|
||||||
import cats.~>
|
import cats.~>
|
||||||
import monocle.Lens
|
import monocle.Lens
|
||||||
import monocle.macros.GenLens
|
import monocle.macros.GenLens
|
||||||
@ -17,10 +17,12 @@ class AbilitiesInterpreter[F[_], X](implicit
|
|||||||
GenLens[AbilitiesState[F]](_.stack)
|
GenLens[AbilitiesState[F]](_.stack)
|
||||||
) with (AbilityOp[F, *] ~> State[X, *]) {
|
) with (AbilityOp[F, *] ~> State[X, *]) {
|
||||||
|
|
||||||
// TODO: resolve abilities as well
|
|
||||||
private def getService(name: String): S[Option[ServiceModel]] =
|
private def getService(name: String): S[Option[ServiceModel]] =
|
||||||
getState.map(_.services.get(name))
|
getState.map(_.services.get(name))
|
||||||
|
|
||||||
|
private def getAbility(name: String): S[Option[AquaContext]] =
|
||||||
|
getState.map(_.abilities.get(name))
|
||||||
|
|
||||||
override def apply[A](fa: AbilityOp[F, A]): State[X, A] =
|
override def apply[A](fa: AbilityOp[F, A]): State[X, A] =
|
||||||
(fa match {
|
(fa match {
|
||||||
case bs: BeginScope[F] =>
|
case bs: BeginScope[F] =>
|
||||||
@ -45,12 +47,25 @@ class AbilitiesInterpreter[F[_], X](implicit
|
|||||||
.fold(
|
.fold(
|
||||||
report(
|
report(
|
||||||
ga.arrow,
|
ga.arrow,
|
||||||
s"Service found, but arrow is undefined, available: ${arrows.value.keys.toNonEmptyList.toList
|
s"Service is found, but arrow is undefined, available: ${arrows.value.keys.toNonEmptyList.toList
|
||||||
.mkString(", ")}"
|
.mkString(", ")}"
|
||||||
).as(Option.empty[ArrowType])
|
).as(Option.empty[ArrowType])
|
||||||
)(a => State.pure(Some(a)))
|
)(a => State.pure(Some(a)))
|
||||||
case None =>
|
case None =>
|
||||||
report(ga.name, "Ability with this name is undefined").as(Option.empty[ArrowType])
|
getAbility(ga.name.value).flatMap {
|
||||||
|
case Some(abCtx) =>
|
||||||
|
abCtx.funcs
|
||||||
|
.get(ga.arrow.value)
|
||||||
|
.fold(
|
||||||
|
report(
|
||||||
|
ga.arrow,
|
||||||
|
s"Ability is found, but arrow is undefined, available: ${abCtx.funcs.keys.toList
|
||||||
|
.mkString(", ")}"
|
||||||
|
).as(Option.empty[ArrowType])
|
||||||
|
)(a => State.pure(Some(a)))
|
||||||
|
case None =>
|
||||||
|
report(ga.name, "Ability with this name is undefined").as(Option.empty[ArrowType])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case s: SetServiceId[F] =>
|
case s: SetServiceId[F] =>
|
||||||
@ -68,24 +83,35 @@ class AbilitiesInterpreter[F[_], X](implicit
|
|||||||
}
|
}
|
||||||
|
|
||||||
case s: GetServiceId[F] =>
|
case s: GetServiceId[F] =>
|
||||||
getState.flatMap(st =>
|
getService(s.name.value).flatMap {
|
||||||
st.stack
|
case Some(_) =>
|
||||||
.flatMap(_.serviceIds.get(s.name.value).map(_._2))
|
getState.flatMap(st =>
|
||||||
.headOption orElse st.rootServiceIds
|
st.stack
|
||||||
.get(
|
.flatMap(_.serviceIds.get(s.name.value).map(_._2))
|
||||||
s.name.value
|
.headOption orElse st.rootServiceIds
|
||||||
)
|
.get(
|
||||||
.map(_._2) orElse st.services.get(s.name.value).flatMap(_.defaultId) match {
|
s.name.value
|
||||||
case None =>
|
)
|
||||||
report(
|
.map(_._2) orElse st.services.get(s.name.value).flatMap(_.defaultId) match {
|
||||||
s.name,
|
case None =>
|
||||||
s"Service ID unresolved, use `${s.name.value} id` expression to set it"
|
report(
|
||||||
)
|
s.name,
|
||||||
.as(Option.empty[ValueModel])
|
s"Service ID unresolved, use `${s.name.value} id` expression to set it"
|
||||||
|
)
|
||||||
|
.as(Left[Boolean, ValueModel](false))
|
||||||
|
|
||||||
case v => State.pure(v)
|
case Some(v) => State.pure(Right(v))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
case None =>
|
||||||
|
getAbility(s.name.value).flatMap {
|
||||||
|
case Some(_) => State.pure(Left[Boolean, ValueModel](true))
|
||||||
|
case None =>
|
||||||
|
report(s.name, "Ability with this name is undefined").as(
|
||||||
|
Left[Boolean, ValueModel](false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case da: DefineArrow[F] =>
|
case da: DefineArrow[F] =>
|
||||||
mapStackHeadE(
|
mapStackHeadE(
|
||||||
|
@ -47,5 +47,8 @@ object AbilitiesState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def init[F[_]](context: AquaContext): AbilitiesState[F] =
|
def init[F[_]](context: AquaContext): AbilitiesState[F] =
|
||||||
AbilitiesState(services = context.allServices(), abilities = context.abilities)
|
AbilitiesState(
|
||||||
|
services = context.allServices(),
|
||||||
|
abilities = context.abilities // TODO is it the right way to collect abilities? Why?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ case class GetArrow[F[_]](name: Ability[F], arrow: Name[F]) extends AbilityOp[F,
|
|||||||
case class SetServiceId[F[_]](name: Ability[F], id: Value[F], vm: ValueModel)
|
case class SetServiceId[F[_]](name: Ability[F], id: Value[F], vm: ValueModel)
|
||||||
extends AbilityOp[F, Boolean]
|
extends AbilityOp[F, Boolean]
|
||||||
|
|
||||||
case class GetServiceId[F[_]](name: Ability[F]) extends AbilityOp[F, Option[ValueModel]]
|
case class GetServiceId[F[_]](name: Ability[F]) extends AbilityOp[F, Either[Boolean, ValueModel]]
|
||||||
|
|
||||||
case class BeginScope[F[_]](token: Token[F]) extends AbilityOp[F, Unit]
|
case class BeginScope[F[_]](token: Token[F]) extends AbilityOp[F, Unit]
|
||||||
|
|
||||||
|
@ -156,5 +156,6 @@ object TypesState {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def init[F[_]](context: AquaContext): TypesState[F] = TypesState(strict = context.allTypes())
|
def init[F[_]](context: AquaContext): TypesState[F] =
|
||||||
|
TypesState(strict = context.allTypes())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user