fun redirect(url: String, permanent: Boolean = false): Nothing = throw HttpRedirectException(url, permanent)
-context(ApplicationCall)
-inline fun <reified T : Any> redirectHref(resource: T, permanent: Boolean = false, hash: String? = null): Nothing = redirect(href(resource, hash), permanent)
+inline fun <reified T : Any> ApplicationCall.redirectHref(resource: T, permanent: Boolean = false, hash: String? = null): Nothing = redirect(href(resource, hash), permanent)
fun <T, C : TagConsumer<T>> C.unsafe(block: Unsafe.() -> Unit) = onTagContentUnsafe(block)
fun ParserTree.shouldSplitSections(): Boolean = firstOrNull()?.let {
- it is ParserTreeNode.Tag && it.tag.lowercase() == "h1"
+ it is ParserTreeNode.Tag && it isTag "h1"
} == true
fun ParserTree.splitSections(): List<ParserTree> = splitBefore {
- it is ParserTreeNode.Tag && it.tag.lowercase() == "h2"
+ it is ParserTreeNode.Tag && it isTag "h2"
}
fun ParserTreeNode.isWhitespace() = when (this) {
fun ParserTreeNode.isParagraph(inlineTags: Set<String>): Boolean = when (this) {
is ParserTreeNode.Text -> true
ParserTreeNode.LineBreak -> false
- is ParserTreeNode.Tag -> tag.lowercase() in inlineTags && subNodes.isParagraph(inlineTags)
+ is ParserTreeNode.Tag -> this isTag inlineTags && subNodes.isParagraph(inlineTags)
}
fun ParserTree.isParagraph(inlineTags: Set<String>): Boolean = any { it.isParagraph(inlineTags) }
ParserTreeNode.LineBreak -> " "
is ParserTreeNode.Tag -> it.subNodes.treeToText()
}
-}
+}.trim()
fun interface HtmlTextBodyLexerTag : HtmlLexerTag {
override fun processTag(env: LexerTagEnvironment<HtmlBuilderContext, HtmlBuilderSubject>, param: String?, subNodes: ParserTree): HtmlBuilderSubject {
--- /dev/null
+package info.mechyrdia.lore
+
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+
+data class AsyncLexerTagEnvironment<TContext, TSubject>(
+ val context: TContext,
+ private val processTags: AsyncLexerTags<TContext, TSubject>,
+ private val processText: AsyncLexerTextProcessor<TContext, TSubject>,
+ private val processBreak: AsyncLexerLineBreakProcessor<TContext, TSubject>,
+ private val processInvalidTag: AsyncLexerTagFallback<TContext, TSubject>,
+ private val combiner: AsyncLexerCombiner<TContext, TSubject>
+) {
+ suspend fun processTree(parserTree: ParserTree): TSubject {
+ return combiner.processAndCombine(this, parserTree)
+ }
+
+ suspend fun processNode(parserTreeNode: ParserTreeNode): TSubject {
+ return when (parserTreeNode) {
+ is ParserTreeNode.Text -> processText.processText(this, parserTreeNode.text)
+ ParserTreeNode.LineBreak -> processBreak.processLineBreak(this)
+ is ParserTreeNode.Tag -> processTags[parserTreeNode.tag]?.processTag(this, parserTreeNode.param, parserTreeNode.subNodes)
+ ?: processInvalidTag.processInvalidTag(this, parserTreeNode.tag, parserTreeNode.param, parserTreeNode.subNodes)
+ }
+ }
+}
+
+@JvmInline
+value class AsyncLexerTags<TContext, TSubject> private constructor(private val tags: Map<String, AsyncLexerTagProcessor<TContext, TSubject>>) {
+ operator fun get(name: String) = tags[name.lowercase()]
+
+ operator fun plus(other: AsyncLexerTags<TContext, TSubject>) = AsyncLexerTags(tags + other.tags)
+
+ companion object {
+ fun <TContext, TSubject> empty() = AsyncLexerTags<TContext, TSubject>(emptyMap())
+
+ operator fun <TContext, TSubject> invoke(tags: Map<String, AsyncLexerTagProcessor<TContext, TSubject>>) = AsyncLexerTags(tags.mapKeys { (name, _) -> name.lowercase() })
+ }
+}
+
+fun interface AsyncLexerTagProcessor<TContext, TSubject> {
+ suspend fun processTag(env: AsyncLexerTagEnvironment<TContext, TSubject>, param: String?, subNodes: ParserTree): TSubject
+}
+
+fun interface AsyncLexerTagFallback<TContext, TSubject> {
+ suspend fun processInvalidTag(env: AsyncLexerTagEnvironment<TContext, TSubject>, tag: String, param: String?, subNodes: ParserTree): TSubject
+}
+
+fun interface AsyncLexerTextProcessor<TContext, TSubject> {
+ suspend fun processText(env: AsyncLexerTagEnvironment<TContext, TSubject>, text: String): TSubject
+}
+
+fun interface AsyncLexerLineBreakProcessor<TContext, TSubject> {
+ suspend fun processLineBreak(env: AsyncLexerTagEnvironment<TContext, TSubject>): TSubject
+}
+
+fun interface AsyncLexerCombiner<TContext, TSubject> {
+ suspend fun processAndCombine(env: AsyncLexerTagEnvironment<TContext, TSubject>, nodes: ParserTree): TSubject {
+ return combine(env, coroutineScope {
+ nodes.map {
+ async { env.processNode(it) }
+ }.awaitAll()
+ })
+ }
+
+ suspend fun combine(env: AsyncLexerTagEnvironment<TContext, TSubject>, subjects: List<TSubject>): TSubject
+}
--- /dev/null
+package info.mechyrdia.lore
+
+import info.mechyrdia.JsonStorageCodec
+import io.ktor.server.application.*
+import io.ktor.server.request.*
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.coroutineScope
+import java.time.Instant
+
+class PreProcessingContext private constructor(
+ val variables: MutableMap<String, ParserTree>,
+ val parent: PreProcessingContext? = null,
+) {
+ constructor(name: String, value: String, parent: PreProcessingContext? = null) : this(mutableMapOf(name to value.textToTree()), parent)
+ constructor(parent: PreProcessingContext? = null, vararg variables: Pair<String, ParserTree>) : this(mutableMapOf(*variables), parent)
+
+ operator fun get(name: String): ParserTree = variables[name] ?: parent?.get(name) ?: "null".textToTree()
+
+ operator fun set(name: String, value: ParserTree) {
+ variables[name] = value
+ }
+
+ operator fun set(name: String, value: String) {
+ variables[name] = value.textToTree()
+ }
+
+ operator fun contains(name: String): Boolean = name in variables || (parent?.contains(name) == true)
+
+ operator fun plus(other: Map<String, ParserTree>) = PreProcessingContext(other.toMutableMap(), this)
+
+ fun toMap(): Map<String, ParserTree> = parent?.toMap().orEmpty() + variables
+
+ companion object {
+ operator fun invoke(variables: Map<String, ParserTree>, parent: PreProcessingContext? = null) = PreProcessingContext(variables.toMutableMap(), parent)
+
+ const val PAGE_PATH_KEY = "PAGE_PATH"
+ const val INSTANT_NOW_KEY = "INSTANT_NOW"
+
+ context(ApplicationCall)
+ fun defaults() = PreProcessingContext(
+ null,
+ PAGE_PATH_KEY to request.path().removePrefix("/").removePrefix("lore").textToTree(),
+ INSTANT_NOW_KEY to Instant.now().toEpochMilli().toString().textToTree(),
+ )
+ }
+}
+
+typealias PreProcessingSubject = ParserTree
+
+object PreProcessorUtils : AsyncLexerTagFallback<PreProcessingContext, PreProcessingSubject>, AsyncLexerTextProcessor<PreProcessingContext, PreProcessingSubject>, AsyncLexerLineBreakProcessor<PreProcessingContext, PreProcessingSubject>, AsyncLexerCombiner<PreProcessingContext, PreProcessingSubject> {
+ override suspend fun processInvalidTag(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, tag: String, param: String?, subNodes: ParserTree): PreProcessingSubject {
+ return listOf(
+ ParserTreeNode.Tag(
+ tag = tag,
+ param = param,
+ subNodes = env.processTree(subNodes)
+ )
+ )
+ }
+
+ override suspend fun processText(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, text: String): PreProcessingSubject {
+ return text.textToTree()
+ }
+
+ override suspend fun processLineBreak(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): PreProcessingSubject {
+ return listOf(ParserTreeNode.LineBreak)
+ }
+
+ override suspend fun combine(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, subjects: List<PreProcessingSubject>): PreProcessingSubject {
+ return subjects.flatten()
+ }
+
+ fun withContext(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, newContext: PreProcessingContext): AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject> {
+ return env.copy(context = newContext)
+ }
+
+ suspend fun processWithContext(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, newContext: PreProcessingContext, input: ParserTree): ParserTree {
+ return withContext(env, newContext).processTree(input)
+ }
+
+ fun indexTree(tree: ParserTree, index: List<String>): ParserTree {
+ if (index.isEmpty()) return tree
+ val tags = tree.filterIsInstance<ParserTreeNode.Tag>()
+ if (tags.isEmpty()) return emptyList()
+
+ val head = index.first()
+ val tail = index.drop(1)
+
+ val firstTag = tags.first()
+ return if (firstTag isTag "item" && firstTag.param == null) {
+ head.toIntOrNull()?.let { listIndex ->
+ tree.asPreProcessorList().getOrNull(listIndex)
+ }?.let { indexTree(it, tail) }.orEmpty()
+ } else if (firstTag isTag "arg" && firstTag.param != null) {
+ tree.asPreProcessorMap()[head]?.let { indexTree(it, tail) }.orEmpty()
+ } else emptyList()
+ }
+}
+
+fun interface PreProcessorLexerTag : AsyncLexerTagProcessor<PreProcessingContext, PreProcessingSubject>
+
+fun String.textToTree(): ParserTree = listOf(ParserTreeNode.Text(this))
+
+fun interface PreProcessorFunction {
+ suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree
+}
+
+fun interface PreProcessorFunctionProvider : PreProcessorLexerTag {
+ suspend fun provideFunction(param: String?): PreProcessorFunction?
+
+ override suspend fun processTag(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>, param: String?, subNodes: ParserTree): PreProcessingSubject {
+ val args = subNodes.asPreProcessorMap().mapValuesSuspend { _, value -> env.processTree(value) }
+ val ctx = PreProcessingContext(args, env.context)
+
+ val func = provideFunction(param) ?: return emptyList()
+ return func.execute(PreProcessorUtils.withContext(env, ctx))
+ }
+}
+
+abstract class PreProcessorFunctionLibrary : PreProcessorFunctionProvider {
+ abstract val functions: Map<String, PreProcessorFunction>
+
+ override suspend fun provideFunction(param: String?) = param?.let { functions[it] }
+
+ companion object {
+ operator fun invoke(library: Map<String, PreProcessorFunction>) = object : PreProcessorFunctionLibrary() {
+ override val functions: Map<String, PreProcessorFunction> = library
+ }
+ }
+}
+
+@JvmInline
+value class PreProcessorVariableFunction(private val variable: String) : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ return env.processTree(env.context[variable])
+ }
+}
+
+object PreProcessorVariableInvoker : PreProcessorFunctionProvider {
+ override suspend fun provideFunction(param: String?): PreProcessorFunction? {
+ return param?.let { PreProcessorVariableFunction(it) }
+ }
+}
+
+fun ParserTree.asPreProcessorList(): List<ParserTree> = mapNotNull {
+ if (it !is ParserTreeNode.Tag || it isNotTag "item" || it.param != null)
+ null
+ else
+ it.subNodes
+}
+
+fun ParserTree.asPreProcessorMap(): Map<String, ParserTree> = mapNotNull {
+ if (it !is ParserTreeNode.Tag || it isNotTag "arg" || it.param == null)
+ null
+ else
+ it.param to it.subNodes
+}.toMap()
+
+suspend fun <T, R> List<T>.mapSuspend(processor: suspend (T) -> R) = coroutineScope {
+ map {
+ async {
+ processor(it)
+ }
+ }.awaitAll()
+}
+
+suspend fun <K, V, R> Map<K, V>.mapValuesSuspend(processor: suspend (K, V) -> R) = coroutineScope {
+ map { (k, v) ->
+ async {
+ k to processor(k, v)
+ }
+ }.awaitAll()
+}.toMap()
+
+enum class PreProcessorTags(val type: PreProcessorLexerTag) {
+ EVAL(PreProcessorLexerTag { env, param, subNodes ->
+ param?.toIntOrNull()?.let { times ->
+ var tree = subNodes
+ repeat(times) {
+ tree = env.processTree(tree)
+ }
+ tree
+ } ?: env.processTree(subNodes)
+ }),
+ LAZY(PreProcessorLexerTag { _, _, subNodes ->
+ subNodes
+ }),
+ VAL(PreProcessorLexerTag { env, _, subNodes ->
+ env.processTree(subNodes).treeToText().textToTree()
+ }),
+ VAR(PreProcessorLexerTag { env, _, subNodes ->
+ val varName = env.processTree(subNodes).treeToText()
+ env.context[varName]
+ }),
+ ENV(PreProcessorVariableInvoker),
+ SET(PreProcessorLexerTag { env, param, subNodes ->
+ param?.let { varName ->
+ env.context[varName] = env.processTree(subNodes)
+ }
+
+ emptyList()
+ }),
+ INDEX(PreProcessorLexerTag { env, param, subNodes ->
+ val inputList = env.processTree(subNodes).asPreProcessorList()
+
+ (param?.toIntOrNull() ?: param?.let {
+ env.processTree(env.context[param]).treeToNumberOrNull(String::toIntOrNull)
+ })?.let { index ->
+ inputList.getOrNull(index)?.let { env.processTree(it) }
+ }.orEmpty()
+ }),
+ MEMBER(PreProcessorLexerTag { env, param, subNodes ->
+ PreProcessorUtils.indexTree(env.processTree(subNodes), param?.split('.').orEmpty())
+ }),
+ FOREACH(PreProcessorLexerTag { env, param, subNodes ->
+ param?.let { itemVar ->
+ val subTags = subNodes.filterIsInstance<ParserTreeNode.Tag>()
+ val list = subTags.singleOrNull { it isTag "in" }?.subNodes
+ ?.let { env.processTree(it) }
+ ?.asPreProcessorList()
+
+ val body = subTags.singleOrNull { it isTag "do" }?.subNodes
+ if (list != null && body != null)
+ list.mapSuspend { item ->
+ PreProcessorUtils.processWithContext(env, env.context + mapOf(itemVar to item), body)
+ }.flatten()
+ else null
+ }.orEmpty()
+ }),
+ MAP(PreProcessorLexerTag { env, param, subNodes ->
+ param?.let { itemVar ->
+ val subTags = subNodes.filterIsInstance<ParserTreeNode.Tag>()
+ val list = subTags.singleOrNull { it isTag "in" }?.subNodes
+ ?.let { env.processTree(it) }
+ ?.asPreProcessorList()
+
+ val body = subTags.singleOrNull { it isTag "do" }?.subNodes
+ if (list != null && body != null)
+ list.mapSuspend { item ->
+ ParserTreeNode.Tag("item", null, PreProcessorUtils.processWithContext(env, env.context + mapOf(itemVar to item), body))
+ }
+ else null
+ }.orEmpty()
+ }),
+ IF(PreProcessorLexerTag { env, param, subNodes ->
+ param?.let { boolVar ->
+ if (env.context[boolVar].treeToBooleanOrNull() == true)
+ env.processTree(subNodes)
+ else null
+ }.orEmpty()
+ }),
+ UNLESS(PreProcessorLexerTag { env, param, subNodes ->
+ param?.let { boolVar ->
+ if (env.context[boolVar].treeToBooleanOrNull() == false)
+ env.processTree(subNodes)
+ else null
+ }.orEmpty()
+ }),
+ MATH(PreProcessorMathOperators),
+ MATH_TEST(PreProcessorMathPredicate),
+ LOGIC(PreProcessorLogicOperator),
+ JSON_PARSE(PreProcessorLexerTag { _, _, subNodes ->
+ JsonStorageCodec.parseToJsonElement(subNodes.treeToText()).toPreProcessTree()
+ }),
+ JSON_STRINGIFY(PreProcessorLexerTag { env, _, subNodes ->
+ env.processTree(subNodes).toPreProcessJson().toString().textToTree()
+ }),
+}
--- /dev/null
+package info.mechyrdia.lore
+
+object PreProcessorScriptLoader {
+
+}
--- /dev/null
+package info.mechyrdia.lore
+
+import kotlinx.serialization.json.*
+
+fun JsonElement.toPreProcessTree(): ParserTree = when (this) {
+ JsonNull -> emptyList()
+
+ is JsonPrimitive -> if (isString)
+ ParserState.parseText(content)
+ else listOf(ParserTreeNode.Text(content))
+
+ is JsonArray -> map {
+ ParserTreeNode.Tag("item", null, it.toPreProcessTree())
+ }
+
+ is JsonObject -> map {
+ ParserTreeNode.Tag("arg", it.key, it.value.toPreProcessTree())
+ }
+}
+
+fun ParserTreeNode.unparse(): String = when (this) {
+ is ParserTreeNode.Text -> text
+ ParserTreeNode.LineBreak -> "\n\n"
+ is ParserTreeNode.Tag -> buildString {
+ append("[")
+ append(tag)
+ param?.let {
+ append("=")
+ append(it)
+ }
+ append("]")
+
+ append(subNodes.unparse())
+
+ append("[/")
+ append(tag)
+ append("]")
+ }
+}
+
+fun ParserTree.unparse() = joinToString(separator = "") { it.unparse() }
+
+fun ParserTree.toPreProcessJson(): JsonElement {
+ val noBlanks = filterNot { it.isWhitespace() }
+ return if (noBlanks.all { it is ParserTreeNode.Tag && it isTag "item" && it.param == null })
+ JsonArray(asPreProcessorList().map { it.toPreProcessJson() })
+ else if (noBlanks.all { it is ParserTreeNode.Tag && it isTag "arg" && it.param != null })
+ JsonObject(asPreProcessorMap().mapValues { (_, it) -> it.toPreProcessJson() })
+ else if (noBlanks.size == 1)
+ when (val node = noBlanks.single()) {
+ is ParserTreeNode.Text -> JsonPrimitive(node.text)
+ ParserTreeNode.LineBreak -> JsonPrimitive("\n\n")
+ is ParserTreeNode.Tag -> if (node isTag "val" && node.param == null) {
+ val value = node.subNodes.treeToText()
+ value.toBooleanStrictOrNull()?.let { JsonPrimitive(it) }
+ ?: value.toDoubleOrNull()?.let { JsonPrimitive(it) }
+ ?: JsonPrimitive(value)
+ } else JsonPrimitive(node.unparse())
+ }
+ else JsonPrimitive(unparse())
+}
--- /dev/null
+package info.mechyrdia.lore
+
+import kotlin.math.*
+
+fun <T : Number> ParserTree.treeToNumberOrNull(convert: String.() -> T?) = treeToText().convert()
+
+fun ParserTree.treeToBooleanOrNull() = when (treeToText().lowercase()) {
+ "true" -> true
+ "false" -> false
+ else -> null
+}
+
+fun Number.numberToTree(): ParserTree = listOf(ParserTreeNode.Tag("val", null, "%f".format(toDouble()).textToTree()))
+
+fun Boolean.booleanToTree(): ParserTree = listOf(ParserTreeNode.Tag("val", null, toString().textToTree()))
+
+object PreProcessorMathOperators : PreProcessorFunctionLibrary() {
+ override val functions: Map<String, PreProcessorFunction> = mapOf(
+ "neg" to PreProcessorMathUnaryOperator(Double::unaryMinus),
+ "sin" to PreProcessorMathUnaryOperator(::sin),
+ "cos" to PreProcessorMathUnaryOperator(::cos),
+ "tan" to PreProcessorMathUnaryOperator(::tan),
+ "asin" to PreProcessorMathUnaryOperator(::asin),
+ "acos" to PreProcessorMathUnaryOperator(::acos),
+ "atan" to PreProcessorMathUnaryOperator(::atan),
+ "sqrt" to PreProcessorMathUnaryOperator(::sqrt),
+ "cbrt" to PreProcessorMathUnaryOperator(::cbrt),
+ "ceil" to PreProcessorMathUnaryOperator(::ceil),
+ "floor" to PreProcessorMathUnaryOperator(::floor),
+ "trunc" to PreProcessorMathUnaryOperator(::truncate),
+ "round" to PreProcessorMathUnaryOperator(::round),
+
+ "add" to PreProcessorMathBinaryOperator(Double::plus),
+ "sub" to PreProcessorMathBinaryOperator(Double::minus),
+ "mul" to PreProcessorMathBinaryOperator(Double::times),
+ "div" to PreProcessorMathBinaryOperator(Double::div),
+ "mod" to PreProcessorMathBinaryOperator(Double::mod),
+ "pow" to PreProcessorMathBinaryOperator(Double::pow),
+ "log" to PreProcessorMathBinaryOperator(::log),
+ "min" to PreProcessorMathBinaryOperator(::min),
+ "max" to PreProcessorMathBinaryOperator(::max),
+ "hypot" to PreProcessorMathBinaryOperator(::hypot),
+ "atan2" to PreProcessorMathBinaryOperator(::atan2),
+
+ "min" to PreProcessorMathVariadicOperator(List<Double>::min),
+ "max" to PreProcessorMathVariadicOperator(List<Double>::max),
+ "sum" to PreProcessorMathVariadicOperator(List<Double>::sum),
+ "prod" to PreProcessorMathVariadicOperator { it.fold(1.0, Double::times) },
+ "mean" to PreProcessorMathVariadicOperator { it.sum() / it.size.coerceAtLeast(1) },
+ )
+}
+
+fun interface PreProcessorMathUnaryOperator : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val input = env.processTree(env.context["in"]).treeToNumberOrNull(String::toDoubleOrNull) ?: 0.0
+
+ return calculate(input).numberToTree()
+ }
+
+ fun calculate(input: Double): Double
+}
+
+fun interface PreProcessorMathBinaryOperator : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val left = env.processTree(env.context["left"]).treeToNumberOrNull(String::toDoubleOrNull) ?: 0.0
+ val right = env.processTree(env.context["right"]).treeToNumberOrNull(String::toDoubleOrNull) ?: 0.0
+
+ return calculate(left, right).numberToTree()
+ }
+
+ fun calculate(left: Double, right: Double): Double
+}
+
+fun interface PreProcessorMathVariadicOperator : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val args = env.processTree(env.context["in"]).asPreProcessorList().mapNotNull { it.treeToNumberOrNull(String::toDoubleOrNull) }
+
+ return calculate(args).numberToTree()
+ }
+
+ fun calculate(args: List<Double>): Double
+}
+
+fun interface PreProcessorMathPredicate : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val left = env.processTree(env.context["left"]).treeToNumberOrNull(String::toDoubleOrNull) ?: 0.0
+ val right = env.processTree(env.context["right"]).treeToNumberOrNull(String::toDoubleOrNull) ?: 0.0
+
+ return calculate(left, right).booleanToTree()
+ }
+
+ fun calculate(left: Double, right: Double): Boolean
+
+ companion object : PreProcessorFunctionLibrary() {
+ override val functions: Map<String, PreProcessorFunction> = mapOf(
+ "eq" to PreProcessorMathPredicate { left, right -> left == right },
+ "lt" to PreProcessorMathPredicate { left, right -> left < right },
+ "gt" to PreProcessorMathPredicate { left, right -> left > right },
+ "ne" to PreProcessorMathPredicate { left, right -> left != right },
+ "le" to PreProcessorMathPredicate { left, right -> left <= right },
+ "ge" to PreProcessorMathPredicate { left, right -> left >= right },
+ )
+ }
+}
+
+fun interface PreProcessorLogicBinaryOperator : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val left = env.processTree(env.context["left"]).treeToBooleanOrNull() == true
+ val right = env.processTree(env.context["right"]).treeToBooleanOrNull() == true
+
+ return calculate(left, right).booleanToTree()
+ }
+
+ fun calculate(left: Boolean, right: Boolean): Boolean
+}
+
+fun interface PreProcessorLogicOperator : PreProcessorFunction {
+ override suspend fun execute(env: AsyncLexerTagEnvironment<PreProcessingContext, PreProcessingSubject>): ParserTree {
+ val input = env.processTree(env.context["in"]).asPreProcessorList().mapNotNull { it.treeToBooleanOrNull() }
+
+ return calculate(input).booleanToTree()
+ }
+
+ fun calculate(inputs: List<Boolean>): Boolean
+
+ companion object : PreProcessorFunctionLibrary() {
+ override val functions: Map<String, PreProcessorFunction> = mapOf(
+ "not" to PreProcessorFunction { env ->
+ env.processTree(env.context["in"])
+ .treeToBooleanOrNull()
+ ?.let { "${!it}".textToTree() }
+ ?: emptyList()
+ },
+
+ "and" to PreProcessorLogicBinaryOperator { left, right -> left && right },
+ "or" to PreProcessorLogicBinaryOperator { left, right -> left || right },
+ "xor" to PreProcessorLogicBinaryOperator { left, right -> left xor right },
+ "nand" to PreProcessorLogicBinaryOperator { left, right -> !(left && right) },
+ "nor" to PreProcessorLogicBinaryOperator { left, right -> !(left || right) },
+ "xnor" to PreProcessorLogicBinaryOperator { left, right -> !(left xor right) },
+ "implies" to PreProcessorLogicBinaryOperator { left, right -> left || !right },
+
+ "all" to PreProcessorLogicOperator { inputs -> inputs.all { it } },
+ "any" to PreProcessorLogicOperator { inputs -> inputs.any { it } },
+ "notAll" to PreProcessorLogicOperator { inputs -> inputs.any { !it } },
+ "none" to PreProcessorLogicOperator { inputs -> inputs.none { it } },
+ "count" to PreProcessorFunction { env ->
+ env.processTree(env.context["in"])
+ .asPreProcessorList()
+ .mapNotNull { it.treeToBooleanOrNull() }
+ .count { it }
+ .toString()
+ .textToTree()
+ },
+ )
+ }
+}
data class Tag(val tag: String, val param: String?, val subNodes: ParserTree) : ParserTreeNode()
}
+infix fun ParserTreeNode.Tag.isTag(test: String) = tag.equals(test, ignoreCase = true)
+infix fun ParserTreeNode.Tag.isTag(test: Collection<String>) = test.any { tag.equals(it, ignoreCase = true) }
+
+infix fun ParserTreeNode.Tag.isNotTag(test: String) = !tag.equals(test, ignoreCase = true)
+infix fun ParserTreeNode.Tag.isNotTag(test: Collection<String>) = test.none { tag.equals(it, ignoreCase = true) }
+
typealias ParserTree = List<ParserTreeNode>
sealed class ParserTreeBuilderState {
val script = scriptFile.readText()
val digest = hasher.get().digest(script.toByteArray()).joinToString(separator = "") { it.toUByte().toString(16) }
- cache[digest]?.let { return it }
-
- val compiledScript = (scriptEngine.get() as Compilable).compile(script)
- cache[digest] = compiledScript
- return compiledScript
+ return cache.computeIfAbsent(digest) {
+ (scriptEngine.get() as Compilable).compile(script)
+ }
}
private fun runScript(scriptName: String, script: CompiledScript, input: Any?, args: MutableMap<String, Any?>, self: PebbleTemplate, context: EvaluationContext, lineNumber: Int): Any? {