Skip to content

Commit

Permalink
[TableGen] Implement resolution of include statements (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
zero9178 authored Feb 13, 2025
1 parent 3c3304b commit 9620fc3
Show file tree
Hide file tree
Showing 27 changed files with 680 additions and 23 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
# MLIR ODS Changelog

## [Unreleased]
### Added
- Added resolution of include statements, enabling the `GoTo` action

## [0.3.0] - 2025-02-04
### Added
Expand Down
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ dependencies {
zipSigner()
testFramework(TestFrameworkType.Platform)
}

implementation("org.yaml:snakeyaml:2.3")
}

// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.util.getValue
import com.intellij.util.setValue
import com.jetbrains.cidr.cpp.cmake.workspace.CMakeWorkspace
import com.jetbrains.cidr.cpp.cmake.workspace.CMakeWorkspaceListener
import com.jetbrains.cidr.cpp.execution.CMakeBuildProfileExecutionTarget
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicReference

private val LOG = logger<CMakeActiveProfileService>()

Expand All @@ -27,9 +30,8 @@ private class CMakeExecutionTargetListener(private val project: Project) : Execu
override fun activeTargetChanged(newTarget: ExecutionTarget) {
val target = newTarget as? CMakeBuildProfileExecutionTarget ?: return

project.service<CMakeActiveProfileService>().profile = target.profileName
project.service<CMakeActiveProfileService>().profileName = target.profileName
LOG.info("Restarting LSP due to build profile change")
restartTableGenLSPAsync(project)
}
}

Expand All @@ -39,6 +41,26 @@ private class CMakeExecutionTargetListener(private val project: Project) : Execu
@Service(Service.Level.PROJECT)
class CMakeActiveProfileService(private val project: Project, private val cs: CoroutineScope) {

private val myProfileNameFlow = MutableStateFlow("")
internal val myProfileListFlow = MutableStateFlow(
project.service<CMakeWorkspace>().profileInfos.toList()
)

init {
project.messageBus.connect(cs).subscribe(CMakeWorkspaceListener.TOPIC, object : CMakeWorkspaceListener {
override fun generationFinished() {
myProfileListFlow.value =
project.service<CMakeWorkspace>().profileInfos.toList()
}
})

cs.launch {
myProfileNameFlow.filter { !it.isEmpty() }.collect {

Check notice on line 58 in src/main/kotlin/com/github/zero9178/mlirods/clion/CMakeActiveProfileService.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Negated call can be simplified

Replace negated 'isEmpty' with 'isNotEmpty'
restartTableGenLSPAsync(project)
}
}
}

/**
* Starts an asynchronous operation to initialize the profile from the currently active target.
*/
Expand All @@ -47,14 +69,31 @@ class CMakeActiveProfileService(private val project: Project, private val cs: Co
val manager = project.service<ExecutionTargetManager>()
val target = manager.activeTarget as? CMakeBuildProfileExecutionTarget ?: return@launch

profile = target.profileName
profileName = target.profileName
LOG.info("Restarting LSP due to run manager being initialized")
restartTableGenLSPAsync(project)
}

/**
* Returns the name of the current cmake build profile.
*/
var profile: String by AtomicReference("")
internal set
var profileName: String
internal set(value) {
myProfileNameFlow.value = value
}
get() = myProfileNameFlow.value

/**
* A flow yielding new profile names whenever the active profile name has changed.
*/
val profileNameFlow: StateFlow<String>

Check notice on line 88 in src/main/kotlin/com/github/zero9178/mlirods/clion/CMakeActiveProfileService.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Class member can have 'private' visibility

Property 'profileNameFlow' could be private
get() = myProfileNameFlow

/**
* A flow yielding the active profile whenever the active profile has changed.
*/
val profileFlow = profileNameFlow.combine(myProfileListFlow) { name, list ->
list.find {
it.profile.name == name
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.github.zero9178.mlirods.clion

import com.github.zero9178.mlirods.model.TableGenCompilationCommandsProvider
import com.github.zero9178.mlirods.model.CompilationCommandsState
import com.github.zero9178.mlirods.model.IncludePaths
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.newvfs.BulkFileListener
import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent
import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent
import com.intellij.openapi.vfs.newvfs.events.VFileEvent
import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent
import com.intellij.util.messages.impl.subscribeAsFlow
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.transform
import org.yaml.snakeyaml.LoaderOptions
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.constructor.Constructor
import java.io.FileNotFoundException
import kotlin.io.path.Path

// Note: Needs to be public due to limitations in snakeyaml.
data class FileInfoDto(
var filepath: String = "",
var includes: String = "",
)

private val LOG = logger<CMakeTableGenCompilationCommandsProvider>()

class CMakeTableGenCompilationCommandsProvider : TableGenCompilationCommandsProvider {

override fun getCompilationCommandsFlow(project: Project): Flow<CompilationCommandsState> {
// React to VCS changes that might create, move or delete one of the files returned by the flow.
val vfsChangeFlow = project.messageBus.subscribeAsFlow(VirtualFileManager.VFS_CHANGES) {
send(Unit)
object : BulkFileListener {
override fun after(events: List<VFileEvent>) {
if (events.any {
it is VFileCreateEvent || it is VFileMoveEvent || it is VFileDeleteEvent
}) trySend(Unit)
}
}
}

return project.service<CMakeActiveProfileService>().profileFlow.filterNotNull().map {
it.generationDir.resolve("tablegen_compile_commands.yml")
}.transform { file ->
try {
val result = file.inputStream().use { inputStream ->
Yaml(Constructor(FileInfoDto::class.java, LoaderOptions())).loadAll(inputStream)
.filterIsInstance<FileInfoDto>()
}
emit(result)
} catch (_: FileNotFoundException) {
// Swallow completely.
} catch (e: Throwable) {
// Rethrow cancellations.
currentCoroutineContext().ensureActive()
// Otherwise just warn.
LOG.warn(e)
}
}.combine(vfsChangeFlow) { dtos, _ ->
val instance = VirtualFileManager.getInstance()
val map = dtos.flatMap {
val virtualFile = instance.findFileByNioPath(Path(it.filepath))
if (virtualFile == null) {
LOG.warn("failed to find virtual file for ${it.filepath}")
return@flatMap emptyList()
}

listOf(
virtualFile to IncludePaths(
it.includes.split(
';'
).map { it -> Path(it) })

Check notice on line 82 in src/main/kotlin/com/github/zero9178/mlirods/clion/CMakeTableGenCompilationCommandsProvider.kt

View workflow job for this annotation

GitHub Actions / Qodana Community for JVM

Redundant lambda arrow

Redundant lambda arrow
)
}.associate { it }
CompilationCommandsState(map)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class CMakeTableGenLspServerSupportProvider : TableGenLspServerSupportP
return false
}

val activeConfig = project.service<CMakeActiveProfileService>().profile
val activeConfig = project.service<CMakeActiveProfileService>().profileName
val buildConfig = target.buildConfigurations.find {
it.name == activeConfig
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.github.zero9178.mlirods.index

import com.github.zero9178.mlirods.language.generated.psi.TableGenIncludeDirective
import com.intellij.psi.stubs.StringStubIndexExtension
import com.intellij.psi.stubs.StubIndexKey

val INCLUDED_INDEX = StubIndexKey.createIndexKey<String, TableGenIncludeDirective>("INCLUDED_INDEX")

/**
* Maps a filename (not path!) to every include directive that ends with that file name.
*/
private class TableGenIncludingIndex : StringStubIndexExtension<TableGenIncludeDirective>() {

override fun getKey(): StubIndexKey<String, TableGenIncludeDirective> {
return INCLUDED_INDEX
}
}
20 changes: 17 additions & 3 deletions src/main/kotlin/com/github/zero9178/mlirods/language/TableGen.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
psiImplPackage="com.github.zero9178.mlirods.language.generated.psi.impl"
psiImplUtilClass="com.github.zero9178.mlirods.language.psi.impl.TableGenPsiImplUtil"

generate=[exact-types="all"]
elementTypeHolderClass="com.github.zero9178.mlirods.language.generated.TableGenTypes"
elementTypeClass="com.github.zero9178.mlirods.language.TableGenElementType"
tokenTypeClass="com.github.zero9178.mlirods.language.TableGenTokenType"
Expand Down Expand Up @@ -71,7 +72,7 @@
WHITE_SPACE="regexp:\s*"
]

extends(".*_(statement|directive)")=statement
extends(".*_(statement)")=statement
extends(".*_type")=type
extends(".*_body_item")=body_item
extends(".*_value")=value
Expand All @@ -97,9 +98,22 @@ statement ::= include_directive
| LINE_COMMENT
| OTHER

include_directive ::= 'include' (LINE_STRING_LITERAL | LINE_STRING_LITERAL_BAD) {pin=1}
include_directive ::= 'include' LINE_STRING_LITERAL {
pin=1
methods=[
string="LINE_STRING_LITERAL"

toString
getReference
getIncludeSuffix
]
implements=["com.intellij.psi.PsiElement"
"com.github.zero9178.mlirods.language.stubs.impl.TableGenIncludeDirectiveStubInterface"]
stubClass="com.github.zero9178.mlirods.language.stubs.impl.TableGenIncludeDirectiveStub"
elementTypeClass="com.github.zero9178.mlirods.language.stubs.impl.TableGenIncludeDirectiveStubElementType"
}
preprocessor_directive ::= (HASHTAG_DEFINE | HASHTAG_IFDEF | HASHTAG_IFNDEF) IDENTIFIER
| HASHTAG_ENDIF | HASHTAG_ELSE
| HASHTAG_ENDIF | HASHTAG_ELSE {extends=statement}
assert_statement ::= 'assert' value ',' value ';' {pin=1}

private equals_value ::= '=' value {pin=1}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.github.zero9178.mlirods.language

import com.github.zero9178.mlirods.language.generated.TableGenParser
import com.github.zero9178.mlirods.language.generated.TableGenTypes
import com.github.zero9178.mlirods.language.stubs.TableGenStubFileElementType
import com.intellij.lang.ASTNode
import com.intellij.lang.ParserDefinition
import com.intellij.lexer.Lexer
Expand All @@ -8,17 +11,13 @@ import com.intellij.psi.FileViewProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.tree.IFileElementType
import com.intellij.psi.tree.TokenSet
import com.github.zero9178.mlirods.language.generated.TableGenTypes
import com.github.zero9178.mlirods.language.generated.TableGenParser

private val FILE = IFileElementType(TableGenLanguage.INSTANCE)

internal class TableGenParserDefinition : ParserDefinition {
override fun createLexer(project: Project?): Lexer = TableGenLexerAdapter()

override fun createParser(project: Project?) = TableGenParser()

override fun getFileNodeType() = FILE
override fun getFileNodeType(): IFileElementType = TableGenStubFileElementType.INSTANCE

override fun getCommentTokens(): TokenSet = COMMENTS

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.zero9178.mlirods.language.psi

import com.github.zero9178.mlirods.language.generated.psi.TableGenIncludeDirective
import com.intellij.openapi.util.TextRange
import com.intellij.psi.AbstractElementManipulator

class TableGenIncludeManipulator : AbstractElementManipulator<TableGenIncludeDirective>() {
override fun handleContentChange(
element: TableGenIncludeDirective,
range: TextRange,
newContent: String?
): TableGenIncludeDirective? {
TODO("Not yet implemented")
}

override fun getRangeInElement(element: TableGenIncludeDirective): TextRange {
return element.string?.textRangeInParent ?: TextRange.EMPTY_RANGE
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.github.zero9178.mlirods.language.psi

import com.github.zero9178.mlirods.language.generated.psi.TableGenIncludeDirective
import com.github.zero9178.mlirods.model.TableGenIncludeGraph
import com.intellij.openapi.components.service
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiReferenceBase

class TableGenIncludeReference(element: TableGenIncludeDirective) :
PsiReferenceBase<TableGenIncludeDirective>(element) {
override fun resolve(): PsiElement? {

val file = element.containingFile
val project = file.project

val vf = file.virtualFile ?: return null
val includes = project.service<TableGenIncludeGraph>().getIncludePaths(vf)

for (include in includes) {
val file = VirtualFileManager.getInstance().findFileByNioPath(include.resolve(element.includeSuffix))
?: continue
return PsiManager.getInstance(project).findFile(file) ?: continue
}
return null
}
}
Loading

0 comments on commit 9620fc3

Please sign in to comment.