feat(language-server): Support declares and exports in LSP [LNG-304, LNG-319] (#1070)

Co-authored-by: InversionSpaces <inversionspaces@vivaldi.net>
This commit is contained in:
Dima 2024-02-07 12:21:42 +03:00 committed by GitHub
parent d03211b492
commit f7194f0a54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 357 additions and 168 deletions

View File

@ -1,19 +1,15 @@
package aqua.compiler package aqua.compiler
import aqua.compiler.AquaError.{ParserError as AquaParserError, *} import aqua.compiler.AquaError.*
import aqua.linker.Linker.link import aqua.linker.Linker
import aqua.linker.{AquaModule, Linker, Modules}
import aqua.parser.{Ast, ParserError} import aqua.parser.{Ast, ParserError}
import aqua.semantics.header.{HeaderHandler, Picker} import aqua.semantics.header.{HeaderHandler, Picker}
import aqua.semantics.{SemanticError, Semantics} import aqua.semantics.{SemanticError, Semantics}
import cats.arrow.FunctionK import cats.arrow.FunctionK
import cats.data.* import cats.data.*
import cats.syntax.applicative.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.{Comonad, Monad, Monoid, Order, ~>} import cats.{Comonad, Monad, Monoid, Order, ~>}
import scribe.Logging import scribe.Logging

View File

@ -7,9 +7,9 @@ import aqua.parser.{Ast, ParserError}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.semantics.RawSemantics import aqua.semantics.RawSemantics
import aqua.semantics.header.{HeaderHandler, HeaderSem} import aqua.semantics.header.{HeaderHandler, HeaderSem}
import aqua.semantics.rules.locations.{LocationsAlgebra, DummyLocationsInterpreter}
import cats.data.* import cats.data.*
import cats.syntax.applicative.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
@ -55,6 +55,9 @@ object CompilerAPI extends Logging {
.rawContextMonoid .rawContextMonoid
val semantics = new RawSemantics[S]() val semantics = new RawSemantics[S]()
given LocationsAlgebra[S, State[RawContext, *]] =
DummyLocationsInterpreter()
new AquaCompiler[F, E, I, S, RawContext]( new AquaCompiler[F, E, I, S, RawContext](
new HeaderHandler(), new HeaderHandler(),

View File

@ -4,9 +4,10 @@ import aqua.compiler.{AquaCompiler, AquaCompilerConf, AquaError, AquaSources}
import aqua.parser.{Ast, ParserError} import aqua.parser.{Ast, ParserError}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.semantics.header.{HeaderHandler, HeaderSem} import aqua.semantics.header.{HeaderHandler, HeaderSem}
import aqua.semantics.rules.locations.LocationsAlgebra
import cats.data.Validated.validNec import cats.data.Validated.validNec
import cats.data.{Chain, Validated, ValidatedNec} import cats.data.{State, Chain, Validated, ValidatedNec}
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.monoid.* import cats.syntax.monoid.*
@ -47,6 +48,9 @@ object LSPCompiler {
val semantics = new LspSemantics[S]() val semantics = new LspSemantics[S]()
given LocationsAlgebra[S, State[LspContext[S], *]] =
LocationsInterpreter[S, LspContext[S]]()
new AquaCompiler[F, E, I, S, LspContext[S]]( new AquaCompiler[F, E, I, S, LspContext[S]](
new HeaderHandler(), new HeaderHandler(),
semantics semantics

View File

@ -6,8 +6,11 @@ import aqua.semantics.header.Picker
import aqua.semantics.rules.locations.{TokenLocation, VariableInfo} import aqua.semantics.rules.locations.{TokenLocation, VariableInfo}
import aqua.semantics.{SemanticError, SemanticWarning} import aqua.semantics.{SemanticError, SemanticWarning}
import aqua.types.{AbilityType, ArrowType, Type} import aqua.types.{AbilityType, ArrowType, Type}
import aqua.semantics.rules.locations.LocationsState
import cats.syntax.monoid.* import cats.syntax.monoid.*
import cats.{Monoid, Semigroup} import cats.{Monoid, Semigroup}
import monocle.Lens
// Context with info that necessary for language server // Context with info that necessary for language server
case class LspContext[S[_]]( case class LspContext[S[_]](
@ -15,6 +18,7 @@ case class LspContext[S[_]](
abDefinitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]], abDefinitions: Map[String, NamedTypeToken[S]] = Map.empty[String, NamedTypeToken[S]],
rootArrows: Map[String, ArrowType] = Map.empty[String, ArrowType], rootArrows: Map[String, ArrowType] = Map.empty[String, ArrowType],
constants: Map[String, Type] = Map.empty[String, Type], constants: Map[String, Type] = Map.empty[String, Type],
// TODO: Can this field be refactored into LocationsState?
variables: List[VariableInfo[S]] = Nil, variables: List[VariableInfo[S]] = Nil,
importTokens: List[LiteralToken[S]] = Nil, importTokens: List[LiteralToken[S]] = Nil,
errors: List[SemanticError[S]] = Nil, errors: List[SemanticError[S]] = Nil,
@ -150,4 +154,17 @@ object LspContext {
)(using Semigroup[LspContext[S]]): LspContext[S] = )(using Semigroup[LspContext[S]]): LspContext[S] =
ctx.copy(raw = ctx.raw.pickDeclared) ctx.copy(raw = ctx.raw.pickDeclared)
} }
/*
NOTE: This instance is used to generate LocationsAlgebra[S, State[LspContext[S], *]]
to reuse the code from the body semantics in the header semantics
*/
given [S[_]]: Lens[LspContext[S], LocationsState[S]] = {
val get: LspContext[S] => LocationsState[S] =
ctx => LocationsState(ctx.variables)
val replace: LocationsState[S] => LspContext[S] => LspContext[S] =
locs => ctx => ctx.copy(variables = locs.variables)
Lens(get)(replace)
}
} }

View File

@ -29,18 +29,29 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
usePosition: Int, usePosition: Int,
defCode: String, defCode: String,
useCode: Option[String] = None, useCode: Option[String] = None,
fieldName: Option[String] = None fieldOrSynonym: Option[String] = None
): Boolean = { ): Boolean = {
(for { (for {
defPos <- getByPosition(defCode, name, defPosition) defPos <- getByPosition(defCode, name, defPosition)
usePos <- getByPosition(useCode.getOrElse(defCode), fieldName.getOrElse(name), usePosition) usePos <- getByPosition(
useCode.getOrElse(defCode),
fieldOrSynonym.getOrElse(name),
usePosition
)
} yield { } yield {
val (defStart, defEnd) = defPos val (defStart, defEnd) = defPos
val (useStart, useEnd) = usePos val (useStart, useEnd) = usePos
c.allLocations.exists { case TokenLocation(useT, defT) => c.variables.exists { case VariableInfo(defI, occs) =>
val defSpan = defT.unit._1 val defSpan = defI.token.unit._1
val useSpan = useT.unit._1 if (defSpan.startIndex == defStart && defSpan.endIndex == defEnd) {
defSpan.startIndex == defStart && defSpan.endIndex == defEnd && useSpan.startIndex == useStart && useSpan.endIndex == useEnd occs.exists { useT =>
val useSpan = useT.unit._1
useSpan.startIndex == useStart && useSpan.endIndex == useEnd
}
} else {
false
}
} }
}).getOrElse(false) }).getOrElse(false)
} }
@ -115,15 +126,23 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
id => txt => Parser.parse(Parser.parserSchema)(txt), id => txt => Parser.parse(Parser.parserSchema)(txt),
AquaCompilerConf(ConstantRaw.defaultConstants(None)) AquaCompilerConf(ConstantRaw.defaultConstants(None))
) )
.leftMap { ee =>
println(ee)
ee
}
it should "return right tokens" in { it should "return right tokens" in {
val main = val main =
"""aqua Import """aqua Import declares foo_wrapper, Ab, Str, useAbAndStruct, SOME_CONST
| |
|import foo, strFunc, num from "export2.aqua" |import foo, strFunc, num, absb as otherName from "export2.aqua"
|
|use thirdFunc as thirdRenamed from "third.aqua" as Third
| |
|import "../gen/OneMore.aqua" |import "../gen/OneMore.aqua"
| |
|export foo_wrapper, SOME_CONST, EXPORTED as NEW_NAME
|
|func foo_wrapper() -> string: |func foo_wrapper() -> string:
| fooResult <- foo() | fooResult <- foo()
| if 1 == 1: | if 1 == 1:
@ -146,13 +165,16 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
| strFunc(s.someField) | strFunc(s.someField)
| num(Ab.someField) | num(Ab.someField)
| |
|const SOME_CONST = 1
|const EXPORTED = 1
|
|""".stripMargin |""".stripMargin
val src = Map( val src = Map(
"index.aqua" -> main "index.aqua" -> main
) )
val firstImport = val firstImport =
"""aqua Export declares strFunc, num, foo """aqua Export declares strFunc, num, foo, absb
| |
|func absb() -> string: |func absb() -> string:
| <- "ff" | <- "ff"
@ -176,11 +198,21 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
| consume(s: string) | consume(s: string)
|""".stripMargin |""".stripMargin
val thirdImport =
"""aqua Third declares thirdFunc
|
|func thirdFunc() -> string:
| <- "I am MyFooBar foo"
|
|""".stripMargin
val imports = Map( val imports = Map(
"export2.aqua" -> "export2.aqua" ->
firstImport, firstImport,
"../gen/OneMore.aqua" -> "../gen/OneMore.aqua" ->
secondImport secondImport,
"third.aqua" ->
thirdImport
) )
val res = compile(src, imports).toOption.get.values.head val res = compile(src, imports).toOption.get.values.head
@ -193,6 +225,50 @@ class AquaLSPSpec extends AnyFlatSpec with Matchers with Inside {
) )
) )
res.checkTokenLoc(
main,
"foo_wrapper",
2,
ArrowType(
NilType,
ProductType(ScalarType.string :: Nil)
)
) shouldBe true
res.checkTokenLoc(
main,
"SOME_CONST",
2,
LiteralType.unsigned
) shouldBe true
// exports
res.checkLocations("foo_wrapper", 2, 1, main) shouldBe true
res.checkLocations("SOME_CONST", 2, 1, main) shouldBe true
res.checkLocations("EXPORTED", 1, 0, main) shouldBe true
res.checkLocations("EXPORTED", 1, 0, main, None, Some("NEW_NAME")) shouldBe true
// declares
res.checkLocations("foo_wrapper", 2, 0, main) shouldBe true
res.checkLocations("SOME_CONST", 2, 0, main) shouldBe true
// imports
res.checkLocations("foo", 1, 1, firstImport, Some(main)) shouldBe true
res.checkLocations("strFunc", 1, 0, firstImport, Some(main)) shouldBe true
res.checkLocations("num", 1, 0, firstImport, Some(main)) shouldBe true
res.checkLocations("absb", 1, 0, firstImport, Some(main)) shouldBe true
res.checkLocations("absb", 1, 0, firstImport, Some(main), Some("otherName")) shouldBe true
// use
res.checkLocations("thirdFunc", 1, 0, thirdImport, Some(main)) shouldBe true
res.checkLocations(
"thirdFunc",
1,
0,
thirdImport,
Some(main),
Some("thirdRenamed")
) shouldBe true
// inside `foo_wrapper` func // inside `foo_wrapper` func
res.checkTokenLoc(main, "fooResult", 0, ScalarType.string) shouldBe true res.checkTokenLoc(main, "fooResult", 0, ScalarType.string) shouldBe true
res.checkLocations("fooResult", 0, 1, main) shouldBe true res.checkLocations("fooResult", 0, 1, main) shouldBe true

View File

@ -3,15 +3,12 @@ package aqua.linker
import aqua.errors.Errors.internalError import aqua.errors.Errors.internalError
import cats.MonadError import cats.MonadError
import cats.data.{NonEmptyChain, Validated, ValidatedNec} import cats.data.NonEmptyChain
import cats.instances.list.* import cats.instances.list.*
import cats.kernel.{Monoid, Semigroup}
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.flatMap.* import cats.syntax.flatMap.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.semigroup.*
import cats.syntax.traverse.* import cats.syntax.traverse.*
import cats.syntax.validated.*
import scala.annotation.tailrec import scala.annotation.tailrec
import scribe.Logging import scribe.Logging
@ -116,10 +113,10 @@ object Linker extends Logging {
} else } else
canHandle.traverse { mod => canHandle.traverse { mod =>
// Gather all imports for module // Gather all imports for module
val imports = mod.imports.mapValues { imp => val imports = mod.imports.view.mapValues { imp =>
proc proc
.get(imp)
.getOrElse( .getOrElse(
imp,
// Should not happen as we check it above // Should not happen as we check it above
internalError(s"Module $imp not found in $proc") internalError(s"Module $imp not found in $proc")
) )
@ -155,6 +152,6 @@ object Linker extends Logging {
else else
iter(modules.loaded.values.toList, Map.empty, cycle).map( iter(modules.loaded.values.toList, Map.empty, cycle).map(
// Remove all modules that are not exported from result // Remove all modules that are not exported from result
_.filterKeys(modules.exports.contains).toMap _.view.filterKeys(modules.exports.contains).toMap
) )
} }

View File

@ -23,8 +23,6 @@ class LinkerSpec extends AnyFlatSpec with Matchers {
imports = Map("mod2" -> "mod2"), imports = Map("mod2" -> "mod2"),
dependsOn = Map("mod2" -> "unresolved mod2 in mod1"), dependsOn = Map("mod2" -> "unresolved mod2 in mod1"),
body = imports => { body = imports => {
println(s"mod1: $imports")
imports imports
.get("mod2") .get("mod2")
.toRight("mod2 not found in mod1") .toRight("mod2 not found in mod1")

View File

@ -18,7 +18,7 @@ case class UseExpr[F[_]](
override def mapK[K[_]: Comonad](fk: F ~> K): UseExpr[K] = override def mapK[K[_]: Comonad](fk: F ~> K): UseExpr[K] =
copy(filename.mapK(fk), asModule.map(_.mapK(fk))) copy(filename.mapK(fk), asModule.map(_.mapK(fk)))
override def toString(): String = override def toString: String =
s"use ${filename.value}${asModule.map(_.value).fold("")(" as " + _)}" s"use ${filename.value}${asModule.map(_.value).fold("")(" as " + _)}"
} }

View File

@ -15,7 +15,6 @@ class ArrowTypeSem[S[_]](val expr: ArrowTypeExpr[S]) extends AnyVal {
def program[Alg[_]: Monad](implicit def program[Alg[_]: Monad](implicit
T: TypesAlgebra[S, Alg], T: TypesAlgebra[S, Alg],
A: AbilitiesAlgebra[S, Alg],
D: DefinitionsAlgebra[S, Alg] D: DefinitionsAlgebra[S, Alg]
): Prog[Alg, Raw] = ): Prog[Alg, Raw] =
T.resolveArrowDef(expr.`type`).flatMap { T.resolveArrowDef(expr.`type`).flatMap {

View File

@ -24,7 +24,6 @@ class ServiceSem[S[_]](val expr: ServiceExpr[S]) extends AnyVal {
private def define[Alg[_]: Monad](using private def define[Alg[_]: Monad](using
A: AbilitiesAlgebra[S, Alg], A: AbilitiesAlgebra[S, Alg],
N: NamesAlgebra[S, Alg],
T: TypesAlgebra[S, Alg], T: TypesAlgebra[S, Alg],
V: ValuesAlgebra[S, Alg], V: ValuesAlgebra[S, Alg],
D: DefinitionsAlgebra[S, Alg] D: DefinitionsAlgebra[S, Alg]

View File

@ -0,0 +1,99 @@
package aqua.semantics.header
import aqua.parser.head.*
import aqua.parser.lexer.Token
import aqua.semantics.SemanticError
import aqua.semantics.header.HeaderHandler.*
import aqua.semantics.header.Picker.*
import aqua.semantics.rules.locations.LocationsAlgebra
import cats.data.*
import cats.data.Validated.*
import cats.instances.option.*
import cats.kernel.Semigroup
import cats.syntax.apply.*
import cats.syntax.bifunctor.*
import cats.syntax.foldable.*
import cats.syntax.functor.*
import cats.syntax.option.*
import cats.syntax.semigroup.*
import cats.syntax.validated.*
import cats.{Comonad, Monoid}
class ExportSem[S[_]: Comonad, C](expr: ExportExpr[S])(using
acm: Monoid[C],
picker: Picker[C],
locations: LocationsAlgebra[S, State[C, *]]
) {
private def exportFuncChecks(
ctx: C,
token: Token[S],
name: String
): ValidatedNec[SemanticError[S], Unit] =
Validated.condNec(
!ctx.funcReturnAbilityOrArrow(name),
(),
error(
token,
s"The function '$name' cannot be exported, because it returns an arrow or an ability"
)
) combine Validated.condNec(
!ctx.funcAcceptAbility(name),
(),
error(
token,
s"The function '$name' cannot be exported, because it accepts an ability"
)
)
def headerSem: Res[S, C] = {
// Save exports, finally handle them
HeaderSem(
// Nothing there
picker.blank,
finSem
).validNec
}
private def finSem(ctx: C, initCtx: C): ValidatedNec[SemanticError[S], C] = {
val pubs = expr.pubs
.map(
_.bimap(
_.bimap(n => (n, n.value), n => (n, n.map(_.value))),
_.bimap(n => (n, n.value), n => (n, n.map(_.value)))
).merge
)
val tokens = pubs.toList.flatMap {
case ((token, name), (renameToken, _)) =>
renameToken.map(name -> _).toList :+ (name, token)
}
val ctxWithExportLocations = ctx.addOccurences(tokens)
val sumCtx = initCtx |+| ctxWithExportLocations
pubs.map { case ((token, name), (_, rename)) =>
sumCtx
.pick(name, rename, declared = false)
.as(Map(name -> rename))
.toValid(
error(
token,
s"Files has no $name declaration or import, " +
s"cannot export, available functions: ${sumCtx.funcNames.mkString(", ")}"
)
)
.ensure(
error(
token,
s"Can not export '$name' as it is an ability"
)
)(_ => !sumCtx.isAbility(name))
.toValidatedNec <* exportFuncChecks(sumCtx, token, name)
}
.prepend(validNec(sumCtx.exports))
.combineAll
.map(sumCtx.setExports)
}
}

View File

@ -2,68 +2,34 @@ package aqua.semantics.header
import aqua.parser.Ast import aqua.parser.Ast
import aqua.parser.head.* import aqua.parser.head.*
import aqua.parser.lexer.{Ability, Name, Token} import aqua.parser.lexer.{Ability, Token}
import aqua.semantics.header.Picker.* import aqua.semantics.header.Picker.*
import aqua.semantics.{HeaderError, SemanticError} import aqua.semantics.{HeaderError, SemanticError}
import aqua.semantics.rules.locations.LocationsAlgebra
import cats.data.* import cats.data.*
import cats.data.Validated.* import cats.data.Validated.*
import cats.free.Cofree
import cats.instances.list.*
import cats.instances.option.*
import cats.kernel.Semigroup
import cats.syntax.apply.*
import cats.syntax.bifunctor.* import cats.syntax.bifunctor.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
import cats.syntax.functor.*
import cats.syntax.option.* import cats.syntax.option.*
import cats.syntax.semigroup.* import cats.syntax.semigroup.*
import cats.syntax.traverse.*
import cats.syntax.validated.* import cats.syntax.validated.*
import cats.{Comonad, Eval, Monoid} import cats.{Comonad, Monoid}
class HeaderHandler[S[_]: Comonad, C](using class HeaderHandler[S[_]: Comonad, C](using
acm: Monoid[C], acm: Monoid[C],
headMonoid: Monoid[HeaderSem[S, C]], headMonoid: Monoid[HeaderSem[S, C]],
picker: Picker[C] picker: Picker[C],
// NOTE: This typeclass is here to reuse
// the code from the body semantics
locations: LocationsAlgebra[S, State[C, *]]
) { ) {
type Res[S[_], C] = ValidatedNec[SemanticError[S], HeaderSem[S, C]] import HeaderHandler.*
type ResAC[S[_]] = ValidatedNec[SemanticError[S], C]
type ResT[S[_], T] = ValidatedNec[SemanticError[S], T]
// Helper: monoidal combine of all the childrens after parent res
private def combineAnd(children: Chain[Res[S, C]])(
parent: Res[S, C]
): Eval[Res[S, C]] =
Eval.later(parent |+| children.combineAll)
// Error generator with token pointer
private def error[T](
token: Token[S],
msg: String
): SemanticError[S] = HeaderError(token, msg)
private def exportFuncChecks(ctx: C, token: Token[S], name: String): ResT[S, Unit] =
Validated.condNec(
!ctx.funcReturnAbilityOrArrow(name),
(),
error(
token,
s"The function '$name' cannot be exported, because it returns an arrow or an ability"
)
) combine Validated.condNec(
!ctx.funcAcceptAbility(name),
(),
error(
token,
s"The function '$name' cannot be exported, because it accepts an ability"
)
)
def sem(imports: Map[String, C], header: Ast.Head[S]): Res[S, C] = { def sem(imports: Map[String, C], header: Ast.Head[S]): Res[S, C] = {
// Resolve a filename from given imports or fail // Resolve a filename from given imports or fail
def resolve(f: FilenameExpr[S]): ResAC[S] = def resolve(f: FilenameExpr[S]): ResAC[S, C] =
imports imports
.get(f.fileValue) .get(f.fileValue)
.map(_.pickDeclared) .map(_.pickDeclared)
@ -72,16 +38,21 @@ class HeaderHandler[S[_]: Comonad, C](using
) )
// Get part of the declared context (for import/use ... from ... expressions) // Get part of the declared context (for import/use ... from ... expressions)
def getFrom(f: FromExpr[S], ctx: C): ResAC[S] = def getFrom(f: FromExpr[S], ctx: C): ResAC[S, C] =
ctx.pickHeader.validNec |+| f.imports ctx.pickHeader.validNec |+| f.imports
.map( .map(
_.bimap( _.bimap(
_.bimap(n => (n, n.value), _.map(_.value)), _.bimap(n => (n, n.value), n => (n, n.map(_.value))),
_.bimap(n => (n, n.value), _.map(_.value)) _.bimap(n => (n, n.value), n => (n, n.map(_.value)))
).merge match { ).merge match {
case ((token, name), rename) => case ((token, name), (renameToken, rename)) =>
ctx ctx
.pick(name, rename, ctx.module.nonEmpty) .pick(name, rename, ctx.module.nonEmpty)
.map { ctx =>
val defName = rename.getOrElse(name)
val occs = renameToken.map(defName -> _).toList :+ (defName, token)
ctx.addOccurences(occs)
}
.toValidNec( .toValidNec(
error( error(
token, token,
@ -93,7 +64,7 @@ class HeaderHandler[S[_]: Comonad, C](using
.combineAll .combineAll
// Convert an imported context into a module (ability) // Convert an imported context into a module (ability)
def toModule(ctx: C, tkn: Token[S], rename: Option[Ability[S]]): ResAC[S] = def toModule(ctx: C, tkn: Token[S], rename: Option[Ability[S]]): ResAC[S, C] =
rename rename
.map(_.value) .map(_.value)
.orElse(ctx.module) .orElse(ctx.module)
@ -105,46 +76,8 @@ class HeaderHandler[S[_]: Comonad, C](using
) )
) )
val handleModule: ModuleExpr[S] => Res[S, C] = { val handleModule: ModuleExpr[S] => Res[S, C] = { me =>
case ModuleExpr(word, name, declareAll, declareNames, declareCustom) => ModuleSem(me).headerSem
val shouldDeclare = declareNames.map(_.value).toSet ++ declareCustom.map(_.value)
lazy val sem = HeaderSem(
// Save module header info
acm.empty.setModule(
name.value,
shouldDeclare
),
(ctx, _) =>
// When file is handled, check that all the declarations exists
if (declareAll.nonEmpty)
ctx.setModule(name.value, declares = ctx.all).validNec
else
(
declareNames.fproductLeft(_.value) ::: declareCustom.fproductLeft(_.value)
).map { case (n, t) =>
ctx
.pick(n, None, ctx.module.nonEmpty)
.toValidNec(
error(
t,
s"`$n` is expected to be declared, but declaration is not found in the file"
)
)
.void
}.combineAll.as(
// TODO: why module name and declares is lost? where is it lost?
ctx.setModule(name.value, declares = shouldDeclare)
)
)
word.value.fold(
module = error(
word,
"Keyword `module` is deprecated, use `aqua` instead"
).invalidNec,
aqua = sem.validNec
)
} }
// Handler for every header expression, will be combined later // Handler for every header expression, will be combined later
@ -181,44 +114,8 @@ class HeaderHandler[S[_]: Comonad, C](using
HeaderSem(fc, (c, _) => validNec(c)) HeaderSem(fc, (c, _) => validNec(c))
} }
case ExportExpr(pubs) => case ee: ExportExpr[S] =>
// Save exports, finally handle them ExportSem(ee).headerSem
HeaderSem(
// Nothing there
picker.blank,
(ctx, initCtx) =>
val sumCtx = initCtx |+| ctx
pubs
.map(
_.bimap(
_.bimap(n => (n, n.value), _.map(_.value)),
_.bimap(n => (n, n.value), _.map(_.value))
).merge
)
.map { case ((token, name), rename) =>
sumCtx
.pick(name, rename, declared = false)
.as(Map(name -> rename))
.toValid(
error(
token,
s"File has no $name declaration or import, " +
s"cannot export, available functions: ${sumCtx.funcNames.mkString(", ")}"
)
)
.ensure(
error(
token,
s"Can not export '$name' as it is an ability"
)
)(_ => !sumCtx.isAbility(name))
.toValidatedNec <* exportFuncChecks(sumCtx, token, name)
}
.prepend(validNec(ctx.exports))
.combineAll
.map(ctx.setExports)
).validNec
case f: FilenameExpr[S] => case f: FilenameExpr[S] =>
resolve(f).map(fc => HeaderSem(fc, (c, _) => validNec(c))) resolve(f).map(fc => HeaderSem(fc, (c, _) => validNec(c)))
@ -241,3 +138,15 @@ class HeaderHandler[S[_]: Comonad, C](using
module |+| other module |+| other
} }
} }
object HeaderHandler {
type Res[S[_], C] = ValidatedNec[SemanticError[S], HeaderSem[S, C]]
type ResAC[S[_], C] = ValidatedNec[SemanticError[S], C]
// Error generator with token pointer
def error[S[_], T](
token: Token[S],
msg: String
): SemanticError[S] = HeaderError(token, msg)
}

View File

@ -0,0 +1,68 @@
package aqua.semantics.header
import aqua.parser.head.ModuleExpr
import aqua.semantics.header.HeaderHandler.{Res, error}
import aqua.semantics.header.Picker.*
import aqua.semantics.rules.locations.LocationsAlgebra
import cats.data.*
import cats.data.Validated.*
import cats.kernel.Semigroup
import cats.syntax.foldable.*
import cats.syntax.functor.*
import cats.syntax.option.*
import cats.syntax.validated.*
import cats.{Comonad, Monoid}
class ModuleSem[S[_]: Comonad, C: Picker](expr: ModuleExpr[S])(using
acm: Monoid[C],
locations: LocationsAlgebra[S, State[C, *]]
) {
import expr.*
def headerSem: Res[S, C] = {
val shouldDeclare = declareNames.map(_.value).toSet ++ declareCustom.map(_.value)
lazy val sem = HeaderSem(
// Save module header info
acm.empty.setModule(
name.value,
shouldDeclare
),
(ctx, _) =>
// When file is handled, check that all the declarations exists
if (declareAll.nonEmpty)
ctx.setModule(name.value, declares = ctx.all).validNec
else
(
declareNames.fproductLeft(_.value) ::: declareCustom.fproductLeft(_.value)
).map { case (n, t) =>
ctx
.pick(n, None, ctx.module.nonEmpty)
.toValidNec(
error(
t,
s"`$n` is expected to be declared, but declaration is not found in the file"
)
)
.void
}.combineAll.as {
val tokens = declareNames.map(n => n.value -> n) ++ declareCustom.map(a => a.value -> a)
val ctxWithDeclaresLoc = ctx.addOccurences(tokens)
// TODO: why module name and declares is lost? where is it lost?
ctxWithDeclaresLoc.setModule(name.value, declares = shouldDeclare)
}
)
word.value.fold(
module = error(
word,
"Keyword `module` is deprecated, use `aqua` instead"
).invalidNec,
aqua = sem.validNec
)
}
}

View File

@ -0,0 +1,20 @@
package aqua.semantics
import cats.data.State
import aqua.semantics.rules.locations.LocationsAlgebra
import aqua.parser.lexer.Token
package object header {
/*
NOTE: This extension glues locations algebra from the body semantics
with the context that is used in the header semantics
*/
extension [S[_], C](context: C)(using
locations: LocationsAlgebra[S, State[C, *]]
) {
def addOccurences(tokens: List[(String, Token[S])]): C =
locations.pointLocations(tokens).runS(context).value
}
}

View File

@ -4,12 +4,11 @@ import aqua.parser.lexer.{Name, NamedTypeToken, Token}
import aqua.raw.RawContext import aqua.raw.RawContext
import aqua.raw.value.ValueRaw import aqua.raw.value.ValueRaw
import aqua.semantics.Levenshtein import aqua.semantics.Levenshtein
import aqua.semantics.rules.locations.LocationsAlgebra import aqua.semantics.rules.locations.{DefinitionInfo, LocationsAlgebra}
import aqua.semantics.rules.mangler.ManglerAlgebra import aqua.semantics.rules.mangler.ManglerAlgebra
import aqua.semantics.rules.report.ReportAlgebra import aqua.semantics.rules.report.ReportAlgebra
import aqua.semantics.rules.{StackInterpreter, abilities} import aqua.semantics.rules.{abilities, StackInterpreter}
import aqua.types.ArrowType import aqua.types.ArrowType
import cats.data.{NonEmptyMap, State} import cats.data.{NonEmptyMap, State}
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.syntax.apply.* import cats.syntax.apply.*

View File

@ -10,6 +10,8 @@ case class LocationsState[S[_]](
variables: List[VariableInfo[S]] = Nil variables: List[VariableInfo[S]] = Nil
) extends Logging { ) extends Logging {
lazy val allLocations: List[TokenLocation[S]] = variables.flatMap(_.allLocations)
def addDefinitions(newDefinitions: List[DefinitionInfo[S]]): LocationsState[S] = def addDefinitions(newDefinitions: List[DefinitionInfo[S]]): LocationsState[S] =
copy(variables = newDefinitions.map(d => VariableInfo(d)) ++ variables) copy(variables = newDefinitions.map(d => VariableInfo(d)) ++ variables)
@ -44,13 +46,12 @@ case class LocationsState[S[_]](
object LocationsState { object LocationsState {
implicit def locationsStateMonoid[S[_]]: Monoid[LocationsState[S]] = given [S[_]]: Monoid[LocationsState[S]] with {
new Monoid[LocationsState[S]] { override def empty: LocationsState[S] = LocationsState()
override def empty: LocationsState[S] = LocationsState()
override def combine(x: LocationsState[S], y: LocationsState[S]): LocationsState[S] = override def combine(x: LocationsState[S], y: LocationsState[S]): LocationsState[S] =
LocationsState( LocationsState(
variables = x.variables ++ y.variables variables = x.variables ++ y.variables
) )
} }
} }

View File

@ -10,8 +10,9 @@ import aqua.raw.ops.RawTag
import aqua.raw.value.VarRaw import aqua.raw.value.VarRaw
import aqua.semantics.header.{HeaderHandler, HeaderSem} import aqua.semantics.header.{HeaderHandler, HeaderSem}
import aqua.types.{AbilityType, ArrowType, NilType, ProductType, ScalarType} import aqua.types.{AbilityType, ArrowType, NilType, ProductType, ScalarType}
import aqua.semantics.rules.locations.{LocationsAlgebra, DummyLocationsInterpreter}
import cats.data.{Chain, NonEmptyList, NonEmptyMap, Validated} import cats.data.{State, Chain, NonEmptyList, NonEmptyMap, Validated}
import cats.free.Cofree import cats.free.Cofree
import cats.syntax.applicative.* import cats.syntax.applicative.*
import cats.{Eval, Id, Monoid} import cats.{Eval, Id, Monoid}
@ -23,6 +24,9 @@ class HeaderSpec extends AnyFlatSpec with Matchers with Inside {
given Monoid[RawContext] = RawContext.implicits(RawContext.blank).rawContextMonoid given Monoid[RawContext] = RawContext.implicits(RawContext.blank).rawContextMonoid
given LocationsAlgebra[Id, State[RawContext, *]] =
DummyLocationsInterpreter[Id, RawContext]()
val handler = new HeaderHandler[Id, RawContext]() val handler = new HeaderHandler[Id, RawContext]()
def exportHeader(funcName: String): Ast.Head[Id] = { def exportHeader(funcName: String): Ast.Head[Id] = {