Skip to content

Commit

Permalink
Initial implementation of ArrayOfTables
Browse files Browse the repository at this point in the history
### What's done:
- Small refactoring related to sealed classes limitations
- Initial test impplemetation of array of tables
- Kotlin update to 1.6.10
  • Loading branch information
orchestr7 committed Feb 10, 2022
1 parent 6e062c6 commit 78aaa0d
Show file tree
Hide file tree
Showing 33 changed files with 482 additions and 307 deletions.
4 changes: 2 additions & 2 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ repositories {

dependencies {
// this hack prevents the following bug: https://github.com/gradle/gradle/issues/9770
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")

implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.0-rc.3")
implementation("org.cqfn.diktat:diktat-gradle-plugin:1.0.2")
implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.15.0")
implementation("io.github.gradle-nexus:publish-plugin:1.1.0")
implementation("org.ajoberstar.reckon:reckon-gradle:0.13.0")
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/com/akuleshov7/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
// now it is just a workaround

object Versions {
const val KOTLIN = "1.6.0"
const val KOTLIN = "1.6.10"
const val JUNIT = "5.7.1"
const val OKIO = "3.0.0"
const val SERIALIZATION = "1.3.1"
const val SERIALIZATION = "1.3.2"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ fun Project.configureDiktat() {
apply<DiktatGradlePlugin>()
configure<DiktatExtension> {
diktatConfigFile = rootProject.file("diktat-analysis.yml")
inputs = files(
"src/**/*.kt"
)
excludes = files("src/commonTest")
inputs {
include("src/**/*.kt", "*.kts", "src/**/*.kts")
exclude("$projectDir/build/**")
}
}
}

Expand All @@ -33,10 +33,15 @@ fun Project.createDiktatTask() {
apply<DiktatGradlePlugin>()
configure<DiktatExtension> {
diktatConfigFile = rootProject.file("diktat-analysis.yml")
inputs = files(
"$rootDir/buildSrc/src/**/*.kt"
)
excludes = files("src/commonTest")
inputs {
include(
"$rootDir/buildSrc/src/**/*.kt",
"$rootDir/buildSrc/src/**/*.kts",
"$rootDir/*.kts",
"$rootDir/buildSrc/*.kts"
)
exclude("$rootDir/build", "$rootDir/buildSrc/build")
}
}
}
tasks.register("diktatCheckAll") {
Expand Down
7 changes: 5 additions & 2 deletions ktoml-core/src/commonMain/kotlin/com/akuleshov7/ktoml/Toml.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.akuleshov7.ktoml
import com.akuleshov7.ktoml.decoders.TomlMainDecoder
import com.akuleshov7.ktoml.exceptions.MissingRequiredPropertyException
import com.akuleshov7.ktoml.parsers.TomlParser
import com.akuleshov7.ktoml.tree.TableType
import com.akuleshov7.ktoml.tree.TomlFile
import com.akuleshov7.ktoml.writers.TomlWriter

Expand Down Expand Up @@ -89,7 +90,7 @@ public open class Toml(
tomlTableName: String,
config: TomlConfig = TomlConfig()
): T {
val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TomlParser::parseString)
val fakeFileNode = generateFakeTomlStructureForPartialParsing(toml, tomlTableName, config, TableType.PRIMITIVE, TomlParser::parseString)
return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config)
}

Expand Down Expand Up @@ -117,6 +118,7 @@ public open class Toml(
toml.joinToString("\n"),
tomlTableName,
config,
TableType.PRIMITIVE,
TomlParser::parseString,
)
return TomlMainDecoder.decode(deserializer, fakeFileNode, this.config)
Expand All @@ -128,10 +130,11 @@ public open class Toml(
toml: String,
tomlTableName: String,
config: TomlConfig = TomlConfig(),
type: TableType,
parsingFunction: (TomlParser, String) -> TomlFile
): TomlFile {
val parsedToml = parsingFunction(TomlParser(this.config), toml)
.findTableInAstByName(tomlTableName, tomlTableName.count { it == '.' } + 1)
.findTableInAstByName(tomlTableName, tomlTableName.count { it == '.' } + 1, type)
?: throw MissingRequiredPropertyException(
"Cannot find table with name <$tomlTableName> in the toml input. " +
"Not able to decode this toml part."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ public class TomlMainDecoder(
when (nextProcessingNode) {
is TomlKeyValueArray -> TomlArrayDecoder(nextProcessingNode, config)
is TomlKeyValuePrimitive, is TomlStubEmptyNode -> TomlMainDecoder(nextProcessingNode, config)
is TomlTable -> {
is TomlTablePrimitive -> {
val firstTableChild = nextProcessingNode.getFirstChild() ?: throw InternalDecodingException(
"Decoding process failed due to invalid structure of parsed AST tree: missing children" +
" in a table <${nextProcessingNode.fullTableName}>"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package com.akuleshov7.ktoml.parsers
import com.akuleshov7.ktoml.exceptions.ParseException

/**
* Splitting dot-separated string to tokens:
* Splitting dot-separated string to the list of tokens:
* a.b.c -> [a, b, c]; a."b.c".d -> [a, "b.c", d];
*
* @param lineNo - the line number in toml
Expand Down Expand Up @@ -74,6 +74,14 @@ internal fun String.trimQuotes(): String = trimSymbols(this, "\"", "\"")
*/
internal fun String.trimBrackets(): String = trimSymbols(this, "[", "]")

/**
* If this string starts and end with a pair brackets([[]]) - will return the string with brackets removed
* Otherwise, returns this string.
*
* @return string with the result
*/
internal fun String.trimDoubleBrackets(): String = trimSymbols(this, "[[", "]]")

private fun String.validateSpaces(lineNo: Int, fullKey: String) {
if (this.trim().count { it == ' ' } > 0 && this.isNotQuoted()) {
throw ParseException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ package com.akuleshov7.ktoml.parsers

import com.akuleshov7.ktoml.TomlConfig
import com.akuleshov7.ktoml.exceptions.InternalAstException
import com.akuleshov7.ktoml.tree.TomlFile
import com.akuleshov7.ktoml.tree.*
import com.akuleshov7.ktoml.tree.TomlKeyValue
import com.akuleshov7.ktoml.tree.TomlKeyValueArray
import com.akuleshov7.ktoml.tree.TomlKeyValuePrimitive
import com.akuleshov7.ktoml.tree.TomlNode
import com.akuleshov7.ktoml.tree.TomlStubEmptyNode
import com.akuleshov7.ktoml.tree.TomlTable
import com.akuleshov7.ktoml.tree.splitKeyValue
import kotlin.jvm.JvmInline

/**
Expand Down Expand Up @@ -39,27 +33,34 @@ public value class TomlParser(private val config: TomlConfig) {
* @throws InternalAstException - if toml node does not inherit TomlNode class
*/
public fun parseStringsToTomlTree(tomlLines: List<String>, config: TomlConfig): TomlFile {
var currentParent: TomlNode = TomlFile(config)
val tomlFileHead = currentParent as TomlFile
var currentParentalNode: TomlNode = TomlFile(config)
val tomlFileHead = currentParentalNode as TomlFile
// need to trim empty lines BEFORE the start of processing
val mutableTomlLines = tomlLines.toMutableList().trimEmptyLines()

mutableTomlLines.forEachIndexed { index, line ->
val lineNo = index + 1
// comments and empty lines can easily be ignored in the TomlTree, but we cannot filter them out in mutableTomlLines
// because we need to calculate and save lineNo
if (!line.isComment() && !line.isEmptyLine()) {
if (line.isTableNode()) {
val tableSection = TomlTable(line, lineNo, config)
// if the table is the last line in toml, than it has no children and we need to
// add at least fake node as a child
if (index == mutableTomlLines.lastIndex) {
tableSection.appendChild(TomlStubEmptyNode(lineNo, config))
}
// covering the case when processed table contains no key-value pairs or no tables (after our insertion)
// adding fake nodes to a previous table (it has no children because we have found another table right after)
if (currentParent.hasNoChildren()) {
currentParent.appendChild(TomlStubEmptyNode(currentParent.lineNo, config))
if (line.isArrayOfTables()) {
val tableArray = TomlArrayOfTables(line, lineNo, config)
currentParentalNode = tomlFileHead.insertTableToTree(tableArray, TableType.ARRAY)
} else {
val tableSection = TomlTablePrimitive(line, lineNo, config)
// if the table is the last line in toml, than it has no children and we need to
// add at least fake node as a child
if (index == mutableTomlLines.lastIndex) {
tableSection.appendChild(TomlStubEmptyNode(lineNo, config))
}
// covering the case when the processed table does not contain nor key-value pairs neither tables (after our insertion)
// adding fake nodes to a previous table (it has no children because we have found another table right after)
if (currentParentalNode.hasNoChildren()) {
currentParentalNode.appendChild(TomlStubEmptyNode(currentParentalNode.lineNo, config))
}
currentParentalNode = tomlFileHead.insertTableToTree(tableSection, TableType.PRIMITIVE)
}
currentParent = tomlFileHead.insertTableToTree(tableSection)
} else {
val keyValue = line.parseTomlKeyValue(lineNo, config)
if (keyValue !is TomlNode) {
Expand All @@ -70,13 +71,13 @@ public value class TomlParser(private val config: TomlConfig) {
if (keyValue.key.isDotted) {
// in case parser has faced dot-separated complex key (a.b.c) it should create proper table [a.b],
// because table is the same as dotted key
val newTableSection = keyValue.createTomlTableFromDottedKey(currentParent, config)
val newTableSection = keyValue.createTomlTableFromDottedKey(currentParentalNode, config)
tomlFileHead
.insertTableToTree(newTableSection)
.insertTableToTree(newTableSection, TableType.PRIMITIVE)
.appendChild(keyValue)
} else {
// otherwise it should simply append the keyValue to the parent
currentParent.appendChild(keyValue)
currentParentalNode.appendChild(keyValue)
}
}
}
Expand Down Expand Up @@ -109,6 +110,11 @@ public value class TomlParser(private val config: TomlConfig) {
}
}

private fun String.isArrayOfTables(): Boolean {
val trimmed = this.trim()
return trimmed.startsWith("[[") && trimmed.endsWith("]]")
}

private fun String.isTableNode(): Boolean {
val trimmed = this.trim()
return trimmed.startsWith("[") && trimmed.endsWith("]")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.akuleshov7.ktoml.tree

import com.akuleshov7.ktoml.TomlConfig
import com.akuleshov7.ktoml.exceptions.ParseException
import com.akuleshov7.ktoml.parsers.splitKeyToTokens
import com.akuleshov7.ktoml.parsers.trimDoubleBrackets
import com.akuleshov7.ktoml.parsers.trimQuotes

// FixMe: this class is mostly identical to the TomlTable - we should unify them together
public class TomlArrayOfTables(
content: String,
lineNo: Int,
config: TomlConfig = TomlConfig(),
public val isSynthetic: Boolean = false
) : TomlTable(content, lineNo, config) {
public override val type: TableType = TableType.ARRAY

// list of tables (including sub-tables) that are included in this table (e.g.: {a, a.b, a.b.c} in a.b.c)
public override lateinit var tablesList: List<String>

// full name of the table (like a.b.c.d)
public override lateinit var fullTableName: String

// short table name (only the name without parental prefix, like a - it is used in decoder and encoder)
override val name: String

internal val keyValues: MutableList<MutableList<TomlKeyValue>> = mutableListOf()

internal fun insertKeyValue(keyValue: TomlKeyValue, isNewElementInArray: Boolean) {
if (isNewElementInArray) {
// creating a new bucket for the array
keyValues.add(mutableListOf(keyValue))
} else {
// adding new keyValue to the last bucket (it should have been created on the previous step)
keyValues[keyValues.lastIndex].add(keyValue)
}
}

init {
// getting the content inside brackets ([a.b] -> a.b)
val sectionFromContent = content.trim().trimDoubleBrackets().trim()

if (sectionFromContent.isBlank()) {
throw ParseException("Incorrect blank name for array of tables: $content", lineNo)
}

fullTableName = sectionFromContent

val sectionsList = sectionFromContent.splitKeyToTokens(lineNo)
name = sectionsList.last().trimQuotes()
tablesList = sectionsList.mapIndexed { index, _ ->
(0..index).joinToString(".") { sectionsList[it] }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.akuleshov7.ktoml.tree

import com.akuleshov7.ktoml.TomlConfig
import com.akuleshov7.ktoml.exceptions.InternalAstException

/**
* A root node for TOML Abstract Syntax Tree
*/
public class TomlFile(config: TomlConfig = TomlConfig()) : TomlNode(
"rootNode",
0,
config) {
override val name: String = "rootNode"

override fun getNeighbourNodes(): MutableSet<TomlNode> =
throw InternalAstException("Invalid call to getNeighbourNodes() for TomlFile node")
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ internal interface TomlKeyValue {
* @param config
* @return the table that is parsed from a dotted key
*/
fun createTomlTableFromDottedKey(parentNode: TomlNode, config: TomlConfig = TomlConfig()): TomlTable {
fun createTomlTableFromDottedKey(parentNode: TomlNode, config: TomlConfig = TomlConfig()): TomlTablePrimitive {
// for a key: a.b.c it will be [a, b]
val syntheticTablePrefix = this.key.keyParts.dropLast(1)
// creating new key with the last dot-separated fragment
val realKeyWithoutDottedPrefix = TomlKey(key.content, lineNo)
// updating current KeyValue with this key
this.key = realKeyWithoutDottedPrefix
// tables should contain fully qualified name, so we need to add parental name
val parentalPrefix = if (parentNode is TomlTable) "${parentNode.fullTableName}." else ""
val parentalPrefix = if (parentNode is TomlTablePrimitive) "${parentNode.fullTableName}." else ""
// and creating a new table that will be created from dotted key
return TomlTable(
return TomlTablePrimitive(
"[$parentalPrefix${syntheticTablePrefix.joinToString(".")}]",
lineNo,
config,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.akuleshov7.ktoml.tree

import com.akuleshov7.ktoml.TomlConfig

/**
* Class for parsing and storing Array in AST. It receives a pair of two strings as an input and converts it to a pair
* of TomlKey and TomlValue (as TomlArray)
* @property lineNo
* @property key
* @property value
* @property name
*/
public class TomlKeyValueArray(
override var key: TomlKey,
override val value: TomlValue,
override val lineNo: Int,
override val name: String,
config: TomlConfig = TomlConfig()
) : TomlNode(key, value, lineNo, config), TomlKeyValue {
// adaptor for a string pair of key-value
public constructor(
keyValuePair: Pair<String, String>,
lineNo: Int,
config: TomlConfig = TomlConfig()
) : this(
TomlKey(keyValuePair.first, lineNo),
keyValuePair.second.parseList(lineNo, config),
lineNo,
TomlKey(keyValuePair.first, lineNo).content
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.akuleshov7.ktoml.tree

import com.akuleshov7.ktoml.TomlConfig

/**
* class for parsing and storing simple single value types in AST
* @property lineNo
* @property key
* @property value
* @property name
*/
public class TomlKeyValuePrimitive(
override var key: TomlKey,
override val value: TomlValue,
override val lineNo: Int,
override val name: String,
config: TomlConfig = TomlConfig()
) : TomlNode(key, value, lineNo, config), TomlKeyValue {
// adaptor for a string pair of key-value
public constructor(
keyValuePair: Pair<String, String>,
lineNo: Int,
config: TomlConfig = TomlConfig()
) : this(
TomlKey(keyValuePair.first, lineNo),
keyValuePair.second.parseValue(lineNo, config),
lineNo,
TomlKey(keyValuePair.first, lineNo).content
)
}

Loading

0 comments on commit 78aaa0d

Please sign in to comment.