mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 03:32:38 +08:00
perf: check type
This commit is contained in:
parent
4758448739
commit
ad394c5a84
|
@ -16,7 +16,6 @@ import kotlinx.coroutines.launch
|
||||||
import li.songe.gkd.composition.CompositionActivity
|
import li.songe.gkd.composition.CompositionActivity
|
||||||
import li.songe.gkd.composition.CompositionExt.useLifeCycleLog
|
import li.songe.gkd.composition.CompositionExt.useLifeCycleLog
|
||||||
import li.songe.gkd.service.ManageService
|
import li.songe.gkd.service.ManageService
|
||||||
import li.songe.gkd.service.updateLauncherAppId
|
|
||||||
import li.songe.gkd.ui.NavGraphs
|
import li.songe.gkd.ui.NavGraphs
|
||||||
import li.songe.gkd.ui.component.ConfirmDialog
|
import li.songe.gkd.ui.component.ConfirmDialog
|
||||||
import li.songe.gkd.ui.theme.AppTheme
|
import li.songe.gkd.ui.theme.AppTheme
|
||||||
|
@ -47,10 +46,8 @@ class MainActivity : CompositionActivity({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 每次打开页面更新记录桌面 appId
|
|
||||||
updateLauncherAppId()
|
|
||||||
|
|
||||||
ManageService.autoStart(this)
|
ManageService.autoStart(this)
|
||||||
|
mainVm
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
val navController = rememberNavController()
|
val navController = rememberNavController()
|
||||||
|
|
|
@ -3,10 +3,12 @@ package li.songe.gkd
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import li.songe.gkd.data.RawSubscription
|
||||||
import li.songe.gkd.data.SubsItem
|
import li.songe.gkd.data.SubsItem
|
||||||
import li.songe.gkd.db.DbSet
|
import li.songe.gkd.db.DbSet
|
||||||
|
import li.songe.gkd.service.updateLauncherAppId
|
||||||
import li.songe.gkd.util.authActionFlow
|
import li.songe.gkd.util.authActionFlow
|
||||||
import li.songe.gkd.util.checkUpdate
|
import li.songe.gkd.util.checkUpdate
|
||||||
import li.songe.gkd.util.initFolder
|
import li.songe.gkd.util.initFolder
|
||||||
|
@ -15,17 +17,42 @@ import li.songe.gkd.util.logZipDir
|
||||||
import li.songe.gkd.util.newVersionApkDir
|
import li.songe.gkd.util.newVersionApkDir
|
||||||
import li.songe.gkd.util.snapshotZipDir
|
import li.songe.gkd.util.snapshotZipDir
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
|
import li.songe.gkd.util.subsIdToRawFlow
|
||||||
|
import li.songe.gkd.util.subsItemsFlow
|
||||||
|
import li.songe.gkd.util.updateSubscription
|
||||||
|
|
||||||
class MainViewModel : ViewModel() {
|
class MainViewModel : ViewModel() {
|
||||||
init {
|
init {
|
||||||
appScope.launchTry(Dispatchers.IO) {
|
// 在某些机型由于未知原因创建失败
|
||||||
|
// 在此保证每次重新打开APP都能重新检测创建
|
||||||
|
initFolder()
|
||||||
|
|
||||||
|
// 每次打开页面更新记录桌面 appId
|
||||||
|
updateLauncherAppId()
|
||||||
|
|
||||||
val localSubsItem = SubsItem(
|
val localSubsItem = SubsItem(
|
||||||
id = -2, order = -2, mtime = System.currentTimeMillis()
|
id = -2, order = -2, mtime = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
if (!DbSet.subsItemDao.query().first().any { s -> s.id == localSubsItem.id }) {
|
viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
subsItemsFlow.debounce(1000).collect { subsItems ->
|
||||||
|
if (!subsItems.any { s -> s.id == localSubsItem.id }) {
|
||||||
DbSet.subsItemDao.insert(localSubsItem)
|
DbSet.subsItemDao.insert(localSubsItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
|
subsIdToRawFlow.debounce(1000).collect { subsIdToRaw ->
|
||||||
|
if (!subsIdToRaw.containsKey(localSubsItem.id)) {
|
||||||
|
updateSubscription(
|
||||||
|
RawSubscription(
|
||||||
|
id = localSubsItem.id,
|
||||||
|
name = "本地订阅",
|
||||||
|
version = 0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launchTry(Dispatchers.IO) {
|
viewModelScope.launchTry(Dispatchers.IO) {
|
||||||
// 每次进入删除缓存
|
// 每次进入删除缓存
|
||||||
|
@ -40,14 +67,8 @@ class MainViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launchTry(Dispatchers.IO) {
|
|
||||||
// 在某些机型由于未知原因创建失败
|
|
||||||
// 在此保证每次重新打开APP都能重新检测创建
|
|
||||||
initFolder()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeFlow.value.autoCheckAppUpdate) {
|
if (storeFlow.value.autoCheckAppUpdate) {
|
||||||
appScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
checkUpdate()
|
checkUpdate()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
|
@ -254,34 +254,7 @@ data class RawSubscription(
|
||||||
(apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) }
|
(apps ?: emptyList()).associate { a -> a.id to (a.enable ?: true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val allSelectorStrings by lazy {
|
val errorDesc by lazy { getErrorDesc() }
|
||||||
rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val allSelector by lazy {
|
|
||||||
allSelectorStrings.map { s -> Selector.parseOrNull(s) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val unknownPropertyNames by lazy {
|
|
||||||
allSelector.filterNotNull().flatMap { r -> r.propertyNames.toList() }.distinct()
|
|
||||||
.filterNot { n -> allowPropertyNames.contains(n) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val errorDesc by lazy {
|
|
||||||
allSelector.forEachIndexed { i, s ->
|
|
||||||
if (s == null) {
|
|
||||||
return@lazy "非法选择器:${allSelectorStrings[i]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unknownPropertyNames.forEach { n ->
|
|
||||||
return@lazy "非法属性名:${n}"
|
|
||||||
}
|
|
||||||
if (rules.any { r -> r.position?.isValid == false }) {
|
|
||||||
return@lazy "非法位置"
|
|
||||||
}
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val valid by lazy { errorDesc == null }
|
val valid by lazy { errorDesc == null }
|
||||||
|
|
||||||
|
@ -349,31 +322,7 @@ data class RawSubscription(
|
||||||
override val excludeVersionCodes: List<Long>?,
|
override val excludeVersionCodes: List<Long>?,
|
||||||
) : RawGroupProps, RawAppRuleProps {
|
) : RawGroupProps, RawAppRuleProps {
|
||||||
|
|
||||||
val allSelectorStrings by lazy {
|
val errorDesc by lazy { getErrorDesc() }
|
||||||
rules.map { r -> (r.matches ?: emptyList()) + (r.excludeMatches ?: emptyList()) }
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
val allSelector by lazy {
|
|
||||||
allSelectorStrings.map { s -> Selector.parseOrNull(s) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val unknownPropertyNames by lazy {
|
|
||||||
allSelector.filterNotNull().flatMap { r -> r.propertyNames.toList() }.distinct()
|
|
||||||
.filterNot { n -> allowPropertyNames.contains(n) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val errorDesc by lazy {
|
|
||||||
allSelector.forEachIndexed { i, s ->
|
|
||||||
if (s == null) {
|
|
||||||
return@lazy "非法选择器:${allSelectorStrings[i]}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unknownPropertyNames.forEach { n ->
|
|
||||||
return@lazy "非法属性名:${n}"
|
|
||||||
}
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
val valid by lazy { errorDesc == null }
|
val valid by lazy { errorDesc == null }
|
||||||
|
|
||||||
|
@ -419,6 +368,36 @@ data class RawSubscription(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
private fun RawGroupProps.getErrorDesc(): String? {
|
||||||
|
val allSelectorStrings =
|
||||||
|
rules.map { r -> (r.matches ?: emptyList()) + (r.excludeMatches ?: emptyList()) }
|
||||||
|
.flatten()
|
||||||
|
|
||||||
|
val allSelector = allSelectorStrings.map { s -> Selector.parseOrNull(s) }
|
||||||
|
|
||||||
|
allSelector.forEachIndexed { i, s ->
|
||||||
|
if (s == null) {
|
||||||
|
return "非法选择器:${allSelectorStrings[i]}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allSelector.forEach { s ->
|
||||||
|
s?.nameToTypeList?.forEach { (name, type) ->
|
||||||
|
if (!allowPropertyNames.contains(name)) {
|
||||||
|
return "非法属性名:${name}"
|
||||||
|
}
|
||||||
|
if (type != "null" && allowPropertyNames[name] != type) {
|
||||||
|
return "非法类型:${name}=$type"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rules.forEach { r ->
|
||||||
|
if (r.position?.isValid == false) {
|
||||||
|
return "非法位置:${r.position}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private val expVars = arrayOf(
|
private val expVars = arrayOf(
|
||||||
"left",
|
"left",
|
||||||
"top",
|
"top",
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.graphics.Rect
|
||||||
import android.view.accessibility.AccessibilityNodeInfo
|
import android.view.accessibility.AccessibilityNodeInfo
|
||||||
import li.songe.selector.Selector
|
import li.songe.selector.Selector
|
||||||
import li.songe.selector.Transform
|
import li.songe.selector.Transform
|
||||||
|
import li.songe.selector.data.PrimitiveValue
|
||||||
|
|
||||||
val AccessibilityService.safeActiveWindow: AccessibilityNodeInfo?
|
val AccessibilityService.safeActiveWindow: AccessibilityNodeInfo?
|
||||||
get() = try {
|
get() = try {
|
||||||
|
@ -128,35 +129,37 @@ val getChildren: (AccessibilityNodeInfo) -> Sequence<AccessibilityNodeInfo> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val allowPropertyNames = setOf(
|
val allowPropertyNames by lazy {
|
||||||
"id",
|
mapOf(
|
||||||
"vid",
|
"id" to PrimitiveValue.StringValue.type,
|
||||||
|
"vid" to PrimitiveValue.StringValue.type,
|
||||||
|
|
||||||
"name",
|
"name" to PrimitiveValue.StringValue.type,
|
||||||
"text",
|
"text" to PrimitiveValue.StringValue.type,
|
||||||
"text.length",
|
"text.length" to PrimitiveValue.IntValue.type,
|
||||||
"desc",
|
"desc" to PrimitiveValue.StringValue.type,
|
||||||
"desc.length",
|
"desc.length" to PrimitiveValue.IntValue.type,
|
||||||
|
|
||||||
"clickable",
|
"clickable" to PrimitiveValue.BooleanValue.type,
|
||||||
"focusable",
|
"focusable" to PrimitiveValue.BooleanValue.type,
|
||||||
"checkable",
|
"checkable" to PrimitiveValue.BooleanValue.type,
|
||||||
"checked",
|
"checked" to PrimitiveValue.BooleanValue.type,
|
||||||
"editable",
|
"editable" to PrimitiveValue.BooleanValue.type,
|
||||||
"longClickable",
|
"longClickable" to PrimitiveValue.BooleanValue.type,
|
||||||
"visibleToUser",
|
"visibleToUser" to PrimitiveValue.BooleanValue.type,
|
||||||
|
|
||||||
"left",
|
"left" to PrimitiveValue.IntValue.type,
|
||||||
"top",
|
"top" to PrimitiveValue.IntValue.type,
|
||||||
"right",
|
"right" to PrimitiveValue.IntValue.type,
|
||||||
"bottom",
|
"bottom" to PrimitiveValue.IntValue.type,
|
||||||
"width",
|
"width" to PrimitiveValue.IntValue.type,
|
||||||
"height",
|
"height" to PrimitiveValue.IntValue.type,
|
||||||
|
|
||||||
"index",
|
"index" to PrimitiveValue.IntValue.type,
|
||||||
"depth",
|
"depth" to PrimitiveValue.IntValue.type,
|
||||||
"childCount"
|
"childCount" to PrimitiveValue.IntValue.type,
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private val getAttr: (AccessibilityNodeInfo, String) -> Any? = { node, name ->
|
private val getAttr: (AccessibilityNodeInfo, String) -> Any? = { node, name ->
|
||||||
when (name) {
|
when (name) {
|
||||||
|
|
|
@ -251,14 +251,6 @@ fun initSubsState() {
|
||||||
subscriptions.forEach { s ->
|
subscriptions.forEach { s ->
|
||||||
newMap[s.id] = s
|
newMap[s.id] = s
|
||||||
}
|
}
|
||||||
if (newMap[-2] == null) {
|
|
||||||
newMap[-2] = RawSubscription(
|
|
||||||
id = -2,
|
|
||||||
name = "本地订阅",
|
|
||||||
version = 0,
|
|
||||||
author = "gkd",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
subsIdToRawFlow.value = newMap.toImmutableMap()
|
subsIdToRawFlow.value = newMap.toImmutableMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ package li.songe.selector
|
||||||
import kotlin.js.JsExport
|
import kotlin.js.JsExport
|
||||||
|
|
||||||
@JsExport
|
@JsExport
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED", "UNCHECKED_CAST")
|
||||||
class MultiplatformSelector private constructor(
|
class MultiplatformSelector private constructor(
|
||||||
internal val selector: Selector,
|
internal val selector: Selector,
|
||||||
) {
|
) {
|
||||||
|
@ -17,15 +17,12 @@ class MultiplatformSelector private constructor(
|
||||||
val qfTextValue = selector.qfTextValue
|
val qfTextValue = selector.qfTextValue
|
||||||
val canQf = selector.canQf
|
val canQf = selector.canQf
|
||||||
val isMatchRoot = selector.isMatchRoot
|
val isMatchRoot = selector.isMatchRoot
|
||||||
fun checkType(getType: (String) -> String): Boolean {
|
val nameToTypeList = selector.nameToTypeList
|
||||||
return selector.checkType(getType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T : Any> match(node: T, transform: MultiplatformTransform<T>): T? {
|
fun <T : Any> match(node: T, transform: MultiplatformTransform<T>): T? {
|
||||||
return selector.match(node, transform.transform)
|
return selector.match(node, transform.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
fun <T : Any> matchTrack(node: T, transform: MultiplatformTransform<T>): Array<T>? {
|
fun <T : Any> matchTrack(node: T, transform: MultiplatformTransform<T>): Array<T>? {
|
||||||
return selector.matchTracks(node, transform.transform)?.toTypedArray<Any?>() as Array<T>?
|
return selector.matchTracks(node, transform.transform)?.toTypedArray<Any?>() as Array<T>?
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,21 +99,11 @@ class Selector internal constructor(private val propertyWrapper: PropertyWrapper
|
||||||
binaryExpressions.map { e -> e.name }.distinct().toTypedArray()
|
binaryExpressions.map { e -> e.name }.distinct().toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkType(getType: (String) -> String): Boolean {
|
|
||||||
binaryExpressions.forEach { e ->
|
|
||||||
if (e.value.value != null) {
|
|
||||||
if (!(when (getType(e.name)) {
|
|
||||||
"boolean" -> e.value is PrimitiveValue.BooleanValue
|
|
||||||
"int" -> e.value is PrimitiveValue.IntValue
|
|
||||||
"string" -> e.value is PrimitiveValue.StringValue
|
|
||||||
else -> false
|
|
||||||
})
|
|
||||||
) return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// [name,type][]
|
||||||
|
val nameToTypeList = run {
|
||||||
|
binaryExpressions.map { e -> arrayOf(e.name, e.value.type) }.distinct().toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
val canCacheIndex =
|
val canCacheIndex =
|
||||||
connectKeys.contains(ConnectOperator.BeforeBrother.key) || connectKeys.contains(
|
connectKeys.contains(ConnectOperator.BeforeBrother.key) || connectKeys.contains(
|
||||||
|
|
|
@ -6,12 +6,12 @@ data class BinaryExpression(
|
||||||
val name: String,
|
val name: String,
|
||||||
val operator: CompareOperator,
|
val operator: CompareOperator,
|
||||||
val value: PrimitiveValue
|
val value: PrimitiveValue
|
||||||
) :
|
) : Expression() {
|
||||||
Expression() {
|
|
||||||
override fun <T> match(node: T, transform: Transform<T>) =
|
override fun <T> match(node: T, transform: Transform<T>) =
|
||||||
operator.compare(transform.getAttr(node, name), value.value)
|
operator.compare(transform.getAttr(node, name), value.value)
|
||||||
|
|
||||||
override val binaryExpressions = listOf(this)
|
override val binaryExpressions
|
||||||
|
get() = arrayOf(this)
|
||||||
|
|
||||||
override fun toString() = "${name}${operator.key}${value}"
|
override fun toString() = "${name}${operator.key}${value}"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ sealed class CompareOperator(val key: String) {
|
||||||
Equal,
|
Equal,
|
||||||
NotEqual,
|
NotEqual,
|
||||||
Start, NotStart, Include, NotInclude, End, NotEnd, Less, LessEqual, More, MoreEqual
|
Start, NotStart, Include, NotInclude, End, NotEnd, Less, LessEqual, More, MoreEqual
|
||||||
).sortedBy { -it.key.length }
|
).sortedBy { -it.key.length }.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
// example
|
// example
|
||||||
|
|
|
@ -3,7 +3,7 @@ package li.songe.selector.data
|
||||||
import li.songe.selector.Transform
|
import li.songe.selector.Transform
|
||||||
|
|
||||||
sealed class Expression {
|
sealed class Expression {
|
||||||
abstract fun <T> match(node: T, transform: Transform<T>): Boolean
|
internal abstract fun <T> match(node: T, transform: Transform<T>): Boolean
|
||||||
|
|
||||||
abstract val binaryExpressions: List<BinaryExpression>
|
abstract val binaryExpressions: Array<BinaryExpression>
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ data class LogicalExpression(
|
||||||
return operator.compare(node, transform, left, right)
|
return operator.compare(node, transform, left, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val binaryExpressions = left.binaryExpressions + right.binaryExpressions
|
override val binaryExpressions
|
||||||
|
get() = left.binaryExpressions + right.binaryExpressions
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val leftStr = if (left is LogicalExpression && left.operator != operator) {
|
val leftStr = if (left is LogicalExpression && left.operator != operator) {
|
||||||
|
|
|
@ -1,19 +1,31 @@
|
||||||
package li.songe.selector.data
|
package li.songe.selector.data
|
||||||
|
|
||||||
sealed class PrimitiveValue(open val value: Any?) {
|
sealed class PrimitiveValue(open val value: Any?, open val type: String) {
|
||||||
data object NullValue : PrimitiveValue(null) {
|
data object NullValue : PrimitiveValue(null, "null") {
|
||||||
override fun toString() = "null"
|
override fun toString() = "null"
|
||||||
}
|
}
|
||||||
|
|
||||||
data class BooleanValue(override val value: Boolean) : PrimitiveValue(value) {
|
data class BooleanValue(override val value: Boolean) : PrimitiveValue(value, type) {
|
||||||
override fun toString() = value.toString()
|
override fun toString() = value.toString()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val type = "boolean"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class IntValue(override val value: Int) : PrimitiveValue(value) {
|
data class IntValue(override val value: Int) : PrimitiveValue(value, type) {
|
||||||
override fun toString() = value.toString()
|
override fun toString() = value.toString()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val type = "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class StringValue(override val value: String) : PrimitiveValue(value, type) {
|
||||||
|
companion object {
|
||||||
|
const val type = "string"
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StringValue(override val value: String) : PrimitiveValue(value) {
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val wrapChar = '"'
|
val wrapChar = '"'
|
||||||
val sb = StringBuilder(value.length + 2)
|
val sb = StringBuilder(value.length + 2)
|
||||||
|
|
|
@ -13,7 +13,8 @@ data class PropertySegment(
|
||||||
) {
|
) {
|
||||||
private val matchAnyName = name.isBlank() || name == "*"
|
private val matchAnyName = name.isBlank() || name == "*"
|
||||||
|
|
||||||
val binaryExpressions = expressions.map { e -> e.binaryExpressions }.flatten()
|
val binaryExpressions
|
||||||
|
get() = expressions.flatMap { it.binaryExpressions.toList() }.toTypedArray()
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val matchTag = if (tracked) "@" else ""
|
val matchTag = if (tracked) "@" else ""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user