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:
Dmitry Kurinskiy 2021-08-20 18:03:47 +03:00 committed by GitHub
parent 296c64836d
commit b9af20339b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 728 additions and 163 deletions

4
.gitignore vendored
View File

@ -1,4 +1,8 @@
.idea .idea
.bsp .bsp
.metals
.vscode
.bloop
metals.sbt
target target
project/target project/target

View File

@ -58,4 +58,5 @@ rewrite {
rules = [ rules = [
SortImports SortImports
] ]
} }
#runner.dialect = scala3

View File

@ -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:

View File

@ -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(

View File

@ -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"

View File

@ -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)
} }

View File

@ -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,

View File

@ -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)))
} }

View File

@ -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
} }

View File

@ -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)
} }

View File

@ -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)
) )

View File

@ -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,

View File

@ -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")))
} }

View File

@ -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)

View File

@ -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)
} }

View File

@ -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))
} }
} }

View File

@ -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)
} }

View File

@ -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)) =>

View 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(_))
}

View 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)
}

View 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`
}

View File

@ -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))
}
} }

View File

@ -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 {

View File

@ -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 {

View 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)
}
}

View 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
)
}
}

View 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)
}
}

View 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)
}
}

View File

@ -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])
} }

View File

@ -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])
} }

View File

@ -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).?
} }

View File

@ -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
) )
} }

View File

@ -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))
} }

View File

@ -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 {

View File

@ -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]

View File

@ -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
} }

View File

@ -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)
) )
}) })

View 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
}
}

View File

@ -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)
} }

View File

@ -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] =

View File

@ -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(

View File

@ -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?
)
} }

View File

@ -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]

View File

@ -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())
} }