Skip to content

Commit

Permalink
Recognize the language of the source file (C, C++, other) (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
0x6675636b796f75676974687562 authored Jul 7, 2023
1 parent 3998742 commit 7c61ee3
Show file tree
Hide file tree
Showing 7 changed files with 525 additions and 153 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.saveourtool.kompiledb.core.io.ParsedArgs
import com.saveourtool.kompiledb.core.io.PathMapper
import com.saveourtool.kompiledb.core.io.PathMapperScope.Companion.withPathMapper
import com.saveourtool.kompiledb.core.io.mappers.LocalPathMapper
import com.saveourtool.kompiledb.core.lang.Language
import java.nio.file.Path
import kotlin.collections.Map.Entry

Expand All @@ -21,6 +22,7 @@ import kotlin.collections.Map.Entry
* @param file the environment-specific path to the main translation unit source
* processed by this compilation step.
* @param compiler the environment-specific path to the compiler.
* @param language the language of the source file (C, C++, or other).
* @param includePaths the include paths.
* The keys are command-line switches such as `-I` or `-include`.
* The values are paths to include files or directories.
Expand All @@ -41,6 +43,7 @@ data class ParsedCompilerCommand(
val directory: EnvPath,
val file: EnvPath,
val compiler: EnvPath,
val language: Language,
val includePaths: Map<String, List<EnvPath>>,
val definedMacros: Map<String, String>,
val undefinedMacros: List<String>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,15 @@ import com.saveourtool.kompiledb.core.io.Arg
import com.saveourtool.kompiledb.core.io.CommandLineParser
import com.saveourtool.kompiledb.core.io.PathMapper
import com.saveourtool.kompiledb.core.io.PathMapperScope
import com.saveourtool.kompiledb.core.io.mappers.LocalPathMapper.toLocalPath
import com.saveourtool.kompiledb.core.lang.C
import com.saveourtool.kompiledb.core.lang.Cxx
import com.saveourtool.kompiledb.core.lang.Language
import com.saveourtool.kompiledb.core.lang.Language.Companion.UNKNOWN
import java.nio.file.NoSuchFileException
import java.nio.file.Path
import kotlin.Result.Companion.failure
import kotlin.io.path.extension
import kotlin.io.path.isDirectory
import kotlin.io.path.readText

Expand Down Expand Up @@ -83,19 +89,50 @@ internal class GccCommandParser(
val includePaths = mutableMapOf<String, MutableList<EnvPath>>()
val definedMacros = mutableMapOf<String, String>()
val undefinedMacros = mutableListOf<String>()
var language: Language? = null

val ignoredArguments = arguments.asSequence()
.drop(1)
.collectIncludePathsTo(includePaths)
.collectDefinedMacrosTo(definedMacros)
.collectUndefinedMacrosTo(undefinedMacros)
.collectOptionValues(options = INCLUDE_SWITCHES) { option, optionValue ->
/*
* `-I-` is just a separator and should be ignored.
*/
if (option + optionValue !in IGNORED_INCLUDE_SWITCHES) {
includePaths.compute(option) { _, value ->
(value ?: mutableListOf()).apply {
this@apply += EnvPath(optionValue)
}
}
}
}
.collectOptionValues(options = DEFINE_MACRO_SWITCHES) { _, optionValue ->
val (name, value) = optionValue.splitToNameAndValue()
definedMacros[name] = value
}
.collectOptionValues(options = UNDEFINE_MACRO_SWITCHES) { _, optionValue ->
undefinedMacros += optionValue
}
.collectOptionValues(options = LANGUAGE_SWITCHES) { _, optionValue ->
/*
* `-x none` has a special meaning.
*/
if (optionValue != "none") {
language = Language(optionValue)
}
}
.collectOptionValues("-o") { _, _ ->
/*
* Ignore `-o` and its argument.
*/
}
.toList()

return ParsedCompilerCommand(
projectRoot = projectRoot,
directory = command.directory,
file = command.file,
compiler = compiler,
language = language ?: command.file.language,
includePaths = includePaths,
definedMacros = definedMacros,
undefinedMacros = undefinedMacros,
Expand Down Expand Up @@ -131,6 +168,10 @@ internal class GccCommandParser(
this(resolved.readText().trimEnd()).asSequence()
}

private val EnvPath.language: Language
get() =
toLocalPath().getOrNull()?.language ?: UNKNOWN

private companion object {
/**
* Arguments which start with this character are gcc/clang response
Expand Down Expand Up @@ -165,25 +206,30 @@ internal class GccCommandParser(
"-U",
)

private val Arg.isResponseFile: Boolean
get() =
isNotEmpty() && this[0] == RESPONSE_FILE

private val Arg.isIncludeSwitch: Boolean
get() =
INCLUDE_SWITCHES.any(this::startsWith) && !isIgnoredIncludeSwitch
private val LANGUAGE_SWITCHES: Array<out String> = arrayOf(
"-x",
)

private val Arg.isIgnoredIncludeSwitch: Boolean
get() =
this in IGNORED_INCLUDE_SWITCHES
/**
* C extensions (not including the dot).
*/
private val C_EXTENSIONS: Array<out String> = arrayOf(
"c",
)

private val Arg.isDefineMacroSwitch: Boolean
get() =
DEFINE_MACRO_SWITCHES.any(this::startsWith)
/**
* C++ extensions (not including the dot).
*/
private val CXX_EXTENSIONS: Array<out String> = arrayOf(
"C",
"cc",
"cpp",
"cxx",
)

private val Arg.isUndefineMacroSwitch: Boolean
private val Arg.isResponseFile: Boolean
get() =
UNDEFINE_MACRO_SWITCHES.any(this::startsWith)
isNotEmpty() && this[0] == RESPONSE_FILE

private val Throwable.localizedMessageExt: String?
get() {
Expand All @@ -194,164 +240,73 @@ internal class GccCommandParser(
}
}

private fun Sequence<Arg>.collectIncludePathsTo(includePaths: MutableMap<String, MutableList<EnvPath>>): Sequence<Arg> =
sequence {
var lastIncludeSwitch: String? = null
val expectingIncludePath: () -> Boolean = {
lastIncludeSwitch != null
}

forEach { argument ->
when {
expectingIncludePath() -> {
includePaths.compute(lastIncludeSwitch!!) { _, value ->
(value ?: mutableListOf()).apply {
this += EnvPath(argument)
}
}
lastIncludeSwitch = null
}

/*
* -I
*/
argument.isIncludeSwitch -> {
val (switch, includePath) = argument.includePathOrNull()
when (includePath) {
null -> lastIncludeSwitch = switch
else -> {
includePaths.compute(switch) { _, value ->
(value ?: mutableListOf()).apply {
this += EnvPath(includePath)
}
}
}
}
}

/*
* Not an include path, pass through.
*/
else -> yield(argument)
}
private val Path.language: Language
get() =
when (val extension = extension) {
in C_EXTENSIONS -> C
in CXX_EXTENSIONS -> Cxx
"" -> UNKNOWN
else -> Language(extension)
}
}

private fun Sequence<Arg>.collectDefinedMacrosTo(definedMacros: MutableMap<String, String>): Sequence<Arg> =
private fun Sequence<Arg>.collectOptionValues(
vararg options: String,
consumeOptionAndValue: (String, String) -> Unit,
): Sequence<Arg> =
sequence {
var expectingDefinedMacro = false
fun Arg.isExpectedOption(): Boolean =
options.any(this::startsWith)

forEach { argument ->
when {
expectingDefinedMacro -> {
val (name, value) = argument.splitToNameAndValue()
definedMacros[name] = value
expectingDefinedMacro = false
/**
* @return the pair where the second value is the argument of
* the switch, or `null` if there's no argument and the next
* command-line argument should be examined instead.
*/
fun Arg.optionValueOrNull(): Pair<String, String?> =
options.asSequence()
.filter(this::startsWith)
.map { option ->
option to substring(option.length)
}

/*
* -D
*/
argument.isDefineMacroSwitch -> {
when (val definedMacro = argument.definedMacroOrNull()) {
null -> expectingDefinedMacro = true
else -> {
val (name, value) = definedMacro.splitToNameAndValue()
definedMacros[name] = value
}
}
.map { (option, value) ->
option to value.nullIfEmpty()
}
.first()

/*
* Not a `-D`, pass through.
*/
else -> yield(argument)
}
var lastOption: String? = null
val expectingOptionValue: () -> Boolean = {
lastOption != null
}
}

private fun Sequence<Arg>.collectUndefinedMacrosTo(undefinedMacros: MutableList<String>): Sequence<Arg> =
sequence {
var expectingUndefinedMacro = false

forEach { argument ->
forEach { arg ->
when {
expectingUndefinedMacro -> {
undefinedMacros += argument
expectingUndefinedMacro = false
/*
* The option value.
*/
expectingOptionValue() -> {
consumeOptionAndValue(lastOption!!, arg)
lastOption = null
}

/*
* -U
* The expected option.
*/
argument.isUndefineMacroSwitch -> {
when (val undefinedMacro = argument.undefinedMacroOrNull()) {
null -> expectingUndefinedMacro = true
else -> undefinedMacros += undefinedMacro
arg.isExpectedOption() -> {
val (option, value) = arg.optionValueOrNull()
when (value) {
null -> lastOption = option
else -> consumeOptionAndValue(option, value)
}
}

/*
* Not a `-U`, pass through.
* Not the the expected option, pass through.
*/
else -> yield(argument)
else -> yield(arg)
}
}
}

/**
* @return the pair where the second value is the argument of the `-I`
* switch, or `null` if there's no argument and the next command-line
* argument should be examined instead.
*/
private fun Arg.includePathOrNull(): Pair<String, String?> {
require(isIncludeSwitch) {
"Not an include (-I) switch: $this"
}

return INCLUDE_SWITCHES.asSequence()
.filter(this::startsWith)
.map { switch ->
switch to substring(switch.length)
}
.map { (switch, includePath) ->
switch to includePath.nullIfEmpty()
}
.first()
}

private fun Arg.definedMacroOrNull(): String? {
require(isDefineMacroSwitch) {
"Not a define macro (-D) switch: $this"
}

return DEFINE_MACRO_SWITCHES.asSequence()
.filter(this::startsWith)
.map { switch ->
substring(switch.length)
}
.map { definedMacro ->
definedMacro.nullIfEmpty()
}
.first()
}

private fun Arg.undefinedMacroOrNull(): String? {
require(isUndefineMacroSwitch) {
"Not an undefine macro (-U) switch: $this"
}

return UNDEFINE_MACRO_SWITCHES.asSequence()
.filter(this::startsWith)
.map { switch ->
substring(switch.length)
}
.map { undefinedMacro ->
undefinedMacro.nullIfEmpty()
}
.first()
}

/**
* - For both `DEBUG` and `DEBUG=1`, will return `("DEBUG", "1")`.
* - For `DEBUG=`, will return `("DEBUG", "")`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.saveourtool.kompiledb.core.lang

import com.saveourtool.kompiledb.core.lang.Language.Companion.LANG_C

/**
* The C language.
*
* @see Cxx
* @see Other
*/
object C : Language {
override val name = LANG_C

override fun toString(): String =
name
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.saveourtool.kompiledb.core.lang

import com.saveourtool.kompiledb.core.lang.Language.Companion.LANG_CXX

/**
* The C++ language.
*
* @see C
* @see Other
*/
object Cxx : Language {
override val name = LANG_CXX

override fun toString(): String =
name
}
Loading

0 comments on commit 7c61ee3

Please sign in to comment.