这里主要介绍 .kt 文件的处理编译过程。
如果使用命令行编译 kotlin 文件,例如编译 hello.kt 文件,执行如下命令,参考 kotlin command-line
kotlinc Hello.kt
在 kotlin 源码中,位于 kotlin/compiler/cli/bin/kotlinc 文件中,这个 shell 文件是 kotlin 编译的入口,然后我们找到编译的入口
declare -a kotlin_app
//运行入口
if [ -n "$KOTLIN_RUNNER" ];
then
//该处理操作在 kotlin/compiler/cli/cli-runner/src/org/jetbrains/kotlin/runner/Main.kt
//主要是创建一系列的 ClassLoader,加载 kotlin stdlib 等库的 Class
java_args=("${java_args[@]}" "-Dkotlin.home=${KOTLIN_HOME}")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-runner.jar" "org.jetbrains.kotlin.runner.Main")
else
//这里才是真正的编译入口
[ -n "$KOTLIN_COMPILER" ] || KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
java_args=("${java_args[@]}" "-noverify")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-preloader.jar" "org.jetbrains.kotlin.preloading.Preloader" "-cp" "${KOTLIN_HOME}/lib/kotlin-compiler.jar" $KOTLIN_COMPILER)
首先会执行 Main.kt 文件的 main() 函数,如下,在 main 函数里面执行了 run() 方法:
//main 方法
@JvmStatic
fun main(args: Array<String>) {
try {
run(args)
}
catch (e: RunnerException) {
System.err.println("error: " + e.message)
System.exit(1)
}
}
//run()方法,主要是判断命令,读取传入参数
private fun run(args: Array<String>) {
val classpath = arrayListOf<URL>()
var runner: Runner? = null
var collectingArguments = false
val arguments = arrayListOf<String>()
var noReflect = false
var i = 0
while (i < args.size) {
val arg = args[i]
if (collectingArguments) {
arguments.add(arg)
i++
continue
}
fun next(): String {
if (++i == args.size) {
throw RunnerException("argument expected to $arg")
}
return args[i]
}
if ("-help" == arg || "-h" == arg) {
printUsageAndExit()
}
else if ("-version" == arg) {
printVersionAndExit()
}
else if ("-classpath" == arg || "-cp" == arg) {
for (path in next().split(File.pathSeparator).filter(String::isNotEmpty)) {
classpath.addPath(path)
}
}
else if ("-expression" == arg || "-e" == arg) {
runner = ExpressionRunner(next())
collectingArguments = true
}
else if ("-no-reflect" == arg) {
noReflect = true
}
else if (arg.startsWith("-")) {
throw RunnerException("unsupported argument: $arg")
}
else if (arg.endsWith(".jar")) {
runner = JarRunner(arg)
collectingArguments = true
}
else if (arg.endsWith(".kts")) {
runner = ScriptRunner(arg)
collectingArguments = true
}
else {
runner = MainClassRunner(arg)
collectingArguments = true
}
i++
}
if (classpath.isEmpty()) {
classpath.addPath(".")
}
classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-stdlib.jar")
if (!noReflect) {
classpath.addPath(KOTLIN_HOME.toString() + "/lib/kotlin-reflect.jar")
}
if (runner == null) {
runner = ReplRunner()
}
//将stdlib 库, reflect 库以及编译参数传入Runner 中
runner.run(classpath, arguments)
}
Runner 的主要工作是
abstract class AbstractRunner : Runner {
protected abstract val className: String
protected abstract fun createClassLoader(classpath: List<URL>): ClassLoader
override fun run(classpath: List<URL>, arguments: List<String>) {
val classLoader = createClassLoader(classpath)
val mainClass = try {
classLoader.loadClass(className)
}
catch (e: ClassNotFoundException) {
throw RunnerException("could not find or load main class $className")
}
val main = try {
mainClass.getDeclaredMethod("main", Array<String>::class.java)
}
catch (e: NoSuchMethodException) {
throw RunnerException("'main' method not found in class $className")
}
if (!Modifier.isStatic(main.modifiers)) {
throw RunnerException(
"'main' method of class $className is not static. " +
"Please ensure that 'main' is either a top level Kotlin function, " +
"a member function annotated with @JvmStatic, or a static Java method"
)
}
try {
main.invoke(null, arguments.toTypedArray())
}
catch (e: IllegalAccessException) {
throw RunnerException("'main' method of class $className is not public")
}
catch (e: InvocationTargetException) {
throw e.targetException
}
}
}
class MainClassRunner(override val className: String) : AbstractRunner() {
override fun createClassLoader(classpath: List<URL>): ClassLoader =
URLClassLoader(classpath.toTypedArray(), null)
}
接着来到真正的编译入口,在kotlin/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt,但是最早的入口是在 CLITools.kt 的companion object 中,这里会调用 Compile.exec() 方法
companion object {
/**
* Useful main for derived command line tools
*/
@JvmStatic
fun doMain(compiler: CLITool<*>, args: Array<String>) {
// We depend on swing (indirectly through PSI or something), so we want to declare headless mode,
// to avoid accidentally starting the UI thread
if (System.getProperty("java.awt.headless") == null) {
System.setProperty("java.awt.headless", "true")
}
if (System.getProperty(PlainTextMessageRenderer.KOTLIN_COLORS_ENABLED_PROPERTY) == null) {
System.setProperty(PlainTextMessageRenderer.KOTLIN_COLORS_ENABLED_PROPERTY, "true")
}
val exitCode = doMainNoExit(compiler, args)
if (exitCode != ExitCode.OK) {
System.exit(exitCode.code)
}
}
@JvmStatic
fun doMainNoExit(compiler: CLITool<*>, args: Array<String>): ExitCode = try {
compiler.exec(System.err, *args)
} catch (e: CompileEnvironmentException) {
System.err.println(e.message)
ExitCode.INTERNAL_ERROR
}
}
但是 exec() 方法只是检查编译参数,真是执行是在抽象方法 execImpl() 该方法交由CLITools子类实现
// Used in kotlin-maven-plugin (KotlinCompileMojoBase)
protected abstract fun execImpl(messageCollector: MessageCollector, services: Services, arguments: A): ExitCode
接着在CLICompile.java 中,看到了 execImpl()方法的实现,却发现,这里也不是真正的编译入口,只是封装了CompilerConfiguration 之类的控制参数,接着把参数传递给了 子类的doExecute() 方法:
ExitCode code = doExecute(arguments, configuration, rootDisposable, paths);
最后便是来到了 K2JVMCompile.kt 的 doExecute()方法中:
//K2JVMCompiler.kt
override fun doExecute(
arguments: K2JVMCompilerArguments,
configuration: CompilerConfiguration,
rootDisposable: Disposable,
paths: KotlinPaths?
): ExitCode {
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)configureJdkHome(arguments, configuration, messageCollector).let {
if (it != OK) return it
}
if (arguments.disableStandardScript) {
configuration.put(JVMConfigurationKeys.DISABLE_STANDARD_SCRIPT_DEFINITION, true)
}
val pluginLoadResult = loadPlugins(arguments, configuration)
if (pluginLoadResult != ExitCode.OK) return pluginLoadResult
val commonSources = arguments.commonSources?.toSet().orEmpty()
if (!arguments.script && arguments.buildFile == null) {
for (arg in arguments.freeArgs) {
val file = File(arg)
if (file.extension == JavaFileType.DEFAULT_EXTENSION) {
configuration.addJavaSourceRoot(file)
} else {
configuration.addKotlinSourceRoot(arg, isCommon = arg in commonSources)
if (file.isDirectory) {
configuration.addJavaSourceRoot(file)
}
}
}
}
configuration.put(CommonConfigurationKeys.MODULE_NAME, arguments.moduleName ?: JvmAbi.DEFAULT_MODULE_NAME)
if (arguments.buildFile == null) {
configureContentRoots(paths, arguments, configuration)
if (arguments.freeArgs.isEmpty() && !arguments.version) {
if (arguments.script) {
messageCollector.report(ERROR, "Specify script source path to evaluate")
return COMPILATION_ERROR
}
ReplFromTerminal.run(rootDisposable, configuration)
return ExitCode.OK
}
}
if (arguments.includeRuntime) {
configuration.put(JVMConfigurationKeys.INCLUDE_RUNTIME, true)
}
val friendPaths = arguments.friendPaths?.toList()
if (friendPaths != null) {
configuration.put(JVMConfigurationKeys.FRIEND_PATHS, friendPaths)
}
if (arguments.jvmTarget != null) {
val jvmTarget = JvmTarget.fromString(arguments.jvmTarget!!)
if (jvmTarget != null) {
configuration.put(JVMConfigurationKeys.JVM_TARGET, jvmTarget)
} else {
messageCollector.report(
ERROR, "Unknown JVM target version: ${arguments.jvmTarget}\n" +
"Supported versions: ${JvmTarget.values().joinToString { it.description }}"
)
}
}
configuration.put(JVMConfigurationKeys.PARAMETERS_METADATA, arguments.javaParameters)
putAdvancedOptions(configuration, arguments)
messageCollector.report(LOGGING, "Configuring the compilation environment")
try {
val destination = arguments.destination
if (arguments.buildFile != null) {
if (destination != null) {
messageCollector.report(
STRONG_WARNING,
"The '-d' option with a directory destination is ignored because '-Xbuild-file' is specified"
)
}
val sanitizedCollector = FilteringMessageCollector(messageCollector, VERBOSE::contains)
val buildFile = File(arguments.buildFile)
val moduleChunk = CompileEnvironmentUtil.loadModuleChunk(buildFile, sanitizedCollector)
configuration.put(JVMConfigurationKeys.MODULE_XML_FILE, buildFile)
KotlinToJVMBytecodeCompiler.configureSourceRoots(configuration, moduleChunk.modules, buildFile)
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
?: return COMPILATION_ERROR
registerJavacIfNeeded(environment, arguments).let {
if (!it) return COMPILATION_ERROR
}
KotlinToJVMBytecodeCompiler.compileModules(environment, buildFile, moduleChunk.modules)
} else if (arguments.script) {
val sourcePath = arguments.freeArgs.first()
configuration.addKotlinSourceRoot(sourcePath)
configuration.put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
?: return COMPILATION_ERROR
val scriptDefinitionProvider = ScriptDefinitionProvider.getInstance(environment.project)
val scriptFile = File(sourcePath)
if (scriptFile.isDirectory || !scriptDefinitionProvider.isScript(scriptFile.name)) {
val extensionHint =
if (configuration.get(JVMConfigurationKeys.SCRIPT_DEFINITIONS) == listOf(StandardScriptDefinition)) " (.kts)"
else ""
messageCollector.report(ERROR, "Specify path to the script file$extensionHint as the first argument")
return COMPILATION_ERROR
}
val scriptArgs = arguments.freeArgs.subList(1, arguments.freeArgs.size)
return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, scriptArgs)
} else {
if (destination != null) {
if (destination.endsWith(".jar")) {
configuration.put(JVMConfigurationKeys.OUTPUT_JAR, File(destination))
} else {
configuration.put(JVMConfigurationKeys.OUTPUT_DIRECTORY, File(destination))
}
}
val environment = createCoreEnvironment(rootDisposable, configuration, messageCollector)
?: return COMPILATION_ERROR
registerJavacIfNeeded(environment, arguments).let {
if (!it) return COMPILATION_ERROR
}
if (environment.getSourceFiles().isEmpty()) {
if (arguments.version) {
return OK
}
messageCollector.report(ERROR, "No source files")
return COMPILATION_ERROR
}
KotlinToJVMBytecodeCompiler.compileBunchOfSources(environment)
compileJavaFilesIfNeeded(environment, arguments).let {
if (!it) return COMPILATION_ERROR
}
}
return OK
} catch (e: CompilationException) {
messageCollector.report(
EXCEPTION,
OutputMessageUtil.renderException(e),
MessageUtil.psiElementToMessageLocation(e.element)
)
return INTERNAL_ERROR
}
}
这里的代码非常的长,非常的长,暂时不进行解释。
简单的可以分为以下四个步骤:
词法分析
这一步骤,将按照 使用词法分析器_JetLexer,将源代码文件分解成特定的语法词汇。
语法分析
这一步骤,是将第一步生成的特定语法词汇,生成语法树(ST)/抽象语法树(AST)
语义分析以及中间代码生成
这一步骤,主要是分析语树的属性,比如是否存在编译错误,就是在改阶段发现的。
分析过程会生成一些中间代码,这些中间代码是根据语义添加的。
目标代码生成