Api for fluence cli (#611)

This commit is contained in:
Dima 2022-12-28 11:30:42 +03:00 committed by GitHub
parent eb1c8f3f6a
commit aa89b85b41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 14977 additions and 1115 deletions

View File

@ -40,6 +40,11 @@ jobs:
env:
BUILD_NUMBER: ${{ github.run_number }}
- name: JS Aqua API build
run: sbt aqua-api/fullLinkJS
env:
BUILD_NUMBER: ${{ github.run_number }}
- name: Get project version
# In CI sbt appends a new line after its output, so we need `tail -n3 | head -n2` to get last two non-empty lines
run: |
@ -64,15 +69,22 @@ jobs:
- name: Check .js exists
run: |
JS="cli/.js/target/scala-3.1.3/cli-opt/aqua-${{ env.VERSION }}.js"
mv cli/.js/target/scala-3.1.3/cli-opt/main.js "$JS"
JS="cli/cli/.js/target/scala-3.1.3/cli-opt/aqua-${{ env.VERSION }}.js"
mv cli/cli/.js/target/scala-3.1.3/cli-opt/main.js "$JS"
stat "$JS"
echo "JS=$JS" >> $GITHUB_ENV
- name: Check API .js exists
- name: Check LSP API .js exists
run: |
JSAPI="language-server-api/target/scala-3.1.3/language-server-api-opt/aqua-${{ env.VERSION }}.js"
mv language-server-api/target/scala-3.1.3/language-server-api-opt/main.js "$JSAPI"
JSLSP="language-server/language-server-api/target/scala-3.1.3/language-server-api-opt/aqua-${{ env.VERSION }}.js"
mv language-server/language-server-api/target/scala-3.1.3/language-server-api-opt/main.js "$JSLSP"
stat "$JSLSP"
echo "JSLSP=$JSLSP" >> $GITHUB_ENV
- name: Check Aqua API .js exists
run: |
JSAPI="api/aqua-api/target/scala-3.1.3/aqua-api-opt/aqua-${{ env.VERSION }}.js"
mv api/aqua-api/target/scala-3.1.3/aqua-api-opt/main.js "$JSAPI"
stat "$JSAPI"
echo "JSAPI=$JSAPI" >> $GITHUB_ENV
@ -83,7 +95,8 @@ jobs:
registry-url: "https://registry.npmjs.org"
- run: cp ${{ env.JS }} ./npm/aqua.js
- run: cp ${{ env.JSAPI }} ./language-server-npm/aqua-lsp-api.js
- run: cp ${{ env.JSLSP }} ./language-server-npm/aqua-lsp-api.js
- run: cp ${{ env.JSAPI }} ./aqua-api-npm/aqua-api.js
- run: npm version ${{ env.VERSION }}
working-directory: ./npm
@ -93,7 +106,7 @@ jobs:
npm i
npm run build
npm publish --access public --tag "${{ github.event.inputs.npm_tag }}"
working-directory: ./npm
working-directory: ./cli/cli-npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@ -134,13 +147,21 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm version ${{ env.VERSION }}
working-directory: ./language-server-npm
working-directory: ./language-server/language-server-npm
- name: Publish aqua LSP API to NPM
run: |
npm i
npm publish --access public
working-directory: ./language-server-npm
working-directory: ./language-server/language-server-npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish aqua API to NPM
run: |
npm i
npm publish --access public
working-directory: ./api/aqua-api-npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

View File

@ -1,4 +1,4 @@
name: Publish snapshot
name: Publish snapshots
on:
workflow_call:
@ -14,7 +14,13 @@ on:
outputs:
aqua-version:
description: "@fluencelabs/aqua version"
value: ${{ jobs.publish-snapshot.outputs.aqua-version }}
value: ${{ jobs.aqua.outputs.version }}
aqua-lsp-api-version:
description: "@fluencelabs/aqua-language-server-api version"
value: ${{ jobs.aqua-lsp-api.outputs.version }}
aqua-api-version:
description: "@fluencelabs/aqua-api version"
value: ${{ jobs.aqua-api.outputs.version }}
env:
FORCE_COLOR: true
@ -31,12 +37,9 @@ jobs:
repository: fluencelabs/aqua
ref: ${{ inputs.ref }}
- name: Generate version
- name: Generate snapshot version
id: version
run: |
SHA=${{ github.event.pull_request.head.sha }}
echo "sha=${SHA::7}" >> $GITHUB_OUTPUT
echo "branch=${GITHUB_HEAD_REF//[^a-zA-Z0-9-]/-}" >> $GITHUB_OUTPUT
uses: fluencelabs/github-actions/generate-snapshot-id@main
- name: Cache Scala
uses: coursier/cache-action@v6
@ -44,43 +47,65 @@ jobs:
- name: Setup Scala
uses: coursier/setup-action@v1
- name: Compile aqua
- name: JS build
env:
BUILD_NUMBER: ${{ steps.version.outputs.branch }}-${{ steps.version.outputs.sha }}-${{ github.run_number }}-${{ github.run_attempt }}
run: sbt "cliJS/fastOptJS"
BUILD_NUMBER: ${{ steps.version.outputs.id }}
run: sbt cliJS/fastOptJS
- name: Upload compiled aqua
- name: JS LSP API build
env:
BUILD_NUMBER: ${{ steps.version.outputs.id }}
run: sbt language-server-api/fastOptJS
- name: JS Aqua API build
env:
BUILD_NUMBER: ${{ steps.version.outputs.id }}
run: sbt aqua-api/fastOptJS
- name: Upload aqua-js artifact
uses: actions/upload-artifact@v3
with:
name: aqua
path: cli/.js/target/scala-*/cli-fastopt.js
name: aqua-js
path: cli/cli/.js/target/scala-*/cli-fastopt.js
publish-snapshot:
name: "Publish snapshot"
- name: Upload aqua-js-lsp artifact
uses: actions/upload-artifact@v3
with:
name: aqua-js-lsp
path: language-server/language-server-api/target/scala-*/language-server-api-fastopt.js
- name: Upload aqua-js-api artifact
uses: actions/upload-artifact@v3
with:
name: aqua-js-api
path: api/aqua-api/target/scala-*/aqua-api-fastopt.js
aqua:
name: "Publish @fluencelabs/aqua"
runs-on: ubuntu-latest
needs: compile
outputs:
aqua-version: "${{ steps.snapshot.outputs.version }}"
version: "${{ steps.snapshot.outputs.version }}"
permissions:
contents: read
id-token: write
steps:
- name: Checkout aqua
- name: Checkout repository
uses: actions/checkout@v3
with:
repository: fluencelabs/aqua
ref: ${{ inputs.ref }}
with: ${{ inputs.ref }}
- name: Download compiled aqua
- name: Download aqua-js artifact
uses: actions/download-artifact@v3
with:
name: aqua
name: aqua-js
- run: mv scala-*/cli-fastopt.js npm/aqua.js
- run: mv scala-*/cli-fastopt.js cli/cli-npm/aqua.js
- name: Import secrets
uses: hashicorp/vault-action@v2.4.3
@ -100,19 +125,19 @@ jobs:
with:
node-version: "16"
registry-url: "https://npm.fluence.dev"
cache-dependency-path: "npm/package-lock.json"
cache-dependency-path: "cli/cli-npm/package-lock.json"
cache: "npm"
- run: npm i
working-directory: npm
working-directory: cli/cli-npm
- name: Set fluence-js version from branch
if: inputs.fluence-js-version != 'null'
working-directory: npm
working-directory: cli/cli-npm
run: npm i --save -E @fluencelabs/fluence@${{ inputs.fluence-js-version }}
- run: npm run build
working-directory: npm
working-directory: cli/cli-npm
- name: Generate snapshot version
id: version
@ -122,5 +147,134 @@ jobs:
id: snapshot
uses: fluencelabs/github-actions/npm-publish-snapshot@main
with:
working-directory: npm
working-directory: cli/cli-npm
id: ${{ steps.version.outputs.id }}
aqua-lsp:
name: "Publish @fluencelabs/aqua-language-server-api"
runs-on: ubuntu-latest
needs: compile
outputs:
version: "${{ steps.snapshot.outputs.version }}"
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
repository: fluencelabs/aqua
with: ${{ inputs.ref }}
- name: Download aqua-js-lsp artifact
uses: actions/download-artifact@v3
with:
name: aqua-js-lsp
- run: mv scala-*/language-server-api-fastopt.js language-server/language-server-npm/aqua-lsp-api.js
- name: Import secrets
uses: hashicorp/vault-action@v2.4.3
with:
url: https://vault.fluence.dev
path: jwt/github
role: ci
method: jwt
jwtGithubAudience: "https://github.com/fluencelabs"
jwtTtl: 300
exportToken: false
secrets: |
kv/npm-registry/basicauth/ci token | NODE_AUTH_TOKEN
- name: Setup node with self-hosted npm registry
uses: actions/setup-node@v3
with:
node-version: "16"
registry-url: "https://npm.fluence.dev"
cache-dependency-path: "language-server/language-server-npm/package-lock.json"
cache: "npm"
- run: npm i
working-directory: language-server/language-server-npm
- name: Generate snapshot version
id: version
uses: fluencelabs/github-actions/generate-snapshot-id@main
- name: Publish snapshot
id: snapshot
uses: fluencelabs/github-actions/npm-publish-snapshot@main
with:
working-directory: language-server/language-server-npm
id: ${{ steps.version.outputs.id }}
aqua-api:
name: "Publish @fluencelabs/aqua-api"
runs-on: ubuntu-latest
needs: compile
outputs:
version: "${{ steps.snapshot.outputs.version }}"
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
repository: fluencelabs/aqua
with: ${{ inputs.ref }}
- name: Download aqua-js artifact
uses: actions/download-artifact@v3
with:
name: aqua-js-api
- run: mv scala-*/aqua-api-fastopt.js api/aqua-api-npm/aqua-api.js
- name: Import secrets
uses: hashicorp/vault-action@v2.4.3
with:
url: https://vault.fluence.dev
path: jwt/github
role: ci
method: jwt
jwtGithubAudience: "https://github.com/fluencelabs"
jwtTtl: 300
exportToken: false
secrets: |
kv/npm-registry/basicauth/ci token | NODE_AUTH_TOKEN
- name: Setup node with self-hosted npm registry
uses: actions/setup-node@v3
with:
node-version: "16"
registry-url: "https://npm.fluence.dev"
cache-dependency-path: "api/aqua-api-npm/package-lock.json"
cache: "npm"
- run: npm i
working-directory: api/aqua-api-npm
- name: Set fluence-js version from branch
if: inputs.fluence-js-version != 'null'
working-directory: cli/cli-npm
run: npm i --save-dev -E @fluencelabs/fluence@${{ inputs.fluence-js-version }}
- name: Generate snapshot version
id: version
uses: fluencelabs/github-actions/generate-snapshot-id@main
- name: Publish snapshot
id: snapshot
uses: fluencelabs/github-actions/npm-publish-snapshot@main
with:
working-directory: api/aqua-api-npm
id: ${{ steps.version.outputs.id }}

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ project/target
.DS_Store
npm/aqua.js
**/node_modules

27
api/aqua-api-npm/aqua-api.d.ts vendored Normal file
View File

@ -0,0 +1,27 @@
import type {FunctionCallDef, ServiceDef} from "@fluencelabs/fluence/dist/internal/compilerSupport/v3impl/interface"
export class AquaConfig {
constructor(logLevel: string, constants: string[], noXor: boolean, noRelay: boolean);
logLevel?: string
constants?: string[]
noXor?: boolean
noRelay?: boolean
}
export class AquaFunction {
funcDef: FunctionCallDef
script: string
}
export class CompilationResult {
services: ServiceDef[]
functions: Record<string, AquaFunction>
}
export class Compiler {
compileRun(functionStr: string, arguments: any, path: string, imports: string[], config?: AquaConfig): Promise<AquaFunction>;
compile(path: string, imports: string[], config?: AquaConfig): Promise<CompilationResult>;
compileString(input: string, imports: string[], config?: AquaConfig): Promise<CompilationResult>;
}
export var Aqua: Compiler;

View File

@ -0,0 +1 @@
const metaUrl = ""

12982
api/aqua-api-npm/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
{
"name": "@fluencelabs/aqua-api",
"version": "0.0.3",
"description": "Aqua API",
"type": "commonjs",
"files": [
"aqua-api.js",
"aqua-api.d.ts",
"meta-utils.js"
],
"scripts": {
"move:scalajs": "cp ../aqua-api/target/scala-3.1.3/aqua-api-opt/main.js ./aqua-api.js",
"move:fast": "cp ../aqua-api/target/scala-3.1.3/aqua-api-fastopt/main.js ./aqua-api.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/fluencelabs/aqua.git"
},
"keywords": [
"aqua",
"fluence"
],
"author": "Fluence Labs",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/fluencelabs/aqua/issues"
},
"homepage": "https://github.com/fluencelabs/aqua#readme",
"devDependencies": {
"@fluencelabs/fluence": "^0.27.3"
}
}

View File

@ -0,0 +1,237 @@
package aqua.api
import aqua.ErrorRendering.showError
import aqua.backend.{AirFunction, Backend, Generated}
import aqua.compiler.*
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.logging.{LogFormatter, LogLevels}
import aqua.constants.Constants
import aqua.io.*
import aqua.raw.ops.Call
import aqua.run.{CallInfo, CallPreparer, CliFunc, FuncCompiler, RunPreparer}
import aqua.parser.lexer.{LiteralToken, Token}
import aqua.parser.lift.FileSpan.F
import aqua.parser.lift.{FileSpan, Span}
import aqua.parser.{ArrowReturnError, BlockIndentError, LexerError, ParserError}
import aqua.semantics.{CompilerState, HeaderError, RulesViolated, WrongAST}
import aqua.{AquaIO, SpanParser}
import aqua.model.transform.{Transform, TransformConfig}
import aqua.backend.api.APIBackend
import cats.data.{Chain, NonEmptyChain, Validated, ValidatedNec}
import cats.data.Validated.{invalidNec, validNec, Invalid, Valid}
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.effect.IO
import cats.effect.unsafe.implicits.global
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.annotation.*
import scala.scalajs.js.{undefined, UndefOr}
import aqua.js.{FunctionDefJs, ServiceDefJs, VarJson}
import aqua.model.AquaContext
import aqua.raw.ops.CallArrowRawTag
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.res.AquaRes
import cats.Applicative
@JSExportTopLevel("AquaFunction")
case class AquaFunction(
@JSExport
funcDef: FunctionDefJs,
@JSExport
script: String
)
case class AquaAPIConfig(
logLevel: String = "info",
constants: List[String] = Nil,
noXor: Boolean = false,
noRelay: Boolean = false
)
object AquaAPIConfig {
def fromJS(cjs: AquaConfig): AquaAPIConfig = {
AquaAPIConfig(
cjs.logLevel.getOrElse("info"),
cjs.constants.map(_.toList).getOrElse(Nil),
cjs.noXor.getOrElse(false),
cjs.noRelay.getOrElse(false)
)
}
}
@JSExportTopLevel("AquaConfig")
case class AquaConfig(
@JSExport
logLevel: js.UndefOr[String],
@JSExport
constants: js.UndefOr[js.Array[String]],
@JSExport
noXor: js.UndefOr[Boolean],
@JSExport
noRelay: js.UndefOr[Boolean]
)
@JSExportTopLevel("CompilationResult")
case class CompilationResult(
@JSExport
services: js.Array[ServiceDefJs],
@JSExport
functions: js.Dictionary[AquaFunction]
)
@JSExportTopLevel("Aqua")
object AquaAPI extends App with Logging {
def getTag(serviceId: String, value: VarRaw) = {
CallArrowRawTag.service(
LiteralRaw.quote(serviceId),
value.name,
Call(List.empty, List(Call.Export(value.name, value.baseType)))
)
}
@JSExport
def compileRun(
functionStr: String,
arguments: js.Dynamic,
pathStr: String,
imports: js.Array[String],
aquaConfigJS: js.UndefOr[AquaConfig]
): js.Promise[AquaFunction] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val aquaConfig: AquaAPIConfig =
aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig())
LogFormatter.initLogger(Some(LogLevels.levelFromString(aquaConfig.logLevel).toOption.get))
val transformConfig = TransformConfig()
new FuncCompiler[IO](
Some(RelativePath(Path(pathStr))),
imports.toList.map(Path.apply),
transformConfig
).compile().map { contextV =>
contextV.andThen { context =>
CliFunc.fromString(functionStr).leftMap(errs => NonEmptyChain.fromNonEmptyList(errs)).andThen { cliFunc =>
FuncCompiler.findFunction(context, cliFunc).andThen { arrow =>
VarJson.checkDataGetServices(cliFunc.args, Some(arguments)).andThen {
case (argsWithTypes, _) =>
val func = cliFunc.copy(args = argsWithTypes)
val preparer = new RunPreparer(
func,
arrow,
transformConfig
)
preparer.prepare().map { ci =>
AquaFunction(FunctionDefJs(ci.definitions), ci.air)
}
}
}
}
}
}.flatMap {
case Valid(result) => IO.pure(result)
case Invalid(err) =>
err.map(_.show).distinct.map(OutputPrinter.errorF[IO]).sequence
IO.raiseError[AquaFunction](new Error("Compilation failed."))
}.unsafeToFuture().toJSPromise
}
@JSExport
def compile(
pathStr: String,
imports: js.Array[String],
aquaConfigJS: js.UndefOr[AquaConfig]
): js.Promise[CompilationResult] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path(pathStr)
val sources = new AquaFileSources[IO](path, imports.toList.map(Path.apply))
compileRaw(aquaConfigJS, sources)
}
@JSExport
def compileString(
input: String,
imports: js.Array[String],
aquaConfigJS: js.UndefOr[AquaConfig]
): js.Promise[CompilationResult] = {
implicit val aio: AquaIO[IO] = new AquaFilesIO[IO]
val path = Path("")
val strSources: AquaFileSources[IO] =
new AquaFileSources[IO](path, imports.toList.map(Path.apply)) {
override def sources: IO[ValidatedNec[AquaFileError, Chain[(FileModuleId, String)]]] = {
IO.pure(Valid(Chain.one((FileModuleId(path), input))))
}
}
compileRaw(aquaConfigJS, strSources)
}
def compileRaw(
aquaConfigJS: js.UndefOr[AquaConfig],
sources: AquaSources[IO, AquaFileError, FileModuleId]
): js.Promise[CompilationResult] = {
val aquaConfig =
aquaConfigJS.toOption.map(cjs => AquaAPIConfig.fromJS(cjs)).getOrElse(AquaAPIConfig())
(
LogLevels.levelFromString(aquaConfig.logLevel),
Constants.parse(aquaConfig.constants)
).mapN { (level, constants) =>
LogFormatter.initLogger(Some(level))
val config = AquaCompilerConf(constants)
val transformConfig = TransformConfig()
val proc = for {
res <- CompilerAPI
.compile[IO, AquaFileError, FileModuleId, FileSpan.F](
sources,
SpanParser.parser,
new AirValidator[IO] {
override def init(): IO[Unit] = Applicative[IO].pure(())
override def validate(airs: List[AirFunction]): IO[ValidatedNec[String, Unit]] =
Applicative[IO].pure(validNec(()))
},
new Backend.Transform:
override def transform(ex: AquaContext): AquaRes =
Transform.contextRes(ex, transformConfig)
override def generate(aqua: AquaRes): Seq[Generated] = APIBackend.generate(aqua)
,
config
)
jsResult <- res match {
case Valid(compiled) =>
val allGenerated: List[Generated] = compiled.toList.flatMap(_.compiled)
val serviceDefs = allGenerated.flatMap(_.services).map(s => ServiceDefJs(s)).toJSArray
val functions = allGenerated.flatMap(
_.air.map(as => (as.name, AquaFunction(FunctionDefJs(as.funcDef), as.air)))
)
IO.pure(CompilationResult(serviceDefs, js.Dictionary.apply(functions: _*)))
case Invalid(errChain) =>
errChain.map(_.show).distinct.map(OutputPrinter.errorF[IO]).sequence
IO.raiseError[CompilationResult](new Error("Compilation failed."))
}
} yield {
jsResult
}
proc.unsafeToFuture().toJSPromise
} match {
case Valid(pr) => pr
case Invalid(err) => js.Promise.reject(err)
}
}
}

View File

@ -0,0 +1,15 @@
package aqua.run
import aqua.definitions.FunctionDef
case class CallInfo(
name: String,
air: String,
definitions: FunctionDef,
config: RunConfig
)
case class RunInfo(
name: String,
air: String,
definitions: FunctionDef
)

View File

@ -0,0 +1,142 @@
package aqua.run
import aqua.backend.air.FuncAirGen
import aqua.definitions.{FunctionDef, TypeDefinition}
import aqua.io.OutputPrinter
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{FuncArrow, ValueModel, VarModel}
import aqua.parser.lexer.CallArrowToken
import aqua.parser.lift.Span
import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, SeqTag}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
import cats.data.Validated.{invalid, invalidNec, invalidNel, validNec, validNel}
import cats.data.{NonEmptyList, Validated, ValidatedNec}
import cats.effect.kernel.Async
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.partialOrder.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import cats.{Id, ~>}
import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
class CallPreparer(
func: CliFunc,
funcCallable: FuncArrow,
getters: List[CallArrowRawTag],
printResultTag: List[VarRaw] => CallArrowRawTag,
finisherService: CallArrowRawTag,
config: RunConfig,
transformConfig: TransformConfig
) {
def validateArguments(
funcDomain: List[(String, Type)],
args: List[ValueRaw]
): ValidatedNec[String, Unit] = {
if (funcDomain.size != args.length) {
invalidNec(
s"Number of arguments for the function is incorrect. Expected: ${args.length}. Actual: ${funcDomain.size}"
)
} else {
funcDomain
.zip(args)
.map { case ((name, lt), rt) =>
rt match {
case VarRaw(n, _) =>
TypeValidator.validateTypes(n, lt, Some(rt.`type`))
case _ =>
TypeValidator.validateTypes(name, lt, Some(rt.`type`))
}
}
.sequence
.map(_ => ())
}
}
// Wraps function with necessary services, registers services and calls wrapped function with FluenceJS
def prepare(): ValidatedNec[String, CallInfo] = {
validateArguments(
funcCallable.arrowType.domain.labelledData,
func.args
).map(_ =>
genCallInfo(
wrapCall()
)
)
}
// Generates air from function, register all services and make a call through FluenceJS
private def genCallInfo(
wrapped: FuncArrow
): CallInfo = {
// TODO: prob we can turn this Eval into F
val funcRes = Transform.funcRes(wrapped, transformConfig).value
val definitions = FunctionDef(funcRes)
val air = FuncAirGen(funcRes).generate.show
if (config.common.flags.printAir) {
OutputPrinter.print(air)
}
CallInfo(func.name, air, definitions, config)
}
// Wrap a function like this:
// func wrapFunc():
// arg1 <- getDataSrv()
// arg2 <- getDataSrv()
// ...
// res <- funcCallable(args:_*)
// Console.print(res)
// Finisher.finish()
private def wrapCall(): FuncArrow = {
val codomain = funcCallable.arrowType.codomain.toList
// pass results to a printing service if an input function returns a result
// otherwise just call it
val body = codomain match {
case Nil =>
CallArrowRawTag.func(func.name, Call(func.args, Nil)).leaf
case types =>
val (variables, exports) = types.zipWithIndex.map { case (t, idx) =>
val name = config.resultName + idx
(VarRaw(name, t), Call.Export(name, t))
}.unzip
val callFuncTag =
CallArrowRawTag.func(func.name, Call(func.args, exports))
val consoleServiceTag = printResultTag(variables)
SeqTag.wrap(
callFuncTag.leaf,
consoleServiceTag.leaf
)
}
// return something to wait a result if we have return value in function
// this is needed to catch an error if it will be occurred
val (returnCodomain, ret) = if (codomain.isEmpty) {
(NilType, Nil)
} else {
(UnlabeledConsType(ScalarType.string, NilType), LiteralRaw.quote("ok") :: Nil)
}
FuncArrow(
config.functionWrapperName,
SeqTag.wrap((getters.map(_.leaf) :+ body :+ finisherService.leaf): _*),
// no arguments and returns "ok" string
ArrowType(NilType, returnCodomain),
ret,
Map(func.name -> funcCallable),
Map.empty,
None
)
}
}

View File

@ -0,0 +1,71 @@
package aqua.run
import aqua.parser.lexer.{CallArrowToken, CollectionToken, LiteralToken, VarToken}
import aqua.parser.lift.Span
import aqua.raw.value.{CollectionRaw, LiteralRaw, ValueRaw, VarRaw}
import aqua.types.{ArrayType, BottomType}
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import cats.data.Validated.{invalid, invalidNel, validNel}
import cats.{Id, ~>}
import cats.syntax.traverse.*
case class CliFunc(name: String, args: List[ValueRaw] = Nil, ability: Option[String] = None)
object CliFunc {
def spanToId: Span.S ~> Id = new (Span.S ~> Id) {
override def apply[A](span: Span.S[A]): Id[A] = {
span._2
}
}
def fromString(func: String): ValidatedNel[String, CliFunc] = {
CallArrowToken.callArrow.parseAll(func.trim) match {
case Right(exprSpan) =>
val expr = exprSpan.mapK(spanToId)
val argsV = expr.args.collect {
case LiteralToken(value, ts) =>
validNel(LiteralRaw(value, ts))
case VarToken(name, _) =>
validNel(VarRaw(name.value, BottomType))
case CollectionToken(_, values) =>
val hasVariables = values.exists {
case LiteralToken(_, _) => false
case _ => true
}
if (!hasVariables) {
val literals = values.collect { case LiteralToken(value, ts) =>
LiteralRaw(value, ts)
}
val hasSameTypesOrEmpty =
literals.isEmpty || literals.map(_.baseType).toSet.size == 1
if (hasSameTypesOrEmpty) {
validNel(
NonEmptyList
.fromList(literals)
.map(l => CollectionRaw(l, ArrayType(l.head.baseType)))
.getOrElse(ValueRaw.Nil)
)
} else
invalidNel(
"If the argument is an array, then it must contain elements of the same type."
)
} else
invalidNel(
"Array arguments can only have numbers, strings, or booleans."
)
case CallArrowToken(_, _, _) =>
invalidNel("Function calls as arguments are not supported.")
}.sequence
argsV.andThen(args =>
validNel(CliFunc(expr.funcName.value, args, expr.ability.map(_.name)))
)
case Left(err) => invalid(err.expected.map(_.context.mkString("\n")))
}
}
}

View File

@ -0,0 +1,99 @@
package aqua.run
import aqua.ErrorRendering.showError
import aqua.compiler.{AquaCompiler, AquaCompilerConf, CompilerAPI}
import aqua.files.{AquaFileSources, FileModuleId}
import aqua.{AquaIO, SpanParser}
import aqua.io.{AquaFileError, AquaPath, PackagePath, Prelude}
import aqua.model.transform.TransformConfig
import aqua.model.{AquaContext, FuncArrow}
import aqua.parser.lift.FileSpan
import aqua.run.CliFunc
import cats.data.Validated.{invalidNec, validNec}
import cats.data.{Chain, NonEmptyList, Validated, ValidatedNec}
import cats.effect.IO
import cats.effect.kernel.{Async, Clock}
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.monad.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.duration.Duration
class FuncCompiler[F[_]: Files: AquaIO: Async](
input: Option[AquaPath],
imports: List[Path],
transformConfig: TransformConfig
) extends Logging {
private def compileToContext(
path: Path,
imports: List[Path],
config: AquaCompilerConf = AquaCompilerConf(transformConfig.constantsList)
) = {
val sources = new AquaFileSources[F](path, imports)
CompilerAPI
.compileToContext[F, AquaFileError, FileModuleId, FileSpan.F](
sources,
SpanParser.parser,
config
)
.map(_.leftMap(_.map(_.show)))
}
private def compileBuiltins() = {
for {
path <- PackagePath.builtin.getPath()
context <- compileToContext(path, Nil)
} yield {
context
}
}
// Compile and get only one function
def compile(
preludeImports: List[Path] = Nil,
withBuiltins: Boolean = false
): F[ValidatedNec[String, Chain[AquaContext]]] = {
for {
// compile builtins and add it to context
builtinsV <-
if (withBuiltins) compileBuiltins()
else validNec[String, Chain[AquaContext]](Chain.empty).pure[F]
compileResult <- input.map { ap =>
// compile only context to wrap and call function later
Clock[F].timed(
ap.getPath().flatMap(p => compileToContext(p, preludeImports ++ imports))
)
}.getOrElse((Duration.Zero, validNec[String, Chain[AquaContext]](Chain.empty)).pure[F])
(compileTime, contextV) = compileResult
} yield {
logger.debug(s"Compile time: ${compileTime.toMillis}ms")
// add builtins to the end of context
contextV.andThen(c => builtinsV.map(bc => c ++ bc))
}
}
}
object FuncCompiler {
def findFunction(
contexts: Chain[AquaContext],
func: CliFunc
): ValidatedNec[String, FuncArrow] =
func.ability
.fold(
contexts
.collectFirstSome(_.allFuncs.get(func.name))
)(ab => contexts.collectFirstSome(_.abilities.get(ab).flatMap(_.allFuncs.get(func.name))))
.map(validNec)
.getOrElse(
Validated.invalidNec[String, FuncArrow](
s"There is no function '${func.ability.map(_ + ".").getOrElse("")}${func.name}' or it is not exported. Check the spelling or see https://fluence.dev/docs/aqua-book/language/header/#export"
)
)
}

View File

@ -0,0 +1,39 @@
package aqua.run
import aqua.logging.LogLevels
import aqua.raw.ConstantRaw
import aqua.raw.value.VarRaw
import scribe.Level
import java.util.concurrent.TimeUnit
import scala.concurrent.duration.Duration
import scala.util.Try
case class Flags(
printAir: Boolean,
showConfig: Boolean,
verbose: Boolean,
noXor: Boolean,
noRelay: Boolean
)
case class GeneralOptions(
timeout: Duration,
logLevel: LogLevels,
multiaddr: String,
on: Option[String],
flags: Flags,
secretKey: Option[Array[Byte]],
constants: List[ConstantRaw]
)
// `run` command configuration
case class RunConfig(
common: GeneralOptions,
resultPrinterServiceId: String = "--after-callback-srv-service--",
resultPrinterName: String = "console-log",
finisherServiceId: String = "--finisher--",
finisherFnName: String = "--finish-execution--",
resultName: String = "-some-unique-res-name-",
functionWrapperName: String = "--someFuncToRun--"
)

View File

@ -0,0 +1,120 @@
package aqua.run
import aqua.backend.air.FuncAirGen
import aqua.definitions.{FunctionDef, TypeDefinition}
import aqua.io.OutputPrinter
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{FuncArrow, ValueModel, VarModel}
import aqua.parser.lexer.CallArrowToken
import aqua.parser.lift.Span
import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, SeqTag}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
import cats.data.Validated.{invalid, invalidNec, invalidNel, validNec, validNel}
import cats.data.{NonEmptyList, Validated, ValidatedNec}
import cats.effect.kernel.Async
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.partialOrder.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import cats.{~>, Id}
import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
class RunPreparer(
func: CliFunc,
funcCallable: FuncArrow,
transformConfig: TransformConfig
) {
def validateArguments(
funcDomain: List[(String, Type)],
args: List[ValueRaw]
): ValidatedNec[String, Unit] = {
if (funcDomain.size != args.length) {
invalidNec(
s"Number of arguments for the function is incorrect. Expected: ${args.length}. Actual: ${funcDomain.size}"
)
} else {
funcDomain
.zip(args)
.map { case ((name, lt), rt) =>
rt match {
case VarRaw(n, _) =>
TypeValidator.validateTypes(n, lt, Some(rt.`type`))
case _ =>
TypeValidator.validateTypes(name, lt, Some(rt.`type`))
}
}
.sequence
.map(_ => ())
}
}
// Wraps function with necessary services, registers services and calls wrapped function with FluenceJS
def prepare(): ValidatedNec[String, RunInfo] = {
validateArguments(
funcCallable.arrowType.domain.labelledData,
func.args
).map(_ =>
genCallInfo(
wrapCall()
)
)
}
// Generates air from function, register all services and make a call through FluenceJS
private def genCallInfo(
wrapped: FuncArrow
): RunInfo = {
// TODO: prob we can turn this Eval into F
val funcRes = Transform.funcRes(wrapped, transformConfig).value
val definitions = FunctionDef(funcRes)
val air = FuncAirGen(funcRes).generate.show
RunInfo(func.name, air, definitions)
}
private def wrapCall(): FuncArrow = {
val codomain = funcCallable.arrowType.codomain.toList
// pass results to a printing service if an input function returns a result
// otherwise just call it
val (results, body) = codomain match {
case Nil =>
Nil -> CallArrowRawTag.func(func.name, Call(func.args, Nil)).leaf
case types =>
val (variables, exports) = types.zipWithIndex.map { case (t, idx) =>
val name = func.name + "_result" + idx
(VarRaw(name, t), Call.Export(name, t))
}.unzip
val callFuncTag =
CallArrowRawTag.func(func.name, Call(func.args, exports))
variables -> callFuncTag.leaf
}
val returnCodomain = ProductType(results.map(_.`type`))
// arguments is only variables, without literals
val argumentsType = ProductType.labelled(func.args.zip(funcCallable.arrowType.domain.labelledData).collect {
case (VarRaw(name, _), (_, t)) => (name, t)
})
FuncArrow(
func.name + "Run",
SeqTag.wrap(body),
ArrowType(argumentsType, returnCodomain),
results,
Map(func.name -> funcCallable),
Map.empty,
None
)
}
}

View File

@ -1,4 +1,4 @@
package aqua.json
package aqua.run
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
@ -13,11 +13,11 @@ import cats.syntax.traverse.*
import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
import scala.scalajs.js
import scala.scalajs.js.JSON
object TypeValidator {
import aqua.types.Type.typesPartialOrder
/**
* Compare and validate type from Aqua file and type generated from JSON.
* Also, the validation will succeed if the JSON type is missing an array or an optional field.

View File

@ -1,3 +1,7 @@
aqua FooBars declares wait
export wait
data Record:
relay_id: []string
peer_id: string
@ -70,9 +74,25 @@ service Ser("ser"):
-- Op2.identity(res!2)
-- <- res
func streamAssignment(arr: []string) -> string:
stream: *[]u32
stream <<- [0]
a = stream[arr.length - 1][0]
b = arr[a]
<- b
data InnerObj:
arr: []string
num: u32
data SomeObj:
str: string
num: u64
inner: InnerObj
func wait(i: []u32) -> SomeObj:
<- SomeObj(str = "some str",
num = 4,
inner = InnerObj(arr = ["a", "b", "c"], num = i[2])
)
-- func a(nums: []u32) -> []u32:
-- <- nums
--
-- func some():
-- a([1,2,3,4])

View File

@ -1,6 +1,7 @@
package aqua.backend.air
import aqua.backend.{AirString, Backend, Generated, Version}
import aqua.backend.{AirFunction, Backend, Generated, Version}
import aqua.definitions.FunctionDef
import aqua.res.AquaRes
import cats.syntax.show.*
@ -19,7 +20,8 @@ object AirBackend extends Backend {
aqua.funcs.toList.map { fr =>
val airStr = FuncAirGen(fr).generate.show
Generated("." + fr.funcName + ext, docs + airStr, AirString(fr.funcName, airStr) :: Nil)
val funcDef = FunctionDef(fr)
Generated("." + fr.funcName + ext, docs + airStr, AirFunction(fr.funcName, airStr, funcDef) :: Nil)
}
}
}

View File

@ -0,0 +1,25 @@
package aqua.backend.api
import aqua.backend.air.AirBackend
import aqua.backend.{Backend, Generated}
import aqua.res.AquaRes
import aqua.definitions.{LabeledProductTypeDef, ArrowTypeDef, ServiceDef}
object APIBackend extends Backend {
override def generate(res: AquaRes): Seq[Generated] =
if (res.isEmpty) Nil
else {
val airGenerated = AirBackend.generate(res)
val services = res.services.map { srv =>
val functions = LabeledProductTypeDef(
srv.members.map { case (n, a) => (n, ArrowTypeDef(a)) }
)
ServiceDef(srv.defaultId.map(s => s.replace("\"", "")), functions)
}.toList
Generated("", "", airGenerated.flatMap(_.air).toList, services) :: Nil
}
}

View File

@ -1,7 +1,8 @@
package aqua.backend
package aqua.definitions
import aqua.res.FuncRes
import aqua.types.*
import aqua.definitions.*
import io.circe.*
import io.circe.parser.*
import io.circe.syntax.*
@ -174,6 +175,8 @@ case class StructTypeDef(name: String, fields: Map[String, TypeDefinition]) exte
case class LabeledProductTypeDef(fields: List[(String, TypeDefinition)]) extends ProductTypeDef {
val tag = "labeledProduct"
override def toString: String = s"LabeledProduct(${fields.map(_.toString())})"
}
case class UnlabeledProductTypeDef(items: List[TypeDefinition]) extends ProductTypeDef {

View File

@ -0,0 +1,5 @@
package aqua.backend
import aqua.definitions.FunctionDef
case class AirFunction(name: String, air: String, funcDef: FunctionDef)

View File

@ -1,3 +0,0 @@
package aqua.backend
case class AirString(name: String, air: String)

View File

@ -1,9 +1,11 @@
package aqua.backend
import aqua.definitions.ServiceDef
/**
* Compilation result
*
* @param suffix extension or another info that will be added to a resulted file
* @param content compiled code
*/
case class Generated(suffix: String, content: String, air: List[AirString])
case class Generated(suffix: String, content: String, air: List[AirFunction], services: List[ServiceDef] = Nil)

View File

@ -6,7 +6,7 @@ import aqua.res.AquaRes
case class OutputFile(res: AquaRes) {
def generate(types: Types, isJs: Boolean, isCommonJS: Boolean): (List[AirString], String) = {
def generate(types: Types, isJs: Boolean, isCommonJS: Boolean): (List[AirFunction], String) = {
import types.*
val services = res.services
.map(s => OutputService(s, types))

View File

@ -20,9 +20,10 @@ case class OutputFunc(func: FuncRes, types: Types) {
val funcTypes = types.funcType(func)
import funcTypes.*
import TypeDefinition.*
import aqua.definitions.TypeDefinition.*
import aqua.definitions.*
def generate: (AirString, String) = {
def generate: (AirFunction, String) = {
val tsAir = FuncAirGen(func).generate
val codeLeftSpace = " " * 20
@ -30,7 +31,7 @@ case class OutputFunc(func: FuncRes, types: Types) {
val funcDef = FunctionDef(func)
(
AirString(func.funcName, script),
AirFunction(func.funcName, script, funcDef),
s"""${funcTypes.generate}
|export function ${func.funcName}(${typed("...args", "any")}) {
|

View File

@ -14,7 +14,8 @@ case class OutputService(srv: ServiceRes, types: Types) {
private val serviceTypes = types.serviceType(srv)
import serviceTypes.*
import TypeDefinition._
import aqua.definitions.TypeDefinition.*
import aqua.definitions.*
def generate: String =
val functions = LabeledProductTypeDef(

View File

@ -17,7 +17,7 @@ val scribeV = "3.7.1"
name := "aqua-hll"
val commons = Seq(
baseAquaVersion := "0.8.0",
baseAquaVersion := "0.9.0",
version := baseAquaVersion.value + "-" + sys.env.getOrElse("BUILD_NUMBER", "SNAPSHOT"),
scalaVersion := dottyVersion,
libraryDependencies ++= Seq(
@ -42,7 +42,7 @@ commons
lazy val cli = crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("cli"))
.in(file("cli/cli"))
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
@ -50,13 +50,13 @@ lazy val cli = crossProject(JSPlatform, JVMPlatform)
"com.monovore" %%% "decline-effect" % declineV
)
)
.dependsOn(compiler, `backend-air`, `backend-ts`, io)
.dependsOn(compiler, `backend-air`, `backend-ts`, io, definitions, logging, constants, `aqua-run`)
lazy val cliJS = cli.js
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.ESModule)),
scalaJSUseMainModuleInitializer := true
)
).dependsOn(`js-exports`, `js-imports`)
lazy val cliJVM = cli.jvm
.settings(
@ -67,6 +67,13 @@ lazy val cliJVM = cli.jvm
)
)
lazy val `aqua-run` = crossProject(JSPlatform, JVMPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("aqua-run"))
.settings(commons: _*)
.dependsOn(compiler, `backend-air`, `backend-ts`, io, definitions, logging, constants)
lazy val io = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
@ -79,8 +86,10 @@ lazy val io = crossProject(JVMPlatform, JSPlatform)
)
.dependsOn(compiler, parser)
lazy val ioJS = io.js.dependsOn(`js-imports`)
lazy val `language-server-api` = project
.in(file("language-server-api"))
.in(file("language-server/language-server-api"))
.enablePlugins(ScalaJSPlugin)
.settings(commons: _*)
.settings(
@ -95,6 +104,29 @@ lazy val `language-server-api` = project
)
.dependsOn(compiler.js, io.js)
lazy val `js-exports` = project
.in(file("js/js-exports"))
.enablePlugins(ScalaJSPlugin)
.settings(commons: _*)
.dependsOn(`backend`.js, definitions.js)
lazy val `js-imports` = project
.in(file("js/js-imports"))
.enablePlugins(ScalaJSPlugin)
.settings(commons: _*)
.dependsOn(`js-exports`, transform.js)
lazy val `aqua-api` = project
.in(file("api/aqua-api"))
.enablePlugins(ScalaJSPlugin)
.settings(commons: _*)
.settings(
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule)),
scalaJSUseMainModuleInitializer := true,
Test / test := {}
)
.dependsOn(`js-exports`, `aqua-run`.js, `backend-api`.js)
lazy val types = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
@ -197,7 +229,42 @@ lazy val backend = crossProject(JVMPlatform, JSPlatform)
buildInfoKeys := Seq[BuildInfoKey](version),
buildInfoPackage := "aqua.backend"
)
.dependsOn(res)
.dependsOn(res, definitions)
lazy val definitions = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend/definitions"))
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"io.circe" %%% "circe-core",
"io.circe" %%% "circe-generic",
"io.circe" %%% "circe-parser"
).map(_ % circeVersion)
).dependsOn(res, types)
lazy val logging = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("utils/logging"))
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % catsV
)
)
lazy val constants = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("utils/constants"))
.settings(commons: _*)
.settings(
libraryDependencies ++= Seq(
"org.typelevel" %%% "cats-core" % catsV
)
).dependsOn(parser, raw)
lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
@ -206,6 +273,13 @@ lazy val `backend-air` = crossProject(JVMPlatform, JSPlatform)
.settings(commons: _*)
.dependsOn(backend, transform)
lazy val `backend-api` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
.in(file("backend/api"))
.settings(commons: _*)
.dependsOn(backend, transform, `backend-air`)
lazy val `backend-ts` = crossProject(JVMPlatform, JSPlatform)
.withoutSuffixFor(JVMPlatform)
.crossType(CrossType.Pure)
@ -218,4 +292,4 @@ lazy val `backend-ts` = crossProject(JVMPlatform, JSPlatform)
"io.circe" %%% "circe-parser"
).map(_ % circeVersion)
)
.dependsOn(`backend-air`)
.dependsOn(`backend-air`, definitions)

View File

@ -1,175 +0,0 @@
package aqua
import aqua.ErrorRendering.showError
import aqua.backend.{ArrowTypeDef, ProductTypeDef, TypeDefinition}
import aqua.builder.{AquaFunction, Service}
import aqua.compiler.{AquaCompiler, AquaCompilerConf, CompilerAPI}
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.io.AquaFileError
import aqua.js.{Conversions, ServiceHandler, TypeDefinitionJs}
import aqua.json.{JsonEncoder, TypeValidator}
import aqua.model.transform.TransformConfig
import aqua.model.{AquaContext, FuncArrow, ServiceModel}
import aqua.parser.lift.FileSpan
import aqua.raw.ConstantRaw
import aqua.run.RunCommand.logger
import aqua.run.{JsonService, Runner}
import aqua.types.{ArrowType, NilType, ProductType}
import cats.data.Validated.{invalidNec, validNec}
import cats.data.{Chain, NonEmptyList, Validated, ValidatedNec}
import cats.effect.IO
import cats.effect.kernel.{Async, Clock}
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.monad.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.duration.Duration
import scala.scalajs.js
// Function compiler
class FuncCompiler[F[_]: Files: AquaIO: Async](
input: Option[AquaPath],
imports: List[Path],
transformConfig: TransformConfig,
withRunImport: Boolean = false
) extends Logging {
private def findFunctionAndServices(
contexts: Chain[AquaContext],
func: CliFunc,
services: List[JsonService]
): ValidatedNec[String, (FuncArrow, List[Service])] =
func.ability
.fold(
contexts
.collectFirstSome(_.allFuncs.get(func.name))
)(ab => contexts.collectFirstSome(_.abilities.get(ab).flatMap(_.allFuncs.get(func.name))))
.map(validNec)
.getOrElse(
Validated.invalidNec[String, FuncArrow](
s"There is no function '${func.ability.map(_ + ".").getOrElse("")}${func.name}' or it is not exported. Check the spelling or see https://fluence.dev/docs/aqua-book/language/header/#export"
)
)
.andThen { func =>
findServices(contexts, services).map { l =>
(func, l)
}
}
private def findServices(
contexts: Chain[AquaContext],
services: List[JsonService]
): ValidatedNec[String, List[Service]] = {
services
.map(js =>
contexts
.collectFirstSome(_.services.get(js.name))
.map(sm => (js, sm))
.map(validNec)
.getOrElse(
Validated.invalidNec[String, ServiceModel](
s"There is no service '${js.name}' (described in json-service file) in aqua source or it is not exported. Check the spelling or see https://fluence.dev/docs/aqua-book/language/header/#export"
)
)
)
.sequence
.andThen { l =>
l.map { case (jsonService: JsonService, sm: ServiceModel) =>
val aquaFunctions: ValidatedNec[String, NonEmptyList[AquaFunction]] =
jsonService.functions.map { jf =>
sm.arrows(jf.name)
.map { case arr: ArrowType =>
if (arr.domain.isEmpty)
TypeValidator
.validateTypes(jf.name, arr.codomain, Some(ProductType(jf.resultType :: Nil)))
.map { _ =>
new AquaFunction {
override def fnName: String = jf.name
override def handler: ServiceHandler = _ => {
val converted = arr.codomain.toList match {
case h :: _ =>
Conversions.ts2aqua(jf.result, TypeDefinitionJs(TypeDefinition(h)))
case Nil =>
Conversions.ts2aqua(
jf.result,
TypeDefinitionJs(TypeDefinition(NilType))
)
}
js.Promise.resolve(converted)
}
override def arrow: ArrowTypeDef =
ArrowTypeDef(ProductTypeDef(NilType), ProductTypeDef(arr.codomain))
}
}
else
invalidNec(s"Json service '${jf.name}' cannot have any arguments")
}
.getOrElse(
Validated.invalidNec[String, AquaFunction](
s"There is no function '${jf.name}' in service '${jsonService.name}' in aqua source. Check your 'json-service' options"
)
)
}.sequence
aquaFunctions.map(funcs => Service(jsonService.serviceId, funcs))
}.sequence
}
}
private def compileToContext(
path: Path,
imports: List[Path],
config: AquaCompilerConf = AquaCompilerConf(transformConfig.constantsList)
) = {
val sources = new AquaFileSources[F](path, imports)
CompilerAPI
.compileToContext[F, AquaFileError, FileModuleId, FileSpan.F](
sources,
SpanParser.parser,
config
)
.map(_.leftMap(_.map(_.show)))
}
private def compileBuiltins() = {
for {
path <- PackagePath.builtin.getPath()
context <- compileToContext(path, Nil)
} yield {
context
}
}
// Compile and get only one function
def compile(
func: CliFunc,
jsonServices: List[JsonService],
withBuiltins: Boolean = false
): F[ValidatedNec[String, (FuncArrow, List[Service])]] = {
for {
prelude <- Prelude.init[F](withRunImport)
// compile builtins and add it to context
builtinsV <-
if (withBuiltins) compileBuiltins()
else validNec[String, Chain[AquaContext]](Chain.empty).pure[F]
compileResult <- input.map { ap =>
// compile only context to wrap and call function later
Clock[F].timed(ap.getPath().flatMap(p => compileToContext(p, prelude.importPaths ++ imports)))
}.getOrElse((Duration.Zero, validNec[String, Chain[AquaContext]](Chain.empty)).pure[F])
(compileTime, contextV) = compileResult
} yield {
logger.debug(s"Compile time: ${compileTime.toMillis}ms")
// add builtins to the end of context
contextV.andThen(c => builtinsV.map(bc => c ++ bc)) andThen (c =>
findFunctionAndServices(c, func, jsonServices)
)
}
}
}

View File

@ -1,130 +0,0 @@
package aqua.run
import aqua.*
import aqua.ErrorRendering.showError
import aqua.backend.air.{AirBackend, FuncAirGen}
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.backend.{FunctionDef, Generated}
import aqua.builder.{ArgumentGetter, Finisher, ResultPrinter, Service}
import aqua.compiler.{AquaCompiled, AquaCompiler}
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.io.{AquaFileError, OutputPrinter}
import aqua.js.*
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{AquaContext, FuncArrow}
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.LiteralToken
import aqua.parser.lift.FileSpan
import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.run.RunConfig
import aqua.run.RunOpts.transformConfig
import aqua.types.*
import cats.data.*
import cats.effect.*
import cats.effect.kernel.{Async, Clock}
import cats.effect.syntax.async.*
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.list.*
import cats.syntax.monad.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import cats.{Id, Monad, ~>}
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.JSON
import scala.scalajs.js.annotation.*
object RunCommand extends Logging {
def createKeyPair(
sk: Option[Array[Byte]]
): Future[KeyPair] = {
sk.map { arr =>
val typedArr = js.typedarray.Uint8Array.from(arr.map(_.toShort).toJSArray)
KeyPair.fromEd25519SK(typedArr).toFuture
}.getOrElse(KeyPair.randomEd25519().toFuture)
}
/**
* Runs a function that is located in `input` file with FluenceJS SDK. Returns no output
* @param func
* function name
* @param input
* path to an aqua code with a function
* @param imports
* the sources the input needs
*/
def run[F[_]: Files: AquaIO: Async](
func: CliFunc,
input: Option[AquaPath],
imports: List[Path],
runConfig: RunConfig,
transformConfig: TransformConfig
): F[ValidatedNec[String, Unit]] = {
val funcCompiler = new FuncCompiler[F](input, imports, transformConfig, withRunImport = true)
for {
funcArrowV <- funcCompiler.compile(func, runConfig.jsonServices, true)
callResult <- Clock[F].timed {
funcArrowV match {
case Validated.Valid((funcCallable, jsonServices)) =>
val runner =
new Runner(func, funcCallable, runConfig.copy(services = runConfig.services ++ jsonServices), transformConfig)
runner.run()
case i @ Validated.Invalid(_) => i.pure[F]
}
}
(callTime, result) = callResult
} yield {
logger.debug(s"Call time: ${callTime.toMillis}ms")
result
}
}
private val builtinServices =
aqua.builder.Console() :: aqua.builder.IPFSUploader("ipfs") :: aqua.builder.DeployHelper() :: Nil
/**
* Executes a function with the specified settings
* @param common
* common settings
* @param funcName
* function name
* @param inputPath
* path to a file with a function
* @param imports
* imports that must be specified for correct compilation
* @param args
* arguments to pass into a function
* @param argumentGetters
* services to get argument if it is a variable
* @param services
* will be registered before calling for correct execution
* @return
*/
def execRun[F[_]: Async](
runInfo: RunInfo,
): F[ValidatedNec[String, Unit]] = {
val common = runInfo.common
LogFormatter.initLogger(Some(common.logLevel.compiler))
implicit val aio: AquaIO[F] = new AquaFilesIO[F]
RunCommand
.run[F](
runInfo.func,
runInfo.input,
runInfo.imports,
RunConfig(common, runInfo.argumentGetters, runInfo.services ++ builtinServices, runInfo.jsonServices, runInfo.pluginsPaths),
transformConfig(common.on, common.constants, common.flags.noXor, common.flags.noRelay)
)
}
}

View File

@ -1,230 +0,0 @@
package aqua.run
import aqua.backend.air.FuncAirGen
import aqua.backend.{FunctionDef, TypeDefinition}
import aqua.builder.{ArgumentGetter, Finisher, ResultPrinter, Service}
import aqua.io.OutputPrinter
import aqua.js.{Conversions, TypeDefinitionJs}
import aqua.json.TypeValidator
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{FuncArrow, ValueModel, VarModel}
import aqua.raw.ops.{Call, CallArrowRawTag, FuncOp, SeqTag}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
import aqua.{CliFunc, VarJson}
import cats.data.Validated.{invalidNec, validNec}
import cats.data.{Validated, ValidatedNec}
import cats.effect.kernel.Async
import cats.syntax.applicative.*
import cats.syntax.flatMap.*
import cats.syntax.partialOrder.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import scala.collection.immutable.SortedMap
import scala.concurrent.ExecutionContext
import scala.scalajs.js
import scala.scalajs.js.JSON
class Runner(
func: CliFunc,
funcCallable: FuncArrow,
config: RunConfig,
transformConfig: TransformConfig
) {
def resultVariableNames(funcCallable: FuncArrow, name: String): List[String] =
funcCallable.arrowType.codomain.toList.zipWithIndex.map { case (t, idx) =>
name + idx
}
import aqua.types.Type.typesPartialOrder
def validateArguments(
funcDomain: List[(String, Type)],
args: List[ValueRaw]
): ValidatedNec[String, Unit] = {
if (funcDomain.size != args.length) {
invalidNec(
s"Number of arguments for the function is incorrect. Expected: ${args.length}. Actual: ${funcDomain.size}"
)
} else {
funcDomain
.zip(args)
.map { case ((name, lt), rt) =>
rt match {
case VarRaw(n, _) =>
TypeValidator.validateTypes(n, lt, Some(rt.`type`))
case _ =>
TypeValidator.validateTypes(name, lt, Some(rt.`type`))
}
}
.sequence
.map(_ => ())
}
}
// Wraps function with necessary services, registers services and calls wrapped function with FluenceJS
def run[F[_]: Async](): F[ValidatedNec[String, Unit]] = {
validateArguments(
funcCallable.arrowType.domain.labelledData,
func.args
) match {
case Validated.Valid(_) =>
val resultNames = resultVariableNames(funcCallable, config.resultName)
val resultPrinterService =
ResultPrinter(config.resultPrinterServiceId, config.resultPrinterName, resultNames)
val promiseFinisherService =
Finisher(config.finisherServiceId, config.finisherFnName)
val wrappedV = wrapCall(
resultPrinterService,
promiseFinisherService
)
// call an input function from a generated function
wrappedV match {
case Validated.Valid((wrapped, getters)) =>
genAirAndMakeCall[F](
wrapped,
resultPrinterService,
promiseFinisherService,
getters
)
case i @ Validated.Invalid(_) => i.pure[F]
}
case v @ Validated.Invalid(_) =>
v.pure[F]
}
}
// Generates air from function, register all services and make a call through FluenceJS
private def genAirAndMakeCall[F[_]: Async](
wrapped: FuncArrow,
consoleService: ResultPrinter,
finisherService: Finisher,
getters: List[ArgumentGetter]
): F[ValidatedNec[String, Unit]] = {
// TODO: prob we can turn this Eval into F
val funcRes = Transform.funcRes(wrapped, transformConfig).value
val definitions = FunctionDef(funcRes)
val air = FuncAirGen(funcRes).generate.show
if (config.common.flags.printAir) {
OutputPrinter.print(air)
}
FuncCaller.funcCall[F](
func.name,
air,
definitions,
config,
finisherService,
config.services :+ consoleService,
getters
)
}
private def createGetter(value: VarRaw, arg: js.Dynamic, argType: Type): ArgumentGetter = {
val converted = Conversions.ts2aqua(arg, TypeDefinitionJs(TypeDefinition(argType)))
ArgumentGetter(value.copy(baseType = argType), converted)
}
// Creates getter services for variables. Return an error if there is no variable in services
// and type of this variable couldn't be optional
private def getGettersForVars(
vars: List[(String, Type)],
argGetters: Map[String, VarJson]
): ValidatedNec[String, List[ArgumentGetter]] = {
vars.map { (n, argType) =>
val argGetterOp = argGetters.get(n)
(argGetterOp, argType) match {
case (None, _) => Validated.invalidNec(s"Unexcepted. There is no service for '$n' argument")
// BoxType could be undefined, so, pass service that will return 'undefined' for this argument
case (Some(s), _: BoxType) if s._2 == js.undefined =>
Validated.validNec(createGetter(s._1, s._2, argType) :: Nil)
case (Some(s), _) if s._2 == js.undefined =>
Validated.invalidNec(
s"Argument '$n' is missing. Expected argument '$n' of type '$argType'"
)
case (Some(s), _) =>
Validated.validNec(createGetter(s._1, s._2, argType) :: Nil)
}
}.reduceOption(_ combine _).getOrElse(Validated.validNec(Nil))
}
// Wrap a functino like this:
// func wrapFunc():
// arg1 <- getDataSrv()
// arg2 <- getDataSrv()
// ...
// res <- funcCallable(args:_*)
// Console.print(res)
// Finisher.finish()
private def wrapCall(
consoleService: ResultPrinter,
finisherService: Finisher
): ValidatedNec[String, (FuncArrow, List[ArgumentGetter])] = {
val codomain = funcCallable.arrowType.codomain.toList
// pass results to a printing service if an input function returns a result
// otherwise just call it
val body = codomain match {
case Nil =>
CallArrowRawTag.func(func.name, Call(func.args, Nil)).leaf
case types =>
val (variables, exports) = types.zipWithIndex.map { case (t, idx) =>
val name = config.resultName + idx
(VarRaw(name, t), Call.Export(name, t))
}.unzip
val callFuncTag =
CallArrowRawTag.func(func.name, Call(func.args, exports))
val consoleServiceTag = consoleService.callTag(variables)
SeqTag.wrap(
callFuncTag.leaf,
consoleServiceTag.leaf
)
}
val finisherServiceTag = finisherService.callTag()
val vars = func.args
.zip(funcCallable.arrowType.domain.toList)
.collect { case (VarRaw(n, _), argType) =>
(n, argType)
}
.distinctBy(_._1)
val gettersV = getGettersForVars(vars, config.argumentGetters)
gettersV.map { getters =>
val gettersTags = getters.map(s => s.callTag().leaf)
// return something to wait a result if we have return value in function
// this is needed to catch an error if it will be occurred
val (returnCodomain, ret) = if (codomain.isEmpty) {
(NilType, Nil)
} else {
(UnlabeledConsType(ScalarType.string, NilType), LiteralRaw.quote("ok") :: Nil)
}
(
FuncArrow(
config.functionWrapperName,
SeqTag.wrap((gettersTags :+ body :+ finisherServiceTag.leaf): _*),
// no arguments and returns "ok" string
ArrowType(NilType, returnCodomain),
ret,
Map(func.name -> funcCallable),
Map.empty,
None
),
getters
)
}
}
}

View File

@ -7,7 +7,7 @@
"aqua.js",
"index.js",
"error.js",
"utils.js",
"meta-utils.js",
"dist/*",
"aqua/*"
],

View File

@ -1,12 +1,13 @@
package aqua
import aqua.builder.ArgumentGetter
import aqua.json.JsonEncoder
import aqua.js.VarJson
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.{CallArrowToken, CollectionToken, LiteralToken, VarToken}
import aqua.parser.lift.Span
import aqua.raw.value.{CollectionRaw, LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
import aqua.run.CliFunc
import cats.data.*
import cats.data.Validated.{invalid, invalidNec, invalidNel, valid, validNec, validNel}
import cats.effect.Concurrent
@ -16,7 +17,7 @@ import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.semigroup.*
import cats.syntax.traverse.*
import cats.{Id, Semigroup, ~>}
import cats.{~>, Id, Semigroup}
import com.monovore.decline.Opts
import fs2.io.file.{Files, Path}
@ -25,71 +26,15 @@ import scala.scalajs.js
import scala.scalajs.js.JSON
case class FuncWithData(func: CliFunc, getters: Map[String, VarJson])
case class CliFunc(name: String, args: List[ValueRaw] = Nil, ability: Option[String] = None)
// Variable and its JSON value
case class VarJson(variable: VarRaw, value: js.Dynamic)
object ArgOpts {
def spanToId: Span.S ~> Id = new (Span.S ~> Id) {
override def apply[A](span: Span.S[A]): Id[A] = {
span._2
}
}
// Parses a function name and arguments from a string
def funcOpt: Opts[CliFunc] =
Opts
.option[String]("func", "Function to call with args", "f", "funcName(args)")
.mapValidated { str =>
CallArrowToken.callArrow.parseAll(str.trim) match {
case Right(exprSpan) =>
val expr = exprSpan.mapK(spanToId)
val argsV = expr.args.collect {
case LiteralToken(value, ts) =>
validNel(LiteralRaw(value, ts))
case VarToken(name, _) =>
validNel(VarRaw(name.value, BottomType))
case CollectionToken(_, values) =>
val hasVariables = values.exists {
case LiteralToken(_, _) => false
case _ => true
}
if (!hasVariables) {
val literals = values.collect { case LiteralToken(value, ts) =>
LiteralRaw(value, ts)
}
val hasSameTypesOrEmpty =
literals.isEmpty || literals.map(_.baseType).toSet.size == 1
if (hasSameTypesOrEmpty) {
validNel(
NonEmptyList
.fromList(literals)
.map(l => CollectionRaw(l, ArrayType(l.head.baseType)))
.getOrElse(ValueRaw.Nil)
)
} else
invalidNel(
"If the argument is an array, then it must contain elements of the same type."
)
} else
invalidNel(
"Array arguments can only have numbers, strings, or booleans."
)
case CallArrowToken(_, _, _) =>
invalidNel("Function calls as arguments are not supported.")
}.sequence
argsV.andThen(args =>
validNel(CliFunc(expr.funcName.value, args, expr.ability.map(_.name)))
)
case Left(err) => invalid(err.expected.map(_.context.mkString("\n")))
}
CliFunc.fromString(str)
}
// Gets data from a file or from a json string
@ -109,60 +54,14 @@ object ArgOpts {
(dataFileOrStringOpt[F], funcOpt).mapN { case (dataF, func) =>
dataF.map { dataV =>
dataV.andThen { data =>
checkDataGetServices(func, data).map { case (funcWithTypedArgs, getters) =>
FuncWithData(funcWithTypedArgs, getters)
VarJson.checkDataGetServices(func.args, data).map { case (argsWithTypes, getters) =>
FuncWithData(func.copy(args = argsWithTypes), getters)
}
}
}
}
}
// checks if data is presented if there is non-literals in function arguments
// creates services to add this data into a call
def checkDataGetServices(
cliFunc: CliFunc,
data: Option[js.Dynamic]
): ValidatedNec[String, (CliFunc, Map[String, VarJson])] = {
val vars = cliFunc.args.collect { case v @ VarRaw(_, _) =>
v
// one variable could be used multiple times
}.distinctBy(_.name)
data match {
case None if vars.nonEmpty =>
// TODO: add a list with actual argument names that where present in the function call
invalidNec("Missing variables. You can provide them via --data or --data-path flags")
case None =>
validNec((cliFunc, Map.empty))
case Some(data) =>
vars.map { vm =>
val arg = {
val a = data.selectDynamic(vm.name)
if (js.isUndefined(a)) null
else a
}
val typeV = JsonEncoder.aquaTypeFromJson(vm.name, arg)
typeV.map(t => (vm.copy(baseType = t), arg))
}.sequence
.map(_.map { case (vm, arg) =>
vm.name -> VarJson(vm, arg)
}.toMap)
.andThen { services =>
val argsWithTypes = cliFunc.args.map {
case v @ VarRaw(n, _) =>
// argument getters have been enriched with types derived from JSON
// put this types to unriched arguments in CliFunc
services.get(n).map(g => v.copy(baseType = g._1.baseType)).getOrElse(v)
case v => v
}
validNec((cliFunc.copy(args = argsWithTypes), services))
}
}
}
def dataOpt: Opts[js.Dynamic] =
Opts
.option[String](

View File

@ -1,8 +1,11 @@
package aqua
import aqua.builder.{ArgumentGetter, Service}
import aqua.io.{AquaPath, PackagePath}
import aqua.js.VarJson
import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.run.{GeneralOptions, JsonService, RunCommand, RunOpts}
import aqua.run.{CliFunc, GeneralOptions, GeneralOpts, JsonService, RunCommand, RunOpts}
import aqua.logging.LogFormatter
import cats.data.Validated.{invalid, invalidNec, valid, validNec, validNel}
import cats.data.{NonEmptyList, Validated, ValidatedNec}
import cats.effect.ExitCode
@ -17,28 +20,8 @@ import fs2.io.file.{Files, Path}
import scribe.Logging
import scalajs.js
import scala.concurrent.ExecutionContext
sealed trait AquaPath {
def getPath[F[_]: Async](): F[Path]
}
// Path for package relative files
case class PackagePath(path: String) extends AquaPath {
def getPath[F[_]: Async](): F[Path] = PlatformOpts.getPackagePath(path)
}
// Path for absolute or call path relative files
case class RelativePath(path: Path) extends AquaPath {
def getPath[F[_]: Async](): F[Path] = path.pure[F]
}
object PackagePath {
// path to a builtin file in aqua package
val builtin: PackagePath = PackagePath("../aqua-lib/builtin.aqua")
}
// All info to run any aqua function
case class RunInfo(
common: GeneralOptions,
@ -109,7 +92,7 @@ object SubCommandBuilder {
.valid(
name,
header,
GeneralOptions.opt.map { c =>
GeneralOpts.opt.map { c =>
RunInfo(c, CliFunc(funcName), Some(path))
}
)

View File

@ -1,6 +1,6 @@
package aqua
import aqua.js.{LogLevel, FluenceJSLogLevel, Meta, Module}
import aqua.js.{LogLevel, FluenceJSLogLevel}
import fs2.io.file.Path
import scribe.Level

View File

@ -0,0 +1,35 @@
package aqua
import aqua.config.ConfigOpts
import aqua.ipfs.IpfsOpts
import aqua.keypair.KeyPairOpts
import aqua.remote.{DistOpts, RemoteOpts}
import aqua.run.RunOpts
import aqua.script.ScriptOpts
import cats.data.ValidatedNec
import cats.effect.ExitCode
import cats.effect.kernel.Async
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.monad.*
import com.monovore.decline.Opts
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.ExecutionContext
import scala.util.Try
import cats.effect.std.Console
// JS-specific options and subcommands
object PlatformOpts extends Logging {
def opts[F[_]: Files: AquaIO: Async: Console]: Opts[F[ValidatedNec[String, Unit]]] =
Opts.subcommand(RunOpts.runCommand[F]) orElse
Opts.subcommand(KeyPairOpts.command[F]) orElse
Opts.subcommand(IpfsOpts.ipfsOpt[F]) orElse
Opts.subcommand(ScriptOpts.scriptOpt[F]) orElse
Opts.subcommand(RemoteOpts.commands[F]) orElse
Opts.subcommand(ConfigOpts.command[F])
}

View File

@ -1,6 +1,6 @@
package aqua.air
import aqua.backend.AirString
import aqua.backend.AirFunction
import aqua.js.Fluence
import cats.data.Validated.{invalid, validNec}
import cats.data.{Chain, NonEmptyChain, ValidatedNec}
@ -22,7 +22,7 @@ object AirValidation {
}
def validate[F[_]: Async](
airs: List[AirString]
airs: List[AirFunction]
): F[ValidatedNec[String, Unit]] =
Async[F].fromFuture {

View File

@ -6,6 +6,7 @@ import aqua.model.{LiteralModel, VarModel}
import aqua.raw.ops
import aqua.raw.ops.{Call, CallArrowRawTag}
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.definitions.*
import cats.data.NonEmptyList
import scala.concurrent.Promise

View File

@ -4,6 +4,7 @@ import aqua.backend.*
import aqua.io.OutputPrinter
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.types.ScalarType
import aqua.definitions.*
import cats.data.NonEmptyList
import scribe.Logging

View File

@ -4,6 +4,7 @@ import aqua.backend.*
import aqua.ipfs.js.IpfsApi
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.types.ScalarType
import aqua.definitions.*
import cats.data.NonEmptyList
import scribe.Logging

View File

@ -4,6 +4,7 @@ import aqua.backend.*
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.model.{LiteralModel, VarModel}
import aqua.raw.ops.{Call, CallArrowRawTag}
import aqua.definitions.*
import aqua.raw.value.LiteralRaw
import cats.data.NonEmptyList

View File

@ -4,6 +4,7 @@ import aqua.backend.*
import aqua.ipfs.js.IpfsApi
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.types.ScalarType
import aqua.definitions.*
import cats.data.NonEmptyList
import scribe.Logging

View File

@ -5,6 +5,7 @@ import aqua.io.OutputPrinter
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.raw.ops.{Call, CallArrowRawTag}
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.definitions.*
import aqua.types.ScalarType
import cats.data.NonEmptyList

View File

@ -2,6 +2,7 @@ package aqua.builder
import aqua.backend.*
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.definitions.*
import cats.data.NonEmptyList
import scribe.Logging

View File

@ -3,25 +3,22 @@ package aqua.ipfs
import aqua.{
AppOpts,
AquaIO,
CliFunc,
CommandBuilder,
FluenceOpts,
LogFormatter,
LogLevelTransformer,
PackagePath,
PlatformOpts,
RunInfo,
SubCommandBuilder
}
import aqua.js.{Fluence, PeerConfig}
import aqua.keypair.KeyPairShow.show
import cats.data.{NonEmptyChain, NonEmptyList, Validated, ValidatedNec, ValidatedNel}
import Validated.{invalid, invalidNec, valid, validNec, validNel}
import aqua.builder.IPFSUploader
import aqua.io.PackagePath
import aqua.ipfs.js.IpfsApi
import aqua.model.LiteralModel
import aqua.raw.value.LiteralRaw
import aqua.run.{GeneralOptions, RunCommand, RunConfig, RunOpts}
import aqua.run.{GeneralOptions, RunCommand, RunConfig, RunOpts, GeneralOpts, CliFunc}
import cats.effect.{Concurrent, ExitCode, Resource, Sync}
import cats.syntax.flatMap.*
import cats.syntax.functor.*
@ -56,7 +53,7 @@ object IpfsOpts extends Logging {
SubCommandBuilder.valid(
"upload",
"Upload a file to IPFS",
(GeneralOptions.opt, pathOpt).mapN { (common, path) =>
(GeneralOpts.opt, pathOpt).mapN { (common, path) =>
RunInfo(
common,
CliFunc(UploadFuncName, LiteralRaw.quote(path) :: Nil),

View File

@ -1,7 +1,5 @@
package aqua.ipfs.js
import aqua.js.FluencePeer
import scala.scalajs.js
import scala.scalajs.js.annotation.{JSExportAll, JSImport}

View File

@ -3,10 +3,11 @@ package aqua.remote
import aqua.ArgOpts.jsonFromFileOpt
import aqua.builder.ArgumentGetter
import aqua.raw.value.{LiteralRaw, VarRaw}
import aqua.run.GeneralOptions
import aqua.run.{GeneralOptions, GeneralOpts, CliFunc}
import aqua.types.{ArrayType, ScalarType, StructType}
import aqua.*
import aqua.json.JsonEncoder
import aqua.io.PackagePath
import aqua.js.{JsonEncoder, VarJson}
import cats.data.{NonEmptyList, NonEmptyMap, ValidatedNec}
import cats.data.Validated.{invalidNec, validNec}
import cats.effect.{Async, Concurrent, ExitCode, Resource, Sync}
@ -60,7 +61,7 @@ object DistOpts extends Logging {
SubCommandBuilder.valid(
"remove_service",
"Remove service",
(GeneralOptions.opt, srvIdOpt).mapN { (common, srvId) =>
(GeneralOpts.opt, srvIdOpt).mapN { (common, srvId) =>
RunInfo(
common,
CliFunc(RemoveFuncName, LiteralRaw.quote(srvId) :: Nil),
@ -73,7 +74,7 @@ object DistOpts extends Logging {
SubCommandBuilder.valid(
"create_service",
"Deploy service from existing blueprint",
(GeneralOptions.opt, blueprintIdOpt).mapN { (common, blueprintId) =>
(GeneralOpts.opt, blueprintIdOpt).mapN { (common, blueprintId) =>
RunInfo(
common,
CliFunc(CreateServiceFuncName, LiteralRaw.quote(blueprintId) :: Nil),
@ -86,7 +87,7 @@ object DistOpts extends Logging {
SubCommandBuilder.valid(
"add_blueprint",
"Add blueprint to a peer",
(GeneralOptions.opt, blueprintNameOpt, dependencyOpt).mapN {
(GeneralOpts.opt, blueprintNameOpt, dependencyOpt).mapN {
(common, blueprintName, dependencies) =>
val depsWithHash = dependencies.map { d =>
if (d.startsWith("hash:"))
@ -129,7 +130,7 @@ object DistOpts extends Logging {
"deploy_service",
"Deploy service from WASM modules",
(
GeneralOptions.optWithSecretKeyCustomTimeout(60000),
GeneralOpts.optWithSecretKeyCustomTimeout(60000),
configFromFileOpt[F],
srvNameOpt
).mapN { (common, configFromFileF, srvName) =>

View File

@ -3,14 +3,14 @@ package aqua.remote
import aqua.builder.IPFSUploader
import DistOpts.*
import aqua.ipfs.IpfsOpts.{pathOpt, UploadFuncName}
import aqua.js.FluenceEnvironment
import aqua.model.{LiteralModel, ValueModel}
import aqua.raw.value.{LiteralRaw, ValueRaw}
import aqua.run.{GeneralOptions, RunCommand, RunConfig, RunOpts}
import aqua.run.{GeneralOptions, GeneralOpts, RunCommand, RunConfig, RunOpts, CliFunc}
import aqua.*
import cats.Applicative
import cats.data.{NonEmptyList, Validated}
import Validated.{invalidNel, validNel}
import aqua.io.PackagePath
import cats.effect.ExitCode
import cats.effect.kernel.Async
import cats.syntax.applicative.*
@ -68,7 +68,7 @@ object RemoteInfoOpts {
SubCommandBuilder.valid(
"list_interfaces",
"List all service interfaces on a peer by a given owner",
(GeneralOptions.opt, AppOpts.wrapWithOption(ownerOpt), allFlag).mapN {
(GeneralOpts.opt, AppOpts.wrapWithOption(ownerOpt), allFlag).mapN {
(common, peer, printAll) =>
if (printAll)
RunInfo(
@ -95,7 +95,7 @@ object RemoteInfoOpts {
SubCommandBuilder.valid(
GetInterfaceFuncName,
"Show interface of a service",
(GeneralOptions.opt, idOpt).mapN { (common, serviceId) =>
(GeneralOpts.opt, idOpt).mapN { (common, serviceId) =>
RunInfo(
common,
CliFunc(GetInterfaceFuncName, LiteralRaw.quote(serviceId) :: Nil),
@ -108,7 +108,7 @@ object RemoteInfoOpts {
SubCommandBuilder.valid(
GetModuleInterfaceFuncName,
"Print a module interface",
(GeneralOptions.opt, idOpt).mapN { (common, serviceId) =>
(GeneralOpts.opt, idOpt).mapN { (common, serviceId) =>
RunInfo(
common,
CliFunc(GetModuleInterfaceFuncName, LiteralRaw.quote(serviceId) :: Nil),

View File

@ -1,12 +1,13 @@
package aqua.run
import aqua.{AppOpts, LogLevels, VarJson}
import aqua.AppOpts
import aqua.FluenceOpts.*
import aqua.builder.{ArgumentGetter, Service}
import aqua.config.ConfigOpts.{Krasnodar, Stage, TestNet}
import aqua.js.FluenceEnvironment
import aqua.raw.ConstantRaw
import aqua.raw.value.VarRaw
import aqua.logging.LogLevels
import cats.data.{NonEmptyList, Validated}
import cats.data.Validated.{invalidNel, validNel}
import cats.syntax.applicative.*
@ -19,25 +20,7 @@ import scala.concurrent.duration.Duration
import scala.scalajs.js
import scala.util.Try
case class Flags(
printAir: Boolean,
showConfig: Boolean,
verbose: Boolean,
noXor: Boolean,
noRelay: Boolean
)
case class GeneralOptions(
timeout: Duration,
logLevel: LogLevels,
multiaddr: String,
on: Option[String],
flags: Flags,
secretKey: Option[Array[Byte]],
constants: List[ConstantRaw]
)
object GeneralOptions {
object GeneralOpts {
val multiaddrOpt: Opts[String] =
Opts
@ -103,22 +86,7 @@ object GeneralOptions {
val opt: Opts[GeneralOptions] = commonOpt(false, false, false)
val runOpt: Opts[GeneralOptions] = commonOpt(true, false, true)
val optWithSecretKey: Opts[GeneralOptions] = commonOpt(false, true, false)
def optWithSecretKeyCustomTimeout(timeoutMs: Int): Opts[GeneralOptions] = commonOpt(false, true, false, Duration(timeoutMs, TimeUnit.MILLISECONDS))
}
// `run` command configuration
case class RunConfig(
common: GeneralOptions,
// services that will pass arguments to air
argumentGetters: Map[String, VarJson],
// builtin services for aqua run, for example: Console, FileSystem, etc
services: List[Service],
jsonServices: List[JsonService],
plugins: List[String],
resultPrinterServiceId: String = "--after-callback-srv-service--",
resultPrinterName: String = "console-log",
finisherServiceId: String = "--finisher--",
finisherFnName: String = "--finish-execution--",
resultName: String = "-some-unique-res-name-",
functionWrapperName: String = "--someFuncToRun--"
)
def optWithSecretKeyCustomTimeout(timeoutMs: Int): Opts[GeneralOptions] =
commonOpt(false, true, false, Duration(timeoutMs, TimeUnit.MILLISECONDS))
}

View File

@ -1,8 +1,8 @@
package aqua.run
import aqua.LogLevelTransformer
import aqua.backend.FunctionDef
import aqua.builder.{ArgumentGetter, Finisher, ResultPrinter, Service}
import aqua.definitions.FunctionDef
import aqua.io.OutputPrinter
import aqua.js.*
import aqua.keypair.KeyPairShow.show
@ -20,7 +20,7 @@ import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future, Promise, TimeoutException}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.{timers, JSON, JavaScriptException}
import scala.scalajs.js.{JSON, JavaScriptException, timers}
object FuncCaller {
@ -34,9 +34,11 @@ object FuncCaller {
air: String,
functionDef: FunctionDef,
config: RunConfig,
resultPrinterService: ResultPrinter,
finisherService: Finisher,
services: List[Service],
getters: List[ArgumentGetter]
getters: List[ArgumentGetter],
plugins: List[String]
): F[ValidatedNec[String, Unit]] = {
FluenceUtils.setLogLevel(
@ -76,9 +78,9 @@ object FuncCaller {
}
// register all services
_ = (services ++ getters :+ finisherService).map(_.register(peer))
_ = (services ++ getters :+ finisherService :+ resultPrinterService).map(_.register(peer))
// register all plugins
plugins <- Plugin.getPlugins(config.plugins)
plugins <- Plugin.getPlugins(plugins)
_ = plugins.map(_.register(peer))
callFuture = CallJsFunction.funcCallJs(
air,

View File

@ -0,0 +1,96 @@
package aqua.run
import aqua.ArgOpts.jsonFromFileOpts
import aqua.builder.{AquaFunction, ArgumentGetter, Service}
import aqua.definitions.{ArrowTypeDef, ProductTypeDef, TypeDefinition}
import aqua.js.{Conversions, ServiceHandler, TypeDefinitionJs}
import aqua.model.{AquaContext, ServiceModel}
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.{CallArrowToken, CollectionToken, LiteralToken, VarToken}
import aqua.parser.lift.Span
import aqua.raw.value.{CollectionRaw, LiteralRaw, ValueRaw, VarRaw}
import aqua.types.*
import cats.data.*
import cats.data.Validated.{invalid, invalidNec, invalidNel, valid, validNec, validNel}
import cats.effect.Concurrent
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.semigroup.*
import cats.syntax.traverse.*
import cats.{Id, Semigroup, ~>}
import com.monovore.decline.Opts
import fs2.io.file.{Files, Path}
import scala.scalajs.js
// Description of a service with functions that return structures
case class JsonService(name: String, serviceId: String, functions: NonEmptyList[JsonFunction])
case class JsonFunction(name: String, result: js.Dynamic, resultType: Type)
object JsonService {
def findServices(
contexts: Chain[AquaContext],
services: List[JsonService]
): ValidatedNec[String, List[Service]] = {
services
.map(js =>
contexts
.collectFirstSome(_.services.get(js.name))
.map(sm => (js, sm))
.map(validNec)
.getOrElse(
Validated.invalidNec[String, ServiceModel](
s"There is no service '${js.name}' (described in json-service file) in aqua source or it is not exported. Check the spelling or see https://fluence.dev/docs/aqua-book/language/header/#export"
)
)
)
.sequence
.andThen { l =>
l.map { case (jsonService: JsonService, sm: ServiceModel) =>
val aquaFunctions: ValidatedNec[String, NonEmptyList[AquaFunction]] =
jsonService.functions.map { jf =>
sm.arrows(jf.name)
.map { case arr: ArrowType =>
if (arr.domain.isEmpty)
TypeValidator
.validateTypes(jf.name, arr.codomain, Some(ProductType(jf.resultType :: Nil)))
.map { _ =>
new AquaFunction {
override def fnName: String = jf.name
override def handler: ServiceHandler = _ => {
val converted = arr.codomain.toList match {
case h :: _ =>
Conversions.ts2aqua(jf.result, TypeDefinitionJs(TypeDefinition(h)))
case Nil =>
Conversions.ts2aqua(
jf.result,
TypeDefinitionJs(TypeDefinition(NilType))
)
}
js.Promise.resolve(converted)
}
override def arrow: ArrowTypeDef =
ArrowTypeDef(ProductTypeDef(NilType), ProductTypeDef(arr.codomain))
}
}
else
invalidNec(s"Json service '${jf.name}' cannot have any arguments")
}
.getOrElse(
Validated.invalidNec[String, AquaFunction](
s"There is no function '${jf.name}' in service '${jsonService.name}' in aqua source. Check your 'json-service' options"
)
)
}.sequence
aquaFunctions.map(funcs => Service(jsonService.serviceId, funcs))
}.sequence
}
}
}

View File

@ -2,7 +2,7 @@ package aqua.run
import aqua.ArgOpts.jsonFromFileOpts
import aqua.builder.ArgumentGetter
import aqua.json.JsonEncoder
import aqua.js.JsonEncoder
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.{CallArrowToken, CollectionToken, LiteralToken, VarToken}
import aqua.parser.lift.Span
@ -23,13 +23,10 @@ import fs2.io.file.{Files, Path}
import scala.scalajs.js
// Description of a service with functions that return structures
case class JsonService(name: String, serviceId: String, functions: NonEmptyList[JsonFunction])
case class JsonFunction(name: String, result: js.Dynamic, resultType: Type)
object JsonServiceOpts {
object JsonService {
def jsonServiceOpt[F[_]: Files: Concurrent]: Opts[F[ValidatedNec[String, NonEmptyList[JsonService]]]] = {
def jsonServiceOpt[F[_]: Files: Concurrent]
: Opts[F[ValidatedNec[String, NonEmptyList[JsonService]]]] = {
jsonFromFileOpts("json-service", "Path to file that describes service with JSON result", "j")
.map(b =>
b.map { case a: ValidatedNec[String, NonEmptyList[(Path, js.Dynamic)]] =>
@ -54,7 +51,9 @@ object JsonService {
val fName = f.name
val fResult = f.result
if (js.isUndefined(fName) || js.typeOf(fName) != "string")
invalidNec(s"One of the functions doesn't have a name or it is not a string in JSON service '$path'")
invalidNec(
s"One of the functions doesn't have a name or it is not a string in JSON service '$path'"
)
else if (js.isUndefined(fResult))
invalidNec(s"Function '$fName' don't have a result in '$path'")
else {
@ -74,7 +73,9 @@ object JsonService {
JsonService(name.asInstanceOf[String], serviceId.asInstanceOf[String], fNEL)
)
)
.getOrElse(invalidNec(s"List of functions in '$name' service is empty in $path"))
.getOrElse(
invalidNec(s"List of functions in '$name' service is empty in $path")
)
}
}
}.sequence

View File

@ -0,0 +1,229 @@
package aqua.run
import aqua.*
import aqua.ErrorRendering.showError
import aqua.backend.air.{AirBackend, FuncAirGen}
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.backend.Generated
import aqua.logging.LogFormatter
import aqua.definitions.{FunctionDef, TypeDefinition}
import aqua.builder.{ArgumentGetter, Finisher, ResultPrinter, Service}
import aqua.compiler.{AquaCompiled, AquaCompiler}
import aqua.files.{AquaFileSources, AquaFilesIO, FileModuleId}
import aqua.io.{AquaFileError, AquaPath, OutputPrinter, Prelude}
import aqua.js.*
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{AquaContext, FuncArrow}
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.LiteralToken
import aqua.parser.lift.FileSpan
import aqua.raw.value.{ValueRaw, VarRaw}
import aqua.run.RunConfig
import aqua.run.RunOpts.transformConfig
import aqua.types.*
import cats.data.*
import cats.effect.*
import cats.effect.kernel.{Async, Clock}
import cats.effect.syntax.async.*
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.list.*
import cats.syntax.monad.*
import cats.syntax.show.*
import cats.syntax.traverse.*
import cats.{Id, Monad, ~>}
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.JSON
import scala.scalajs.js.annotation.*
object RunCommand extends Logging {
def createKeyPair(
sk: Option[Array[Byte]]
): Future[KeyPair] = {
sk.map { arr =>
val typedArr = js.typedarray.Uint8Array.from(arr.map(_.toShort).toJSArray)
KeyPair.fromEd25519SK(typedArr).toFuture
}.getOrElse(KeyPair.randomEd25519().toFuture)
}
private def createGetter(value: VarRaw, arg: js.Dynamic, argType: Type): ArgumentGetter = {
val converted = Conversions.ts2aqua(arg, TypeDefinitionJs(TypeDefinition(argType)))
ArgumentGetter(value.copy(baseType = argType), converted)
}
// Creates getter services for variables. Return an error if there is no variable in services
// and type of this variable couldn't be optional
private def getGettersForVars(
vars: List[(String, Type)],
argGetters: Map[String, VarJson]
): ValidatedNec[String, List[ArgumentGetter]] = {
vars.map { (n, argType) =>
val argGetterOp = argGetters.get(n)
(argGetterOp, argType) match {
case (None, _) => Validated.invalidNec(s"Unexcepted. There is no service for '$n' argument")
// BoxType could be undefined, so, pass service that will return 'undefined' for this argument
case (Some(s), _: BoxType) if s._2 == js.undefined =>
Validated.validNec(createGetter(s._1, s._2, argType) :: Nil)
case (Some(s), _) if s._2 == js.undefined =>
Validated.invalidNec(
s"Argument '$n' is missing. Expected argument '$n' of type '$argType'"
)
case (Some(s), _) =>
Validated.validNec(createGetter(s._1, s._2, argType) :: Nil)
}
}.reduceOption(_ combine _).getOrElse(Validated.validNec(Nil))
}
def resultVariableNames(funcCallable: FuncArrow, name: String): List[String] =
funcCallable.arrowType.codomain.toList.zipWithIndex.map { case (t, idx) =>
name + idx
}
/**
* Runs a function that is located in `input` file with FluenceJS SDK. Returns no output
* @param func
* function name
* @param input
* path to an aqua code with a function
* @param imports
* the sources the input needs
*/
def run[F[_]: Files: AquaIO: Async](
func: CliFunc,
input: Option[AquaPath],
imports: List[Path],
runConfig: RunConfig,
// services that will pass arguments to air
argumentGetters: Map[String, VarJson],
// builtin services for aqua run, for example: Console, FileSystem, etc
services: List[Service],
jsonServices: List[JsonService],
plugins: List[String],
transformConfig: TransformConfig
): F[ValidatedNec[String, Unit]] = {
val funcCompiler = new FuncCompiler[F](input, imports, transformConfig)
for {
prelude <- Prelude.init[F](true)
contextV <- funcCompiler.compile(prelude.importPaths, true)
callResult <- Clock[F].timed {
contextV.andThen { context =>
FuncCompiler
.findFunction(context, func)
.andThen(callable =>
JsonService
.findServices(context, jsonServices)
.map(jsonServices => (callable, jsonServices))
)
}.andThen { case (funcCallable, jsonServices) =>
val resultNames = resultVariableNames(funcCallable, runConfig.resultName)
val resultPrinterService =
ResultPrinter(
runConfig.resultPrinterServiceId,
runConfig.resultPrinterName,
resultNames
)
val promiseFinisherService =
Finisher(runConfig.finisherServiceId, runConfig.finisherFnName)
val vars = func.args
.zip(funcCallable.arrowType.domain.toList)
.collect { case (VarRaw(n, _), argType) =>
(n, argType)
}
.distinctBy(_._1)
getGettersForVars(vars, argumentGetters).andThen { getters =>
val gettersTags = getters.map(s => s.callTag())
val preparer =
new CallPreparer(
func,
funcCallable,
gettersTags,
resultPrinterService.callTag,
promiseFinisherService.callTag(),
runConfig,
transformConfig
)
preparer.prepare().map { info =>
FuncCaller.funcCall[F](
info.name,
info.air,
info.definitions,
info.config,
resultPrinterService,
promiseFinisherService,
services ++ jsonServices,
getters,
plugins
)
}
}
} match {
case Validated.Valid(f) =>
f
case i @ Validated.Invalid(_) => i.pure[F]
}
}
(callTime, result) = callResult
} yield {
logger.debug(s"Call time: ${callTime.toMillis}ms")
result
}
}
private val builtinServices =
aqua.builder
.Console() :: aqua.builder.IPFSUploader("ipfs") :: aqua.builder.DeployHelper() :: Nil
/**
* Executes a function with the specified settings
* @param common
* common settings
* @param funcName
* function name
* @param inputPath
* path to a file with a function
* @param imports
* imports that must be specified for correct compilation
* @param args
* arguments to pass into a function
* @param argumentGetters
* services to get argument if it is a variable
* @param services
* will be registered before calling for correct execution
* @return
*/
def execRun[F[_]: Async](
runInfo: RunInfo
): F[ValidatedNec[String, Unit]] = {
val common = runInfo.common
LogFormatter.initLogger(Some(common.logLevel.compiler))
implicit val aio: AquaIO[F] = new AquaFilesIO[F]
RunCommand
.run[F](
runInfo.func,
runInfo.input,
runInfo.imports,
RunConfig(
common
),
runInfo.argumentGetters,
runInfo.services ++ builtinServices,
runInfo.jsonServices,
runInfo.pluginsPaths,
transformConfig(common.on, common.constants, common.flags.noXor, common.flags.noRelay)
)
}
}

View File

@ -1,14 +1,15 @@
package aqua.run
import aqua.*
import aqua.ArgOpts.checkDataGetServices
import aqua.builder.{ArgumentGetter, Service}
import aqua.io.{AquaPath, RelativePath}
import aqua.model.transform.TransformConfig
import aqua.model.{LiteralModel, ValueModel, VarModel}
import aqua.parser.expr.func.CallArrowExpr
import aqua.parser.lexer.{LiteralToken, VarToken}
import aqua.parser.lift.LiftParser.Implicits.idLiftParser
import aqua.parser.lift.Span
import aqua.logging.LogFormatter
import aqua.raw.ConstantRaw
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.run.plugin.Plugin
@ -56,7 +57,7 @@ object RunOpts extends Logging {
AppOpts.wrapWithOption(AppOpts.inputOpts[F]),
AppOpts.importOpts[F],
ArgOpts.funcWithArgsOpt[F],
AppOpts.wrapWithOption(JsonService.jsonServiceOpt),
AppOpts.wrapWithOption(JsonServiceOpts.jsonServiceOpt),
AppOpts.wrapWithOption(Plugin.opt)
).mapN { case (inputF, importF, funcWithArgsF, jsonServiceOp, pluginsOp) =>
for {
@ -83,7 +84,7 @@ object RunOpts extends Logging {
name = "run",
header = "Run Aqua code",
(
GeneralOptions.runOpt,
GeneralOpts.runOpt,
runOptsCompose[F]
).mapN {
case (

View File

@ -1,16 +1,10 @@
package aqua.run.plugin
import aqua.backend.{
ArrowTypeDef,
LabeledProductTypeDef,
ServiceDef,
TopTypeDef,
UnlabeledProductTypeDef
}
import aqua.js.{CallJsFunction, FluencePeer, ServiceHandler}
import aqua.run.JsonService
import aqua.run.plugin.Plugin.toPromise
import aqua.types.TopType
import aqua.definitions.*
import cats.data.{NonEmptyList, ValidatedNec}
import cats.effect.Concurrent
import cats.syntax.applicative.*

View File

@ -6,8 +6,9 @@ import aqua.backend.Generated
import aqua.backend.air.{AirBackend, AirGen, FuncAirGen}
import aqua.builder.ArgumentGetter
import aqua.compiler.AquaCompiler
import aqua.js.VarJson
import aqua.io.{PackagePath, Prelude, RelativePath}
import aqua.ipfs.js.IpfsApi
import aqua.js.{Fluence, PeerConfig}
import aqua.keypair.KeyPairShow.show
import aqua.model.transform.{Transform, TransformConfig}
import aqua.model.{AquaContext, FuncArrow, LiteralModel}
@ -16,7 +17,15 @@ import aqua.raw.ops.{Call, CallArrowRawTag}
import aqua.raw.value.{LiteralRaw, ValueRaw, VarRaw}
import aqua.res.{AquaRes, FuncRes}
import aqua.run.RunOpts.logger
import aqua.run.{GeneralOptions, RunCommand, RunConfig, RunOpts}
import aqua.run.{
CliFunc,
FuncCompiler,
GeneralOptions,
GeneralOpts,
RunCommand,
RunConfig,
RunOpts
}
import aqua.types.{ArrowType, LiteralType, NilType, ScalarType}
import cats.data.*
import cats.data.Validated.{invalid, invalidNec, valid, validNec, validNel}
@ -110,7 +119,7 @@ object ScriptOpts extends Logging {
AirGen(funcRes.body).generate.show
}
private def commonScriptOpts = GeneralOptions.commonOpt(false, true, true)
private def commonScriptOpts = GeneralOpts.commonOpt(false, true, true)
private def compileAir[F[_]: Async: AquaIO](
input: Path,
@ -122,30 +131,31 @@ object ScriptOpts extends Logging {
new FuncCompiler[F](
Option(RelativePath(input)),
imports,
tConfig,
withRunImport = true
tConfig
)
val funcName = funcWithArgs.func.name
for {
callableV <- funcCompiler.compile(funcWithArgs.func, Nil)
prelude <- Prelude.init[F](true)
contextV <- funcCompiler.compile(prelude.importPaths)
wrappedBody = CallArrowRawTag.func(funcName, Call(funcWithArgs.func.args, Nil)).leaf
result = callableV
.map(callable =>
result = contextV
.andThen(context => FuncCompiler.findFunction(context, funcWithArgs.func))
.map { callable =>
generateAir(
FuncArrow(
funcName + "_scheduled",
wrappedBody,
ArrowType(NilType, NilType),
Nil,
Map(funcName -> callable._1),
Map(funcName -> callable),
Map.empty,
None
),
tConfig
)
)
}
} yield result
}

View File

@ -1,6 +1,6 @@
package aqua
import aqua.json.JsonEncoder
import aqua.js.JsonEncoder
import aqua.types.{ArrayType, LiteralType, OptionType, StructType}
import cats.Id
import org.scalatest.flatspec.AnyFlatSpec

View File

@ -1,6 +1,5 @@
package aqua
import aqua.json.TypeValidator
import aqua.run.TypeValidator
import aqua.types.{ArrayType, LiteralType, OptionType, ScalarType, StructType, Type}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

View File

@ -11,8 +11,5 @@ import scala.concurrent.ExecutionContext
// Scala-specific options and subcommands
object PlatformOpts {
def opts[F[_]: Files: AquaIO: Async: Console]: Opts[F[ValidatedNec[String, Unit]]] = Opts.never
def getGlobalNodeModulePath: List[Path] = Nil
def getPackagePath: Option[Path] = None
}

View File

@ -2,6 +2,7 @@ package aqua
import aqua.backend.ts.TypeScriptBackend
import aqua.files.AquaFilesIO
import aqua.logging.LogFormatter
import aqua.model.transform.TransformConfig
import cats.data.Validated
import cats.effect.{IO, IOApp, Sync}

View File

@ -1,5 +1,5 @@
package aqua.air
import aqua.backend.AirString
import aqua.backend.AirFunction
import cats.data.ValidatedNec
import cats.effect.Async
import cats.data.Validated.validNec
@ -10,6 +10,6 @@ object AirValidation {
def init[F[_]: Async](): F[Unit] = Async[F].pure(())
def validate[F[_]: Async](airs: List[AirString]): F[ValidatedNec[String, Unit]] = Async[F].pure(validNec(()))
def validate[F[_]: Async](airs: List[AirFunction]): F[ValidatedNec[String, Unit]] = Async[F].pure(validNec(()))
}

View File

@ -14,7 +14,7 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
implicit val aquaIO: AquaIO[IO] = AquaFilesIO.summon[IO]
"AquaFileSources" should "generate correct fileId with imports" in {
val path = Path("cli/.jvm/src/test/test-dir/path-test")
val path = Path("cli/cli/.jvm/src/test/test-dir/path-test")
val importPath = path.resolve("imports")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
@ -48,7 +48,7 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
}
"AquaFileSources" should "throw an error if there is no import that is indicated in a source" in {
val path = Path("cli/.jvm/src/test/test-dir")
val path = Path("cli/cli/.jvm/src/test/test-dir")
val importPath = path.resolve("random/import/path")
val sourceGen = new AquaFileSources[IO](path, importPath :: Nil)
@ -59,7 +59,7 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
}
"AquaFileSources" should "find correct imports" in {
val srcPath = Path("cli/.jvm/src/test/test-dir/index.aqua")
val srcPath = Path("cli/cli/.jvm/src/test/test-dir/index.aqua")
val importPath = srcPath.resolve("imports")
val sourceGen = new AquaFileSources[IO](srcPath, importPath :: Nil)
@ -94,7 +94,7 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
}
"AquaFileSources" should "resolve correct path for target" in {
val path = Path("cli/.jvm/src/test/test-dir")
val path = Path("cli/cli/.jvm/src/test/test-dir")
val filePath = path.resolve("some-dir/file.aqua")
val targetPath = Path("/target/dir/")
@ -115,7 +115,7 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
}
"AquaFileSources" should "resolve correct path for target when file is in current directory" in {
val path = Path("cli/.jvm/src/test/test-dir")
val path = Path("cli/cli/.jvm/src/test/test-dir")
val filePath = path.resolve("file.aqua")
val targetPath = Path("/target/dir/")
@ -136,13 +136,13 @@ class SourcesSpec extends AsyncFlatSpec with Matchers {
}
"AquaFileSources" should "write correct file with correct path" in {
val path = Path("cli/.jvm/src/test/test-dir")
val path = Path("cli/cli/.jvm/src/test/test-dir")
val filePath = path.resolve("imports/import.aqua")
val targetPath = path.resolve("target/")
// clean up
val resultPath = Path("cli/.jvm/src/test/test-dir/target/imports/import_hey.custom")
val resultPath = Path("cli/cli/.jvm/src/test/test-dir/target/imports/import_hey.custom")
(for {
_ <- Files[IO].deleteIfExists(resultPath)
sourceGen = new AquaFileSources[IO](path, Nil)

View File

@ -12,7 +12,7 @@ import fs2.io.file.{Files, Path}
class WriteFileSpec extends AnyFlatSpec with Matchers {
"cli" should "compile aqua code in js" in {
val src = Path("./cli/.jvm/src/test/aqua")
val src = Path("./cli/cli/.jvm/src/test/aqua")
val targetTs = Files[IO].createTempDirectory.unsafeRunSync()
val targetJs = Files[IO].createTempDirectory.unsafeRunSync()
val targetAir = Files[IO].createTempDirectory.unsafeRunSync()

View File

@ -7,6 +7,7 @@ import aqua.parser.expr.ConstantExpr
import aqua.parser.lift.LiftParser
import aqua.raw.ConstantRaw
import aqua.raw.value.LiteralRaw
import aqua.constants.Constants
import cats.data.Validated.{Invalid, Valid}
import cats.data.{NonEmptyList, Validated, ValidatedNec, ValidatedNel}
import cats.effect.kernel.Async
@ -103,22 +104,7 @@ object AppOpts {
"NAME=value"
)
.mapValidated { strs =>
val parsed = strs.map(s => ConstantExpr.onlyLiteral.parseAll(s))
val errors = parsed.zip(strs).collect { case (Left(er), str) =>
str
}
NonEmptyList
.fromList(errors)
.fold(
Validated.validNel[String, List[ConstantRaw]](parsed.collect { case Right(v) =>
ConstantRaw(v._1.value, LiteralRaw(v._2.value, v._2.ts), false)
})
) { errors =>
val errorMsgs = errors.map(str => s"Invalid constant definition '$str'.")
Validated.invalid(errorMsgs)
}
Constants.parse(strs.toList)
}
.withDefault(List.empty)

View File

@ -4,6 +4,7 @@ import aqua.backend.Backend
import aqua.backend.air.AirBackend
import aqua.backend.js.JavaScriptBackend
import aqua.backend.ts.TypeScriptBackend
import aqua.logging.LogFormatter
import aqua.files.AquaFilesIO
import aqua.io.OutputPrinter
import aqua.model.transform.TransformConfig
@ -16,7 +17,7 @@ import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{~>, Functor, Id, Monad}
import cats.{Functor, Id, Monad, ~>}
import com.monovore.decline
import com.monovore.decline.effect.CommandIOApp
import com.monovore.decline.effect.CommandIOApp.printHelp
@ -132,8 +133,8 @@ object AquaCli extends IOApp with Logging {
val bc = TransformConfig(wrapWithXor = !noXor, constants = constants)
bc.copy(relayVarName = bc.relayVarName.filterNot(_ => noRelay))
}
logger.info(s"Aqua Compiler $versionStr")
LogFormatter.initLogger(Some(logLevel.compiler))
logger.info(s"Aqua Compiler $versionStr")
(inputF, outputF, importsF).mapN { (i, o, imp) =>
i.andThen { input =>

View File

@ -1,18 +1,12 @@
package aqua
import aqua.backend.{Backend, Generated}
import aqua.compiler.{
AirValidator,
AquaCompiled,
AquaCompiler,
AquaCompilerConf,
AquaError,
CompilerAPI
}
import aqua.compiler.{AirValidator, AquaCompiled, AquaCompiler, AquaCompilerConf, AquaError, CompilerAPI}
import aqua.files.{AquaFileSources, FileModuleId}
import aqua.io.Prelude
import aqua.io.*
import aqua.air.AirValidation
import aqua.backend.AirString
import aqua.backend.AirFunction
import aqua.model.AquaContext
import aqua.model.transform.TransformConfig
import aqua.model.transform.Transform
@ -29,7 +23,7 @@ import cats.syntax.applicative.*
import cats.syntax.functor.*
import cats.syntax.flatMap.*
import cats.syntax.show.*
import cats.{~>, Applicative, Eval, Monad, Show}
import cats.{Applicative, Eval, Monad, Show, ~>}
import fs2.io.file.{Files, Path}
import scribe.Logging
import cats.data.Validated.validNec
@ -60,14 +54,14 @@ object AquaPathCompiler extends Logging {
if (disableAirValidation) {
new AirValidator[F] {
override def init(): F[Unit] = Applicative[F].pure(())
override def validate(airs: List[AirString]): F[ValidatedNec[String, Unit]] =
override def validate(airs: List[AirFunction]): F[ValidatedNec[String, Unit]] =
Applicative[F].pure(validNec(()))
}
} else {
new AirValidator[F] {
override def init(): F[Unit] = AirValidation.init[F]()
override def validate(
airs: List[AirString]
airs: List[AirFunction]
): F[ValidatedNec[String, Unit]] = AirValidation.validate[F](airs)
}
}

View File

@ -0,0 +1,65 @@
package aqua
import cats.data.{NonEmptyList, Validated, ValidatedNel}
import aqua.logging.LogLevels
import com.monovore.decline.Opts
import scribe.Level
import cats.syntax.traverse.*
import cats.data.Validated.{invalid, invalidNec, invalidNel, valid, validNec, validNel}
import java.util.Base64
import java.util.concurrent.TimeUnit
import scala.concurrent.duration.Duration
object FluenceOpts {
val timeoutOpt: Opts[Duration] =
Opts
.option[Int]("timeout", "Request timeout in milliseconds", "t")
.map(i => Duration(i, TimeUnit.MILLISECONDS))
val onOpt: Opts[Option[String]] =
AppOpts.wrapWithOption(
Opts
.option[String](
"on",
"peerId of the peer that will execute the function. Default: host_peer_id",
"o",
"peerId"
)
)
val showConfigOpt: Opts[Boolean] =
Opts
.flag("show-config", "Print current configuration on start")
.map(_ => true)
.withDefault(false)
val verboseOpt: Opts[Boolean] =
Opts
.flag("verbose", "Show additional information about the call")
.map(_ => true)
.withDefault(false)
val secretKeyOpt: Opts[Array[Byte]] =
Opts
.option[String]("sk", "Ed25519 32-byte secret key in base64", "s", "base64")
.mapValidated { s =>
val decoder = Base64.getDecoder
Validated.catchNonFatal {
decoder.decode(s)
}.leftMap(t => NonEmptyList.one("secret key isn't a valid base64 string: " + t.getMessage))
}
val printAir: Opts[Boolean] =
Opts
.flag("print-air", "Prints generated AIR code before function execution")
.map(_ => true)
.withDefault(false)
val logLevelOpt: Opts[LogLevels] =
Opts.option[String]("log-level", help = s"Set log level. ${LogLevels.logHelpMessage}").mapValidated {
str =>
LogLevels.fromString(str)
}.withDefault(LogLevels())
}

View File

@ -1,6 +1,6 @@
package aqua.compiler
import aqua.backend.AirString
import aqua.backend.AirFunction
import cats.data.ValidatedNec
@ -8,6 +8,6 @@ trait AirValidator[F[_]] {
def init(): F[Unit]
def validate(
airs: List[AirString]
airs: List[AirFunction]
): F[ValidatedNec[String, Unit]]
}

View File

@ -1,6 +1,6 @@
package aqua.compiler
import aqua.backend.{AirString, Backend}
import aqua.backend.{AirFunction, Backend}
import aqua.linker.{AquaModule, Linker, Modules}
import aqua.model.AquaContext
import aqua.parser.lift.{LiftParser, Span}

View File

@ -1,38 +1,18 @@
package aqua
import aqua.config.ConfigOpts
import aqua.ipfs.IpfsOpts
import aqua.js.{Meta, Module}
import aqua.keypair.KeyPairOpts
import aqua.remote.{DistOpts, RemoteOpts}
import aqua.run.RunOpts
import aqua.script.ScriptOpts
import cats.data.ValidatedNec
import cats.effect.ExitCode
import cats.effect.kernel.Async
import fs2.io.file.{Files, Path}
import aqua.js.{Meta, Module}
import scribe.Logging
import cats.syntax.applicative.*
import cats.syntax.apply.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.monad.*
import com.monovore.decline.Opts
import fs2.io.file.{Files, Path}
import scribe.Logging
import scala.concurrent.ExecutionContext
import scala.util.Try
import cats.effect.std.Console
// JS-specific options and subcommands
object PlatformOpts extends Logging {
def opts[F[_]: Files: AquaIO: Async: Console]: Opts[F[ValidatedNec[String, Unit]]] =
Opts.subcommand(RunOpts.runCommand[F]) orElse
Opts.subcommand(KeyPairOpts.command[F]) orElse
Opts.subcommand(IpfsOpts.ipfsOpt[F]) orElse
Opts.subcommand(ScriptOpts.scriptOpt[F]) orElse
Opts.subcommand(RemoteOpts.commands[F]) orElse
Opts.subcommand(ConfigOpts.command[F])
object PlatformPackagePath extends Logging {
// it could be global installed aqua and local installed, different paths for this
def getPackagePath[F[_]: Async](path: String): F[Path] = {

Some files were not shown because too many files have changed in this diff Show More