mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-03-16 16:46:07 +09:00
200 lines
6.5 KiB
Kotlin
200 lines
6.5 KiB
Kotlin
package net.torvald.terrarum
|
|
|
|
import java.util.*
|
|
|
|
/**
|
|
* Simplified version of YAML, only for the representation of a text tree.
|
|
*
|
|
* Example code:
|
|
* ```
|
|
* - File
|
|
* - New : Ctrl-N
|
|
* - Open : Ctrl-O
|
|
* - Open Recent
|
|
* - yaml_example.yaml
|
|
* - Yaml.kt
|
|
* - Close : Ctrl-W
|
|
* - Settings
|
|
* - Line Separators
|
|
* - CRLF
|
|
* - CR
|
|
* - LF
|
|
* - Edit
|
|
* - Undo : Ctrl-Z
|
|
* - Redo : Shift-Ctrl-Z
|
|
* - Cut : Ctrl-X
|
|
* - Copy : Ctrl-C
|
|
* - Paste : Ctrl-V
|
|
* - Find
|
|
* - Find : Ctrl-F
|
|
* - Replace : Shift-Ctrl-F
|
|
* - Convert Indents
|
|
* - To Spaces
|
|
* - Set Project Indentation
|
|
* - To Tabs
|
|
* - Refactor
|
|
* - Refactor This
|
|
* - Rename : Shift-Ctrl-R
|
|
* - Extract
|
|
* - Variable
|
|
* - Property
|
|
* - Function
|
|
* ```
|
|
*
|
|
* - All lines are indented with one space
|
|
* - All entries are preceded by '- ' (dash and a space)
|
|
* - All propery are separated by ' : ' (space colon space)
|
|
* - A line that does not start with '- ' are simply ignored, so you can freely make empty lines and/or comments.
|
|
*
|
|
* Any deviation to the above rule will cause a parse failure, because it's simple and dumb as that.
|
|
*
|
|
* Created by minjaesong on 2018-12-08.
|
|
*/
|
|
inline class Yaml(val text: String) {
|
|
|
|
companion object {
|
|
val SEPARATOR = Regex(" : ")
|
|
}
|
|
|
|
fun parse(): QNDTreeNode<String> {
|
|
var currentIndentLevel = -1
|
|
val root = QNDTreeNode<String>()
|
|
var currentNode = root
|
|
val nodesStack = Stack<QNDTreeNode<String>>()
|
|
val validLineStartRe = Regex(""" *\- """)
|
|
|
|
nodesStack.push(currentNode)
|
|
|
|
text.split('\n') .forEach {
|
|
if (validLineStartRe.containsMatchIn(it)) { // take partial match; do the task if the text's line is valid
|
|
val indentLevel = it.countSpaces()
|
|
val it = it.trimIndent()
|
|
if (it.startsWith("- ")) { // just double check if indent-trimmed line looks valid
|
|
val nodeName = it.drop(2)
|
|
|
|
if (indentLevel == currentIndentLevel) {
|
|
val sibling = QNDTreeNode(nodeName, currentNode.parent)
|
|
currentNode.parent!!.children.add(sibling)
|
|
currentNode = sibling
|
|
}
|
|
else if (indentLevel > currentIndentLevel) {
|
|
val childNode = QNDTreeNode(nodeName, currentNode)
|
|
currentNode.children.add(childNode)
|
|
nodesStack.push(currentNode)
|
|
currentNode = childNode
|
|
currentIndentLevel = indentLevel
|
|
}
|
|
else {
|
|
repeat(currentIndentLevel - indentLevel) { currentNode = nodesStack.pop() }
|
|
currentIndentLevel = indentLevel
|
|
val sibling = QNDTreeNode(nodeName, currentNode.parent)
|
|
currentNode.parent!!.children.add(sibling)
|
|
currentNode = sibling
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// test traverse resulting tree
|
|
/*root.traversePreorder { node, depth ->
|
|
repeat(depth + 1) { print("-") }
|
|
println("${node.data} -> ${node.parent}")
|
|
}*/
|
|
|
|
|
|
return root
|
|
}
|
|
|
|
fun parseAsYamlInvokable(): QNDTreeNode<Pair<String, YamlInvokable?>> {
|
|
var currentIndentLevel = -1
|
|
val root = QNDTreeNode<Pair<String, YamlInvokable?>>()
|
|
var currentNode = root
|
|
val nodesStack = Stack<QNDTreeNode<Pair<String, YamlInvokable?>>>()
|
|
val validLineStartRe = Regex(""" *\- """)
|
|
|
|
nodesStack.push(currentNode)
|
|
|
|
text.split('\n') .forEach {
|
|
if (validLineStartRe.containsMatchIn(it)) { // take partial match; do the task if the text's line is valid
|
|
val indentLevel = it.countSpaces()
|
|
val it = it.trimIndent()
|
|
if (it.startsWith("- ")) { // just double check if indent-trimmed line looks valid
|
|
val nodeString = it.drop(2)
|
|
val nodeNameAndInvocation = nodeString.split(SEPARATOR)
|
|
val nodeName = nodeNameAndInvocation[0]
|
|
val nodeInvocation = if (nodeNameAndInvocation.size == 2)
|
|
loadClass(nodeNameAndInvocation[1])
|
|
else
|
|
null
|
|
|
|
val nameInvokePair = nodeName to nodeInvocation
|
|
|
|
if (indentLevel == currentIndentLevel) {
|
|
val sibling = QNDTreeNode(nameInvokePair, currentNode.parent)
|
|
currentNode.parent!!.children.add(sibling)
|
|
currentNode = sibling
|
|
}
|
|
else if (indentLevel > currentIndentLevel) {
|
|
val childNode = QNDTreeNode(nameInvokePair, currentNode)
|
|
currentNode.children.add(childNode)
|
|
nodesStack.push(currentNode)
|
|
currentNode = childNode
|
|
currentIndentLevel = indentLevel
|
|
}
|
|
else {
|
|
repeat(currentIndentLevel - indentLevel) { currentNode = nodesStack.pop() }
|
|
currentIndentLevel = indentLevel
|
|
val sibling = QNDTreeNode(nameInvokePair, currentNode.parent)
|
|
currentNode.parent!!.children.add(sibling)
|
|
currentNode = sibling
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// test traverse resulting tree
|
|
/*root.traversePreorder { node, depth ->
|
|
repeat(depth + 1) { print("-") }
|
|
println("${node.data} -> ${node.parent}")
|
|
}*/
|
|
|
|
|
|
return root
|
|
}
|
|
|
|
private fun String.countSpaces(): Int {
|
|
var c = 0
|
|
while (c <= this.length) {
|
|
if (this[c] == ' ')
|
|
c++
|
|
else
|
|
break
|
|
}
|
|
|
|
return c
|
|
}
|
|
|
|
private fun loadClass(name: String): YamlInvokable {
|
|
val newClass = Class.forName(name)
|
|
val newClassConstructor = newClass.getConstructor(/* no args defined */)
|
|
val newClassInstance = newClassConstructor.newInstance(/* no args defined */)
|
|
return newClassInstance as YamlInvokable
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A simple interface that meant to be attached with Yaml tree, so that the entry can be ```invoke()```d.
|
|
*
|
|
* Example usage in Yaml:
|
|
* ```
|
|
* - File
|
|
* - Import : net.torvald.terrarum.whatever.package.ImportFile
|
|
* ```
|
|
*
|
|
*/
|
|
interface YamlInvokable {
|
|
operator fun invoke(args: Array<Any>)
|
|
} |