Optimize work with names and tokens
This commit is contained in:
parent
70bd92f019
commit
233639f0b6
@ -6,7 +6,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.6.0-dev-14"
|
||||
version = "0.6.0-dev-15"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
|
@ -3,9 +3,8 @@ package space.kscience.dataforge.context
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.Job
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.Name.Companion.parse
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
|
||||
@ -22,4 +21,4 @@ private object GlobalContext : Context("GLOBAL".asName(), null, emptySet(), Meta
|
||||
public val Global: Context get() = GlobalContext
|
||||
|
||||
public fun Context(name: String? = null, block: ContextBuilder.() -> Unit = {}): Context =
|
||||
Global.buildContext(name?.let(Name::parse), block)
|
||||
Global.buildContext(name?.parseAsName(), block)
|
@ -6,6 +6,7 @@ import space.kscience.dataforge.meta.MetaRepr
|
||||
import space.kscience.dataforge.misc.Named
|
||||
import space.kscience.dataforge.misc.Type
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.provider.Provider
|
||||
|
||||
/**
|
||||
@ -30,7 +31,7 @@ public interface Plugin : Named, ContextAware, Provider, MetaRepr {
|
||||
/**
|
||||
* The name of this plugin ignoring version and group
|
||||
*/
|
||||
override val name: Name get() = Name.parse(tag.name)
|
||||
override val name: Name get() = tag.name.parseAsName()
|
||||
|
||||
/**
|
||||
* Plugin dependencies which are required to attach this plugin. Plugin
|
||||
|
@ -3,7 +3,7 @@ package space.kscience.dataforge.properties
|
||||
|
||||
import space.kscience.dataforge.meta.Scheme
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
|
||||
@ -18,7 +18,7 @@ public fun <S : Scheme, T : Any> S.property(property: KMutableProperty1<S, T?>):
|
||||
|
||||
override fun onChange(owner: Any?, callback: (T?) -> Unit) {
|
||||
this@property.meta.onChange(this) { name ->
|
||||
if (name.startsWith(Name.parse(property.name))) {
|
||||
if (name.startsWith(property.name.parseAsName(true))) {
|
||||
callback(property.get(this@property))
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@
|
||||
package space.kscience.dataforge.provider
|
||||
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import kotlin.jvm.JvmInline
|
||||
|
||||
/**
|
||||
@ -60,9 +61,10 @@ public data class PathToken(val name: Name, val target: String? = null) {
|
||||
|
||||
public companion object {
|
||||
public const val TARGET_SEPARATOR: String = "::"
|
||||
public fun parse(token: String): PathToken {
|
||||
|
||||
public fun parse(token: String, cache: Boolean = false): PathToken {
|
||||
val target = token.substringBefore(TARGET_SEPARATOR, "")
|
||||
val name = Name.parse(token.substringAfter(TARGET_SEPARATOR))
|
||||
val name = token.substringAfter(TARGET_SEPARATOR).parseAsName(cache)
|
||||
if (target.contains("[")) TODO("target separators in queries are not supported")
|
||||
return PathToken(name, target)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import space.kscience.dataforge.misc.DFBuilder
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.misc.DFInternal
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
|
||||
@ -96,7 +97,7 @@ internal class ReduceAction<T : Any, R : Any>(
|
||||
|
||||
val groupMeta = group.meta
|
||||
|
||||
val env = ActionEnv(Name.parse(groupName), groupMeta, meta)
|
||||
val env = ActionEnv(groupName.parseAsName(), groupMeta, meta)
|
||||
@OptIn(DFInternal::class) val res: Data<R> = dataFlow.reduceToData(
|
||||
group.outputType,
|
||||
meta = groupMeta
|
||||
|
@ -7,6 +7,7 @@ import space.kscience.dataforge.meta.MutableMeta
|
||||
import space.kscience.dataforge.meta.toMutableMeta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.typeOf
|
||||
@ -35,7 +36,7 @@ public class SplitBuilder<T : Any, R : Any>(public val name: Name, public val me
|
||||
* @param rule the rule to transform fragment name and meta using
|
||||
*/
|
||||
public fun fragment(name: String, rule: FragmentRule<T, R>.() -> Unit) {
|
||||
fragments[Name.parse(name)] = rule
|
||||
fragments[name.parseAsName()] = rule
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,7 @@ import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.isEmpty
|
||||
import space.kscience.dataforge.names.plus
|
||||
import space.kscience.dataforge.names.removeHeadOrNull
|
||||
import space.kscience.dataforge.names.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.reflect.KType
|
||||
@ -31,8 +28,8 @@ public fun <T : Any> DataSet<T>.filter(
|
||||
override val meta: Meta get() = this@filter.meta
|
||||
|
||||
override fun iterator(): Iterator<NamedData<T>> = iterator {
|
||||
for(d in this@filter){
|
||||
if(predicate(d.name, d.meta)){
|
||||
for (d in this@filter) {
|
||||
if (predicate(d.name, d.meta)) {
|
||||
yield(d)
|
||||
}
|
||||
}
|
||||
@ -64,7 +61,7 @@ public fun <T : Any> DataSet<T>.withNamePrefix(prefix: Name): DataSet<T> = if (p
|
||||
|
||||
|
||||
override fun iterator(): Iterator<NamedData<T>> = iterator {
|
||||
for(d in this@withNamePrefix){
|
||||
for (d in this@withNamePrefix) {
|
||||
yield(d.data.named(prefix + d.name))
|
||||
}
|
||||
}
|
||||
@ -89,7 +86,7 @@ public fun <T : Any> DataSet<T>.branch(branchName: Name): DataSet<T> = if (branc
|
||||
override val meta: Meta get() = this@branch.meta
|
||||
|
||||
override fun iterator(): Iterator<NamedData<T>> = iterator {
|
||||
for(d in this@branch){
|
||||
for (d in this@branch) {
|
||||
d.name.removeHeadOrNull(branchName)?.let { name ->
|
||||
yield(d.data.named(name))
|
||||
}
|
||||
@ -101,7 +98,7 @@ public fun <T : Any> DataSet<T>.branch(branchName: Name): DataSet<T> = if (branc
|
||||
override val updates: Flow<Name> get() = this@branch.updates.mapNotNull { it.removeHeadOrNull(branchName) }
|
||||
}
|
||||
|
||||
public fun <T : Any> DataSet<T>.branch(branchName: String): DataSet<T> = this@branch.branch(Name.parse(branchName))
|
||||
public fun <T : Any> DataSet<T>.branch(branchName: String): DataSet<T> = this@branch.branch(branchName.parseAsName())
|
||||
|
||||
@DFExperimental
|
||||
public suspend fun <T : Any> DataSet<T>.rootData(): Data<T>? = get(Name.EMPTY)
|
||||
|
@ -101,14 +101,12 @@ public operator fun Meta.get(token: NameToken): Meta? = items[token]
|
||||
*
|
||||
* If [name] is empty return current [Meta]
|
||||
*/
|
||||
public operator fun Meta.get(name: Name): Meta? = this.getMeta(name)
|
||||
|
||||
//TODO allow nullable receivers after Kotlin 1.7
|
||||
public operator fun Meta?.get(name: Name): Meta? = this?.getMeta(name)
|
||||
|
||||
/**
|
||||
* Parse [Name] from [key] using full name notation and pass it to [Meta.get]
|
||||
*/
|
||||
public operator fun Meta.get(key: String): Meta? = this[Name.parse(key)]
|
||||
public operator fun Meta?.get(key: String): Meta? = this?.get(key.parseAsName(true))
|
||||
|
||||
/**
|
||||
* Get all items matching given name. The index of the last element, if present is used as a [Regex],
|
||||
@ -134,7 +132,7 @@ public fun Meta.getIndexed(name: Name): Map<String?, Meta> {
|
||||
}
|
||||
}
|
||||
|
||||
public fun Meta.getIndexed(name: String): Map<String?, Meta> = getIndexed(name.parseAsName())
|
||||
public fun Meta.getIndexed(name: String): Map<String?, Meta> = getIndexed(name.parseAsName(true))
|
||||
|
||||
/**
|
||||
* A meta node that ensures that all of its descendants has at least the same type.
|
||||
@ -171,16 +169,16 @@ public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(token: NameToken): M? =
|
||||
*
|
||||
* If [name] is empty return current [Meta]
|
||||
*/
|
||||
public tailrec operator fun <M : TypedMeta<M>> TypedMeta<M>.get(name: Name): M? = if (name.isEmpty()) {
|
||||
self
|
||||
} else {
|
||||
get(name.firstOrNull()!!)?.get(name.cutFirst())
|
||||
public tailrec operator fun <M : TypedMeta<M>> TypedMeta<M>?.get(name: Name): M? = when {
|
||||
this == null -> null
|
||||
name.isEmpty() -> self
|
||||
else -> get(name.firstOrNull()!!)?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse [Name] from [key] using full name notation and pass it to [TypedMeta.get]
|
||||
*/
|
||||
public operator fun <M : TypedMeta<M>> TypedMeta<M>.get(key: String): M? = this[Name.parse(key)]
|
||||
public operator fun <M : TypedMeta<M>> TypedMeta<M>?.get(key: String): M? = this[key.parseAsName(true)]
|
||||
|
||||
|
||||
/**
|
||||
@ -223,7 +221,8 @@ public fun Meta.isEmpty(): Boolean = this === Meta.EMPTY
|
||||
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: Name): Map<String?, M> =
|
||||
(this as Meta).getIndexed(name) as Map<String?, M>
|
||||
|
||||
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: String): Map<String?, Meta> = getIndexed(Name.parse(name))
|
||||
public fun <M : TypedMeta<M>> TypedMeta<M>.getIndexed(name: String): Map<String?, Meta> =
|
||||
getIndexed(name.parseAsName(true))
|
||||
|
||||
|
||||
public val Meta?.string: String? get() = this?.value?.string
|
||||
|
@ -77,7 +77,7 @@ public operator fun MetaDescriptor.get(name: Name): MetaDescriptor? = when (name
|
||||
else -> get(name.firstOrNull()!!.asName())?.get(name.cutFirst())
|
||||
}
|
||||
|
||||
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(Name.parse(name))
|
||||
public operator fun MetaDescriptor.get(name: String): MetaDescriptor? = get(name.parseAsName(true))
|
||||
|
||||
public fun MetaDescriptor.validate(value: Value?): Boolean = if (value == null) {
|
||||
valueRequirement != ValueRequirement.REQUIRED
|
||||
|
@ -2,6 +2,7 @@ package space.kscience.dataforge.names
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import kotlin.native.concurrent.ThreadLocal
|
||||
|
||||
|
||||
/**
|
||||
@ -55,15 +56,15 @@ public class Name(public val tokens: List<NameToken>) {
|
||||
|
||||
/**
|
||||
* Convert a [String] to name parsing it and extracting name tokens and index syntax.
|
||||
* This operation is rather heavy so it should be used with care in high performance code.
|
||||
* This operation is rather heavy, so it should be used with care in high performance code.
|
||||
*/
|
||||
public fun parse(string: String): Name{
|
||||
if (string.isBlank()) return Name.EMPTY
|
||||
public fun parse(string: String): Name {
|
||||
if (string.isBlank()) return EMPTY
|
||||
val tokens = sequence {
|
||||
var bodyBuilder = StringBuilder()
|
||||
var queryBuilder = StringBuilder()
|
||||
var bracketCount: Int = 0
|
||||
var escape: Boolean = false
|
||||
var bracketCount = 0
|
||||
var escape = false
|
||||
fun queryOn() = bracketCount > 0
|
||||
|
||||
for (it in string) {
|
||||
@ -76,9 +77,11 @@ public class Name(public val tokens: List<NameToken>) {
|
||||
}
|
||||
escape = false
|
||||
}
|
||||
|
||||
it == '\\' -> {
|
||||
escape = true
|
||||
}
|
||||
|
||||
queryOn() -> {
|
||||
when (it) {
|
||||
'[' -> bracketCount++
|
||||
@ -86,6 +89,7 @@ public class Name(public val tokens: List<NameToken>) {
|
||||
}
|
||||
if (queryOn()) queryBuilder.append(it)
|
||||
}
|
||||
|
||||
else -> when (it) {
|
||||
'.' -> {
|
||||
val query = if (queryBuilder.isEmpty()) null else queryBuilder.toString()
|
||||
@ -93,6 +97,7 @@ public class Name(public val tokens: List<NameToken>) {
|
||||
bodyBuilder = StringBuilder()
|
||||
queryBuilder = StringBuilder()
|
||||
}
|
||||
|
||||
'[' -> bracketCount++
|
||||
']' -> error("Syntax error: closing bracket ] not have not matching open bracket")
|
||||
else -> {
|
||||
@ -203,4 +208,14 @@ public fun Name.removeHeadOrNull(head: Name): Name? = if (startsWith(head)) {
|
||||
null
|
||||
}
|
||||
|
||||
public fun String.parseAsName(): Name = Name.parse(this)
|
||||
@ThreadLocal
|
||||
private val nameCache = HashMap<String, Name>()
|
||||
|
||||
/**
|
||||
* Parse a string as name. If [cache], use global cache for parsed name to avoid multiple parsings.
|
||||
*/
|
||||
public fun String.parseAsName(cache: Boolean = false): Name = if (cache) {
|
||||
nameCache.getOrPut(this) { Name.parse(this) }
|
||||
} else {
|
||||
Name.parse(this)
|
||||
}
|
@ -15,16 +15,22 @@ public data class NameToken(val body: String, val index: String? = null) {
|
||||
if (body.isEmpty()) error("Syntax error: Name token body is empty")
|
||||
}
|
||||
|
||||
private fun String.escape() =
|
||||
replace("\\", "\\\\")
|
||||
.replace(".", "\\.")
|
||||
.replace("[", "\\[")
|
||||
.replace("]", "\\]")
|
||||
private val bodyEscaped by lazy {
|
||||
val escaped = buildString {
|
||||
body.forEach {
|
||||
if (it in escapedChars) {
|
||||
append('\\')
|
||||
}
|
||||
append(it)
|
||||
}
|
||||
}
|
||||
if (escaped == body) body else escaped
|
||||
}
|
||||
|
||||
override fun toString(): String = if (hasIndex()) {
|
||||
"${body.escape()}[$index]"
|
||||
"${bodyEscaped}[$index]"
|
||||
} else {
|
||||
body.escape()
|
||||
bodyEscaped
|
||||
}
|
||||
|
||||
/**
|
||||
@ -39,6 +45,8 @@ public data class NameToken(val body: String, val index: String? = null) {
|
||||
|
||||
public companion object {
|
||||
|
||||
private val escapedChars = listOf('\\', '.', '[', ']')
|
||||
|
||||
/**
|
||||
* Parse name token from a string
|
||||
*/
|
||||
@ -47,7 +55,7 @@ public data class NameToken(val body: String, val index: String? = null) {
|
||||
val body = string.substringBefore('[')
|
||||
val index = string.substringAfter('[', "")
|
||||
if (index.isNotEmpty() && index.endsWith(']')) error("NameToken with index must end with ']'")
|
||||
return NameToken(body,index.removeSuffix("]"))
|
||||
return NameToken(body, index.removeSuffix("]"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,12 @@ public fun Name.matches(pattern: Name): Boolean = when {
|
||||
length >= pattern.length
|
||||
&& Name(tokens.subList(0, pattern.length - 1)).matches(pattern.cutLast())
|
||||
}
|
||||
|
||||
pattern.startsWith(Name.MATCH_ALL_TOKEN) -> {
|
||||
length >= pattern.length
|
||||
&& Name(tokens.subList(tokens.size - pattern.length + 1, tokens.size)).matches(pattern.cutFirst())
|
||||
}
|
||||
|
||||
else -> {
|
||||
tokens.indices.forEach {
|
||||
val thisToken = tokens.getOrNull(it) ?: return false
|
||||
@ -44,4 +46,4 @@ public fun Name.matches(pattern: Name): Boolean = when {
|
||||
}
|
||||
|
||||
@OptIn(DFExperimental::class)
|
||||
public fun Name.matches(pattern: String): Boolean = matches(Name.parse(pattern))
|
||||
public fun Name.matches(pattern: String): Boolean = matches(pattern.parseAsName(true))
|
Loading…
Reference in New Issue
Block a user