如何获取Kotlin AST?

8

我有一个包含Kotlin源代码的字符串。如何在运行时编译它,并获取抽象语法树和类型信息以进行分析?


我建立了一个项目来完成这个任务(使用AST,但不是类型):https://github.com/cretz/kastree - Chad Retz
2个回答

11

我对Kotlin编译器进行了一些调查。关于获取AST的概念验证可以在我的GitHub存储库中看到。

这只是一个草图,但可能会有所帮助:

class KotlinScriptParser {
    companion object {
        private val LOG = Logger.getLogger(KotlinScriptParser.javaClass.name)
        private val messageCollector = object : MessageCollector {
            override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation) {
                val path = location.path
                val position = if (path == null) "" else "$path: (${location.line}, ${location.column}) "

                val text = position + message

                if (CompilerMessageSeverity.VERBOSE.contains(severity)) {
                    LOG.finest(text)
                } else if (CompilerMessageSeverity.ERRORS.contains(severity)) {
                    LOG.severe(text)
                } else if (severity == CompilerMessageSeverity.INFO) {
                    LOG.info(text)
                } else {
                    LOG.warning(text)
                }
            }
        }

        private val classPath: ArrayList<File> by lazy {
            val classpath = arrayListOf<File>()
            classpath += PathUtil.getResourcePathForClass(AnnotationTarget.CLASS.javaClass)
            classpath
        }
    }

    fun parse(vararg files: String): TopDownAnalysisContext {
        // The Kotlin compiler configuration
        val configuration = CompilerConfiguration()

        val groupingCollector = GroupingMessageCollector(messageCollector)
        val severityCollector = MessageSeverityCollector(groupingCollector)
        configuration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, severityCollector)


        configuration.addJvmClasspathRoots(PathUtil.getJdkClassesRoots())
        // The path to .kt files sources
        files.forEach { configuration.addKotlinSourceRoot(it) }
        // Configuring Kotlin class path
        configuration.addJvmClasspathRoots(classPath)
        configuration.put(JVMConfigurationKeys.MODULE_NAME, JvmAbi.DEFAULT_MODULE_NAME)
        configuration.put<List<AnalyzerScriptParameter>>(JVMConfigurationKeys.SCRIPT_PARAMETERS, CommandLineScriptUtils.scriptParameters())

        val rootDisposable = Disposer.newDisposable()
        try {
            val environment = KotlinCoreEnvironment.createForProduction(rootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
            val ktFiles = environment.getSourceFiles()
            val sharedTrace = CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace()
            val moduleContext = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(environment.project,
                    environment.getModuleName())

            val project = moduleContext.project
            val allFiles = JvmAnalyzerFacade.getAllFilesToAnalyze(project, null, ktFiles)
            val providerFactory = FileBasedDeclarationProviderFactory(moduleContext.storageManager, allFiles)
            val lookupTracker = LookupTracker.DO_NOTHING
            val packagePartProvider = JvmPackagePartProvider(environment)
            val container = createContainerForTopDownAnalyzerForJvm(
                    moduleContext,
                    sharedTrace,
                    providerFactory,
                    GlobalSearchScope.allScope(project),
                    lookupTracker,
                    packagePartProvider)

            val additionalProviders = ArrayList<PackageFragmentProvider>()

            additionalProviders.add(container.javaDescriptorResolver.packageFragmentProvider)

            return container.lazyTopDownAnalyzerForTopLevel.analyzeFiles(TopDownAnalysisMode.LocalDeclarations, allFiles, additionalProviders)
        } finally {
            rootDisposable.dispose()
            if (severityCollector.anyReported(CompilerMessageSeverity.ERROR)) {
                throw RuntimeException("Compilation error")
            }
        }
    }
}


fun main(args: Array<String>) {
    val scriptFile = "/media/data/java/blackfern/kotlin-compile-test/test.kt"

    val parser = KotlinScriptParser()
    // Getting a root element of the AST
    val analyzeContext = parser.parse(scriptFile)
    // Sample AST investigation
    val function = analyzeContext.functions.keys.first()
    val body = function.bodyExpression as KtBlockExpression
}

请在答案中包含链接资源的必要部分,应避免所谓的“仅链接”答案。 - plamut

7
目前还没有标准的API来实现这个功能。你可以尝试使用Kotlin编译器和REPL源代码来进行尝试。请注意,不要删除HTML标签。

有任何起点吗?相关的单元测试,或许是类名?我知道没有API,但我会感激任何关于从哪里开始或查看哪些类的提示。 - Iurii
3
说实话,这有点复杂。你可以了解一下我们的 ExpressionTypingVisitor 类如何工作,还有一个叫做 PSI 的概念 - 一种具体的语法树,以及 BindingContext - 从 PSI 到类型信息和其他语义信息的映射。PSI 类都以 Jet 为前缀。 - Andrey Breslav
2
现在的状态是什么?是否有或将会有一个用于此目的的API? - user3464741
@user3464741 没有实质性的更改,我们可能会在某个时候公开这样的API,但目前这不是一项高优先级的任务。 - Andrey Breslav
1
今天的情况如何?在我上次提问一年后,有什么新进展吗?我该如何获取多个 .kt 源文件的类型检查语法树? - user3464741
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接