Skip to content

Commit

Permalink
Use tree-sitter in completions
Browse files Browse the repository at this point in the history
  • Loading branch information
kubukoz committed Nov 10, 2024
1 parent 75e5b2a commit 4177ade
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 78 deletions.
11 changes: 6 additions & 5 deletions modules/ast/src/main/scala/playground/smithyql/AST.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import cats.Applicative
import cats.Functor
import cats.Id
import cats.Show
import cats.arrow.FunctionK
import cats.data.NonEmptyList
import cats.kernel.Eq
import cats.kernel.Order
Expand Down Expand Up @@ -87,7 +88,7 @@ final case class SourceFile[F[_]](

def mapK[G[_]: Functor](
fk: F ~> G
): AST[G] = SourceFile(
): SourceFile[G] = SourceFile(
prelude = prelude.mapK(fk),
statements = fk(statements).map(_.map(_.mapK(fk))),
)
Expand Down Expand Up @@ -132,9 +133,9 @@ final case class OperationName[F[_]](
text: String
) extends AST[F] {

def mapK[G[_]: Functor](
fk: F ~> G
): OperationName[G] = copy()
def mapK[G[_]: Functor](fk: FunctionK[F, G]): OperationName[G] = retag[G]

def retag[G[_]]: OperationName[G] = copy()

}

Expand Down Expand Up @@ -189,7 +190,7 @@ final case class QueryOperationName[F[_]](
fk: F ~> G
): QueryOperationName[G] = QueryOperationName(
identifier.map(fk(_)),
fk(operationName).map(_.mapK(fk)),
fk(operationName).map(_.retag[G]),
)

}
Expand Down
12 changes: 12 additions & 0 deletions modules/core/src/main/scala/playground/ASTAdapter.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package playground

import cats.syntax.all.*
import playground.smithyql.QualifiedIdentifier

object ASTAdapter {

def decodeQI(qi: playground.generated.nodes.QualifiedIdentifier): Option[QualifiedIdentifier] =
(qi.namespace.map(_.source).toNel, qi.selection.map(_.source))
.mapN(QualifiedIdentifier.apply)

}
95 changes: 95 additions & 0 deletions modules/core/src/main/scala/playground/MultiServiceResolver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import playground.smithyql.UseClause
import playground.smithyql.WithSource

object MultiServiceResolver {
import playground.smithyql.tsutils.*

/** Determines which service should be used for a query. The rules are:
* - If the operation name has a service identifier, there MUST be a service with that name
Expand Down Expand Up @@ -37,6 +38,40 @@ object MultiServiceResolver {
case None => resolveImplicit(queryOperationName.operationName, serviceIndex, useClauses)
}

/** Determines which service should be used for a query. The rules are:
* - If the operation name has a service identifier, there MUST be a service with that name
* that contains the given operation.
* - If there's no service identifier, find all matching services that are included in the use
* clauses. MUST find exactly one entry.
*
* In other cases, such as when we can't find a unique entry, or the explicitly referenced
* service doesn't have an operation with a matching name, we fail. The latter might eventually
* be refactored to a separate piece of code.
*
* **Important**!
*
* This method assumes that all of the use clauses match the available service set. It does NOT
* perform a check on that. For the actual check, see PreludeCompiler.
*/
def resolveServiceTs(
queryOperationName: playground.generated.nodes.OperationName,
serviceIndex: ServiceIndex,
useClauses: List[playground.generated.nodes.UseClause],
): EitherNel[CompilationError, QualifiedIdentifier] =
queryOperationName.name match {
case Some(opName) =>
// todo: this should be an option in codegen. might be a bad grammar
queryOperationName.identifier.headOption match {
case Some(explicitRef) => resolveExplicitTs(serviceIndex, explicitRef, opName)

case None => resolveImplicitTs(opName, serviceIndex, useClauses)
}
case None =>
// TODO: operation name is invalid or something like that
???

}

private def resolveExplicit(
index: ServiceIndex,
explicitRef: WithSource[QualifiedIdentifier],
Expand Down Expand Up @@ -66,6 +101,41 @@ object MultiServiceResolver {
case Some(_) => explicitRef.value.asRight
}

private def resolveExplicitTs(
index: ServiceIndex,
explicitRef: playground.generated.nodes.QualifiedIdentifier,
operationName: playground.generated.nodes.Identifier,
): EitherNel[CompilationError, QualifiedIdentifier] =
ASTAdapter.decodeQI(explicitRef) match {
case None => ??? /* todo - I don't really know xD */
// explicit reference exists, but doesn't parse
case Some(ref) =>
index.getService(ref) match {
// explicit reference exists, but the service doesn't
case None =>
CompilationError
.error(
CompilationErrorDetails.UnknownService(index.serviceIds.toList),
explicitRef.range,
)
.leftNel

// the service exists, but doesn't have the requested operation
case Some(service)
if !service.operationNames.contains_(OperationName(operationName.source)) =>
CompilationError
.error(
CompilationErrorDetails.OperationMissing(service.operationNames.toList),
operationName.range,
)
.leftNel

// all good
case Some(_) => ref.asRight
}

}

private def resolveImplicit(
operationName: WithSource[OperationName[WithSource]],
index: ServiceIndex,
Expand All @@ -90,4 +160,29 @@ object MultiServiceResolver {
}
}

private def resolveImplicitTs(
// todo: introduce type wrapper for OperationName, rename current to QueryOperationName
operationName: playground.generated.nodes.Identifier,
index: ServiceIndex,
useClauses: List[playground.generated.nodes.UseClause],
): EitherNel[CompilationError, QualifiedIdentifier] = {
val matchingServices = index
.getServices(useClauses.flatMap(_.identifier).flatMap(ASTAdapter.decodeQI).toSet)
.filter(_.hasOperation(OperationName(operationName.source)))

matchingServices match {
case one :: Nil => one.id.asRight
case _ =>
CompilationError
.error(
CompilationErrorDetails
.AmbiguousService(
workspaceServices = index.serviceIds.toList
),
operationName.range,
)
.leftNel
}
}

}
18 changes: 7 additions & 11 deletions modules/core/src/main/scala/playground/smithyql/RangeIndex.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package playground.smithyql

import cats.kernel.Monoid
import cats.syntax.all.*
import org.polyvariant.treesitter4s.Node
import tsutils.*
import util.chaining.*

trait RangeIndex {
Expand All @@ -22,10 +22,6 @@ object RangeIndex {

def build(parsed: playground.generated.nodes.SourceFile): RangeIndex = fromRanges {

extension (node: Node) {
def range: SourceRange = SourceRange(Position(node.startByte), Position(node.endByte))
}

val root = NodeContext.EmptyPath

val preludeRanges = parsed
Expand Down Expand Up @@ -137,12 +133,12 @@ object RangeIndex {
allRanges
.filter(_.range.contains(pos))
.tap { ranges =>
println()
println("=======")
println(s"all ranges: ${allRanges.map(_.render).mkString(", ")}")
println(s"ranges for position ${pos.index}: ${ranges.map(_.render).mkString(", ")}")
println("=======")
println()
// println()
// println("=======")
// println(s"all ranges: ${allRanges.map(_.render).mkString(", ")}")
// println(s"ranges for position ${pos.index}: ${ranges.map(_.render).mkString(", ")}")
// println("=======")
// println()
}
.maxByOption(_.ctx.length)
.map(_.ctx)
Expand Down
11 changes: 11 additions & 0 deletions modules/core/src/main/scala/playground/smithyql/tsutils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package playground.smithyql

import org.polyvariant.treesitter4s.Node

object tsutils {

extension (node: Node) {
def range: SourceRange = SourceRange(Position(node.startByte), Position(node.endByte))
}

}
Loading

0 comments on commit 4177ade

Please sign in to comment.