Optimize work with names and tokens

This commit is contained in:
Alexander Nozik 2022-08-22 14:16:16 +03:00
parent 70bd92f019
commit 233639f0b6
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
13 changed files with 72 additions and 47 deletions

View File

@ -6,7 +6,7 @@ plugins {
allprojects {
group = "space.kscience"
version = "0.6.0-dev-14"
version = "0.6.0-dev-15"
}
subprojects {

View File

@ -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)

View File

@ -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

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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("]"))
}
}
}

View File

@ -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))