mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-07 12:21:52 +09:00
new: #if(n)def preprocessor for Terrarum-formatted CSVs
This commit is contained in:
@@ -202,7 +202,9 @@ id;classname;tags
|
|||||||
#1048835;net.torvald.terrarum.modulebasegame.gameitems.ItemBucketIron03;FLUIDSTORAGE,OPENSTORAGE
|
#1048835;net.torvald.terrarum.modulebasegame.gameitems.ItemBucketIron03;FLUIDSTORAGE,OPENSTORAGE
|
||||||
#
|
#
|
||||||
## reserved for debug items
|
## reserved for debug items
|
||||||
##16777216;net.torvald.terrarum.modulebasegame.gameitems.ItemBottomlessWaterBucket;DEBUG,TOOL
|
#ifdef App.IS_DEVELOPMENT_BUILD
|
||||||
##16777217;net.torvald.terrarum.modulebasegame.gameitems.ItemBottomlessLavaBucket;DEBUG,TOOL
|
16777216;net.torvald.terrarum.modulebasegame.gameitems.ItemBottomlessWaterBucket;DEBUG,TOOL
|
||||||
#16777472;net.torvald.terrarum.modulebasegame.gameitems.ItemMysteriousATM;DEBUG
|
16777217;net.torvald.terrarum.modulebasegame.gameitems.ItemBottomlessLavaBucket;DEBUG,TOOL
|
||||||
#16777473;net.torvald.terrarum.modulebasegame.gameitems.ItemDebugInventron;DEBUG
|
16777472;net.torvald.terrarum.modulebasegame.gameitems.ItemMysteriousATM;DEBUG
|
||||||
|
16777473;net.torvald.terrarum.modulebasegame.gameitems.ItemDebugInventron;DEBUG
|
||||||
|
#endif
|
||||||
|
|||||||
|
@@ -3,12 +3,16 @@ package net.torvald.terrarum.utils
|
|||||||
import net.torvald.terrarum.App.printdbg
|
import net.torvald.terrarum.App.printdbg
|
||||||
import net.torvald.terrarum.ModMgr
|
import net.torvald.terrarum.ModMgr
|
||||||
import org.apache.commons.csv.CSVFormat
|
import org.apache.commons.csv.CSVFormat
|
||||||
|
import java.util.ArrayDeque
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by minjaesong on 2016-02-16.
|
* Created by minjaesong on 2016-02-16.
|
||||||
*/
|
*/
|
||||||
object CSVFetcher {
|
object CSVFetcher {
|
||||||
|
|
||||||
|
private const val DEFAULT_PACKAGE = "net.torvald.terrarum"
|
||||||
|
private val resolvedVariables = mutableMapOf<String, Boolean>()
|
||||||
|
|
||||||
val terrarumCSVFormat: CSVFormat = org.apache.commons.csv.CSVFormat.DEFAULT.withIgnoreSurroundingSpaces()
|
val terrarumCSVFormat: CSVFormat = org.apache.commons.csv.CSVFormat.DEFAULT.withIgnoreSurroundingSpaces()
|
||||||
.withHeader()
|
.withHeader()
|
||||||
.withIgnoreEmptyLines()
|
.withIgnoreEmptyLines()
|
||||||
@@ -21,12 +25,12 @@ object CSVFetcher {
|
|||||||
|
|
||||||
fun readFromFile(csvFilePath: String): List<org.apache.commons.csv.CSVRecord> {
|
fun readFromFile(csvFilePath: String): List<org.apache.commons.csv.CSVRecord> {
|
||||||
net.torvald.terrarum.utils.CSVFetcher.csvString = StringBuffer() // reset buffer every time it called
|
net.torvald.terrarum.utils.CSVFetcher.csvString = StringBuffer() // reset buffer every time it called
|
||||||
net.torvald.terrarum.utils.CSVFetcher.readCSVasString(csvFilePath)
|
val preprocessed = net.torvald.terrarum.utils.CSVFetcher.readCSVasString(csvFilePath)
|
||||||
|
|
||||||
printdbg(this, "Reading CSV $csvFilePath")
|
printdbg(this, "Reading CSV $csvFilePath")
|
||||||
|
|
||||||
val csvParser = org.apache.commons.csv.CSVParser.parse(
|
val csvParser = org.apache.commons.csv.CSVParser.parse(
|
||||||
net.torvald.terrarum.utils.CSVFetcher.csvString!!.toString(),
|
preprocessed,
|
||||||
terrarumCSVFormat
|
terrarumCSVFormat
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -39,8 +43,9 @@ object CSVFetcher {
|
|||||||
fun readFromModule(module: String, path: String) = net.torvald.terrarum.utils.CSVFetcher.readFromFile(ModMgr.getGdxFile(module, path).path())
|
fun readFromModule(module: String, path: String) = net.torvald.terrarum.utils.CSVFetcher.readFromFile(ModMgr.getGdxFile(module, path).path())
|
||||||
|
|
||||||
fun readFromString(csv: String): List<org.apache.commons.csv.CSVRecord> {
|
fun readFromString(csv: String): List<org.apache.commons.csv.CSVRecord> {
|
||||||
|
val preprocessed = preprocessCSV(csv)
|
||||||
val csvParser = org.apache.commons.csv.CSVParser.parse(
|
val csvParser = org.apache.commons.csv.CSVParser.parse(
|
||||||
csv,
|
preprocessed,
|
||||||
terrarumCSVFormat
|
terrarumCSVFormat
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,6 +62,155 @@ object CSVFetcher {
|
|||||||
s -> net.torvald.terrarum.utils.CSVFetcher.csvString!!.append("$s\n")
|
s -> net.torvald.terrarum.utils.CSVFetcher.csvString!!.append("$s\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
return net.torvald.terrarum.utils.CSVFetcher.csvString!!.toString()
|
return preprocessCSV(net.torvald.terrarum.utils.CSVFetcher.csvString!!.toString(), path)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preprocesses CSV content, handling #ifdef, #ifndef, #else, and #endif directives.
|
||||||
|
*
|
||||||
|
* Supported syntax:
|
||||||
|
* - #ifdef VARIABLE_NAME ... #endif
|
||||||
|
* - #ifdef VARIABLE_NAME ... #else ... #endif
|
||||||
|
* - #ifndef VARIABLE_NAME ... #endif
|
||||||
|
*
|
||||||
|
* Variables can be specified as:
|
||||||
|
* - Short form: App.IS_DEVELOPMENT_BUILD (resolved with net.torvald.terrarum prefix)
|
||||||
|
* - Fully qualified: net.torvald.terrarum.App.IS_DEVELOPMENT_BUILD
|
||||||
|
*
|
||||||
|
* @param content The raw CSV content to preprocess
|
||||||
|
* @param sourcePath Optional source path for error messages
|
||||||
|
* @return The preprocessed CSV content
|
||||||
|
*/
|
||||||
|
fun preprocessCSV(content: String, sourcePath: String = "<unknown>"): String {
|
||||||
|
val result = StringBuilder()
|
||||||
|
// Stack of pairs: (shouldIncludeInThisBlock, hasSeenElse)
|
||||||
|
val stateStack = ArrayDeque<Pair<Boolean, Boolean>>()
|
||||||
|
var lineNumber = 0
|
||||||
|
|
||||||
|
fun shouldInclude(): Boolean = stateStack.isEmpty() || stateStack.all { it.first }
|
||||||
|
|
||||||
|
for (line in content.lineSequence()) {
|
||||||
|
lineNumber++
|
||||||
|
val trimmed = line.trim()
|
||||||
|
|
||||||
|
when {
|
||||||
|
trimmed.startsWith("#ifdef ") -> {
|
||||||
|
val varName = trimmed.substring(7).trim()
|
||||||
|
if (varName.isEmpty()) {
|
||||||
|
printdbg(this, "Warning: Empty #ifdef at $sourcePath:$lineNumber")
|
||||||
|
stateStack.addLast(Pair(false, false))
|
||||||
|
} else {
|
||||||
|
val parentIncluding = shouldInclude()
|
||||||
|
val value = if (parentIncluding) resolveVariable(varName, sourcePath, lineNumber) else false
|
||||||
|
stateStack.addLast(Pair(parentIncluding && value, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed.startsWith("#ifndef ") -> {
|
||||||
|
val varName = trimmed.substring(8).trim()
|
||||||
|
if (varName.isEmpty()) {
|
||||||
|
printdbg(this, "Warning: Empty #ifndef at $sourcePath:$lineNumber")
|
||||||
|
stateStack.addLast(Pair(true, false))
|
||||||
|
} else {
|
||||||
|
val parentIncluding = shouldInclude()
|
||||||
|
val value = if (parentIncluding) resolveVariable(varName, sourcePath, lineNumber) else true
|
||||||
|
stateStack.addLast(Pair(parentIncluding && !value, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed == "#else" -> {
|
||||||
|
if (stateStack.isEmpty()) {
|
||||||
|
printdbg(this, "Warning: Unmatched #else at $sourcePath:$lineNumber")
|
||||||
|
} else {
|
||||||
|
val (wasIncluding, hasSeenElse) = stateStack.removeLast()
|
||||||
|
if (hasSeenElse) {
|
||||||
|
printdbg(this, "Warning: Duplicate #else at $sourcePath:$lineNumber")
|
||||||
|
stateStack.addLast(Pair(false, true))
|
||||||
|
} else {
|
||||||
|
// Check if parent blocks are including
|
||||||
|
val parentIncluding = stateStack.isEmpty() || stateStack.all { it.first }
|
||||||
|
// Invert the condition, but only if parent is including and we weren't including before
|
||||||
|
stateStack.addLast(Pair(parentIncluding && !wasIncluding, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trimmed == "#endif" -> {
|
||||||
|
if (stateStack.isEmpty()) {
|
||||||
|
printdbg(this, "Warning: Unmatched #endif at $sourcePath:$lineNumber")
|
||||||
|
} else {
|
||||||
|
stateStack.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular line (including plain # comments)
|
||||||
|
else -> {
|
||||||
|
if (shouldInclude()) {
|
||||||
|
result.append(line).append('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for unclosed directives
|
||||||
|
if (stateStack.isNotEmpty()) {
|
||||||
|
printdbg(this, "Warning: ${stateStack.size} unclosed #ifdef/#ifndef directive(s) in $sourcePath")
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resolveVariable(varSpec: String, sourcePath: String, lineNumber: Int): Boolean {
|
||||||
|
// Check cache first
|
||||||
|
resolvedVariables[varSpec]?.let { return it }
|
||||||
|
|
||||||
|
val result = try {
|
||||||
|
val (className, fieldName) = parseVariableSpec(varSpec)
|
||||||
|
val clazz = Class.forName(className)
|
||||||
|
val field = clazz.getDeclaredField(fieldName)
|
||||||
|
field.isAccessible = true
|
||||||
|
|
||||||
|
if (field.type != Boolean::class.javaPrimitiveType && field.type != Boolean::class.java) {
|
||||||
|
printdbg(this, "Warning: Variable '$varSpec' is not a boolean at $sourcePath:$lineNumber")
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
field.getBoolean(null) // null for static fields
|
||||||
|
}
|
||||||
|
} catch (e: ClassNotFoundException) {
|
||||||
|
printdbg(this, "Warning: Class not found for '$varSpec' at $sourcePath:$lineNumber")
|
||||||
|
false
|
||||||
|
} catch (e: NoSuchFieldException) {
|
||||||
|
printdbg(this, "Warning: Field not found for '$varSpec' at $sourcePath:$lineNumber")
|
||||||
|
false
|
||||||
|
} catch (e: IllegalAccessException) {
|
||||||
|
printdbg(this, "Warning: Cannot access '$varSpec' at $sourcePath:$lineNumber")
|
||||||
|
false
|
||||||
|
} catch (e: Exception) {
|
||||||
|
printdbg(this, "Warning: Error resolving '$varSpec' at $sourcePath:$lineNumber: ${e.message}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the result
|
||||||
|
resolvedVariables[varSpec] = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseVariableSpec(varSpec: String): Pair<String, String> {
|
||||||
|
val lastDot = varSpec.lastIndexOf('.')
|
||||||
|
if (lastDot == -1) {
|
||||||
|
throw IllegalArgumentException("Invalid variable spec: $varSpec (expected ClassName.fieldName)")
|
||||||
|
}
|
||||||
|
|
||||||
|
val classPath = varSpec.substring(0, lastDot)
|
||||||
|
val fieldName = varSpec.substring(lastDot + 1)
|
||||||
|
|
||||||
|
// Apply default package if class path doesn't contain a dot (simple class name)
|
||||||
|
val fullClassName = if (classPath.contains('.')) {
|
||||||
|
classPath
|
||||||
|
} else {
|
||||||
|
"$DEFAULT_PACKAGE.$classPath"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fullClassName to fieldName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user