mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
feat(selector): 连接选择器新增元组语法
This commit is contained in:
parent
37804aee2b
commit
142c98a85c
|
@ -1,15 +1,15 @@
|
|||
package li.songe.selector
|
||||
|
||||
import kotlin.js.ExperimentalJsExport
|
||||
import kotlin.js.JsExport
|
||||
import kotlin.random.Random
|
||||
|
||||
internal interface NodeMatchFc {
|
||||
operator fun <T> invoke(node: T, transform: Transform<T>): Boolean
|
||||
}
|
||||
|
||||
internal interface NodeSequenceFc {
|
||||
operator fun <T> invoke(sequence: Sequence<T?>): Sequence<T?>
|
||||
interface NodeSequenceFc {
|
||||
operator fun <T> invoke(sq: Sequence<T?>): Sequence<T?>
|
||||
}
|
||||
|
||||
internal val emptyNodeSequence = object : NodeSequenceFc {
|
||||
override fun <T> invoke(sq: Sequence<T?>) = emptySequence<T?>()
|
||||
}
|
||||
|
||||
internal interface NodeTraversalFc {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package li.songe.selector.data
|
||||
|
||||
import li.songe.selector.NodeSequenceFc
|
||||
|
||||
sealed class ConnectExpression {
|
||||
abstract val isConstant: Boolean
|
||||
abstract val minOffset: Int
|
||||
|
||||
internal abstract val traversal: NodeSequenceFc
|
||||
}
|
|
@ -5,19 +5,19 @@ import li.songe.selector.NodeTraversalFc
|
|||
|
||||
data class ConnectSegment(
|
||||
val operator: ConnectOperator = ConnectOperator.Ancestor,
|
||||
val polynomialExpression: PolynomialExpression = PolynomialExpression()
|
||||
val connectExpression: ConnectExpression = PolynomialExpression(),
|
||||
) {
|
||||
override fun toString(): String {
|
||||
if (operator == ConnectOperator.Ancestor && polynomialExpression.a == 1 && polynomialExpression.b == 0) {
|
||||
if (operator == ConnectOperator.Ancestor && connectExpression is PolynomialExpression && connectExpression.a == 1 && connectExpression.b == 0) {
|
||||
return ""
|
||||
}
|
||||
return operator.toString() + polynomialExpression.toString()
|
||||
return operator.toString() + connectExpression.toString()
|
||||
}
|
||||
|
||||
internal val traversal = if (polynomialExpression.isConstant) {
|
||||
internal val traversal = if (connectExpression.isConstant) {
|
||||
object : NodeTraversalFc {
|
||||
override fun <T> invoke(node: T, transform: Transform<T>): Sequence<T?> = sequence {
|
||||
val node1 = operator.traversal(node, transform, polynomialExpression.b1)
|
||||
val node1 = operator.traversal(node, transform, connectExpression.minOffset)
|
||||
if (node1 != null) {
|
||||
yield(node1)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ data class ConnectSegment(
|
|||
} else {
|
||||
object : NodeTraversalFc {
|
||||
override fun <T> invoke(node: T, transform: Transform<T>): Sequence<T?> {
|
||||
return polynomialExpression.traversal(
|
||||
return connectExpression.traversal(
|
||||
operator.traversal(node, transform)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package li.songe.selector.data
|
||||
|
||||
import li.songe.selector.NodeSequenceFc
|
||||
import li.songe.selector.util.filterIndexes
|
||||
|
||||
/**
|
||||
* an+b
|
||||
*/
|
||||
data class PolynomialExpression(val a: Int = 0, val b: Int = 1) {
|
||||
data class PolynomialExpression(val a: Int = 0, val b: Int = 1) : ConnectExpression() {
|
||||
|
||||
override fun toString(): String {
|
||||
if (a == 0 && b == 0) return "0"
|
||||
|
@ -30,28 +31,50 @@ data class PolynomialExpression(val a: Int = 0, val b: Int = 1) {
|
|||
return "(${a}n${bOp}${b})"
|
||||
}
|
||||
|
||||
/**
|
||||
* [nth-child](https://developer.mozilla.org/zh-CN/docs/Web/CSS/:nth-child)
|
||||
*/
|
||||
val b1 = b - 1
|
||||
|
||||
internal val traversal = if (a <= 0 && b <= 0) {
|
||||
object : NodeSequenceFc {
|
||||
override fun <T> invoke(sequence: Sequence<T?>): Sequence<T?> {
|
||||
return emptySequence()
|
||||
val numbers = if (a < 0) {
|
||||
if (b < 0) {
|
||||
emptyList()
|
||||
} else if (b > 0) {
|
||||
if (b <= -a) {
|
||||
emptyList()
|
||||
} else {
|
||||
val list = mutableListOf<Int>()
|
||||
var n = 1
|
||||
while (a * n + b > 0) {
|
||||
list.add(a * n + b)
|
||||
n++
|
||||
}
|
||||
list.sorted()
|
||||
}
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
} else if (a > 0) {
|
||||
// infinite
|
||||
emptyList()
|
||||
} else {
|
||||
object : NodeSequenceFc {
|
||||
override fun <T> invoke(sequence: Sequence<T?>): Sequence<T?> {
|
||||
return sequence.filterIndexed { x, _ -> (x - b1) % a == 0 && (x - b1) / a > 0 }
|
||||
if (b < 0) {
|
||||
emptyList()
|
||||
} else if (b > 0) {
|
||||
listOf(b)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override val isConstant = numbers.size == 1
|
||||
override val minOffset = (numbers.firstOrNull() ?: 1) - 1
|
||||
private val b1 = b - 1
|
||||
private val indexes = numbers.map { x -> x - 1 }
|
||||
|
||||
override val traversal = object : NodeSequenceFc {
|
||||
override fun <T> invoke(sq: Sequence<T?>): Sequence<T?> {
|
||||
return if (a > 0) {
|
||||
sq.filterIndexed { x, _ -> (x - b1) % a == 0 && (x - b1) / a > 0 }
|
||||
} else {
|
||||
sq.filterIndexes(indexes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val isConstant = a == 0
|
||||
}
|
||||
|
||||
// 3n+1, 1,4,7
|
||||
// -n+9, 9,8,7,...,1
|
||||
// an+b=x, n=(x-b)/a
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package li.songe.selector.data
|
||||
|
||||
import li.songe.selector.NodeSequenceFc
|
||||
import li.songe.selector.util.filterIndexes
|
||||
|
||||
data class TupleExpression(
|
||||
val numbers: List<Int>,
|
||||
) : ConnectExpression() {
|
||||
override val isConstant = numbers.size == 1
|
||||
override val minOffset = (numbers.firstOrNull() ?: 1) - 1
|
||||
private val indexes = numbers.map { x -> x - 1 }
|
||||
override val traversal: NodeSequenceFc = object : NodeSequenceFc {
|
||||
override fun <T> invoke(sq: Sequence<T?>): Sequence<T?> {
|
||||
return sq.filterIndexes(indexes)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
if (numbers.size == 1) {
|
||||
return if (numbers.first() == 1) {
|
||||
""
|
||||
} else {
|
||||
numbers.first().toString()
|
||||
}
|
||||
}
|
||||
return "(${numbers.joinToString(",")})"
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import li.songe.selector.ExtSyntaxError
|
|||
import li.songe.selector.Selector
|
||||
import li.songe.selector.data.BinaryExpression
|
||||
import li.songe.selector.data.CompareOperator
|
||||
import li.songe.selector.data.ConnectExpression
|
||||
import li.songe.selector.data.ConnectOperator
|
||||
import li.songe.selector.data.ConnectSegment
|
||||
import li.songe.selector.data.ConnectWrapper
|
||||
|
@ -13,6 +14,7 @@ import li.songe.selector.data.LogicalOperator
|
|||
import li.songe.selector.data.PolynomialExpression
|
||||
import li.songe.selector.data.PropertySegment
|
||||
import li.songe.selector.data.PropertyWrapper
|
||||
import li.songe.selector.data.TupleExpression
|
||||
|
||||
internal object ParserSet {
|
||||
val whiteCharParser = Parser("\u0020\t\r\n") { source, offset, prefix ->
|
||||
|
@ -75,11 +77,17 @@ internal object ParserSet {
|
|||
s += source[i]
|
||||
i++
|
||||
}
|
||||
ParserResult(s.toInt(), i - offset)
|
||||
ParserResult(
|
||||
try {
|
||||
s.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
ExtSyntaxError.throwError(source, offset, "valid format number")
|
||||
}, i - offset
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// [+-][a][n[^b]]
|
||||
// [+-][a][n]
|
||||
val monomialParser = Parser("+-1234567890n") { source, offset, prefix ->
|
||||
var i = offset
|
||||
ExtSyntaxError.assert(source, i, prefix)
|
||||
|
@ -100,7 +108,7 @@ internal object ParserSet {
|
|||
else -> 1
|
||||
}
|
||||
i += whiteCharParser(source, i).length
|
||||
// [a][n[^b]]
|
||||
// [a][n]
|
||||
ExtSyntaxError.assert(source, i, integerParser.prefix + "n")
|
||||
val coefficient = if (integerParser.prefix.contains(source[i])) {
|
||||
val coefficientResult = integerParser(source, i)
|
||||
|
@ -109,23 +117,19 @@ internal object ParserSet {
|
|||
} else {
|
||||
1
|
||||
} * signal
|
||||
// [n[^b]]
|
||||
// [n]
|
||||
if (i < source.length && source[i] == 'n') {
|
||||
i++
|
||||
if (i < source.length && source[i] == '^') {
|
||||
i++
|
||||
val powerResult = integerParser(source, i)
|
||||
i += powerResult.length
|
||||
return@Parser ParserResult(Pair(powerResult.data, coefficient), i - offset)
|
||||
} else {
|
||||
return@Parser ParserResult(Pair(1, coefficient), i - offset)
|
||||
}
|
||||
// +-an
|
||||
return@Parser ParserResult(Pair(1, coefficient), i - offset)
|
||||
} else {
|
||||
// +-a
|
||||
return@Parser ParserResult(Pair(0, coefficient), i - offset)
|
||||
}
|
||||
}
|
||||
|
||||
// ([+-][a][n[^b]] [+-][a][n[^b]])
|
||||
|
||||
// (+-an+-b)
|
||||
val polynomialExpressionParser = Parser("(0123456789n") { source, offset, prefix ->
|
||||
var i = offset
|
||||
ExtSyntaxError.assert(source, i, prefix)
|
||||
|
@ -166,17 +170,69 @@ internal object ParserSet {
|
|||
ExtSyntaxError.throwError(source, offset, "power must be 0 or 1")
|
||||
}
|
||||
}
|
||||
ParserResult(PolynomialExpression(map[1] ?: 0, map[0] ?: 0), i - offset)
|
||||
val polynomialExpression = PolynomialExpression(map[1] ?: 0, map[0] ?: 0)
|
||||
polynomialExpression.apply {
|
||||
if ((a <= 0 && numbers.isEmpty()) || (numbers.isNotEmpty() && numbers.first() <= 0)) {
|
||||
ExtSyntaxError.throwError(source, offset, "valid polynomialExpression")
|
||||
}
|
||||
}
|
||||
ParserResult(polynomialExpression, i - offset)
|
||||
}
|
||||
|
||||
// [+-><](a*n^b)
|
||||
val tupleExpressionParser = Parser { source, offset, _ ->
|
||||
var i = offset
|
||||
ExtSyntaxError.assert(source, i, "(")
|
||||
i++
|
||||
val numbers = mutableListOf<Int>()
|
||||
while (i < source.length && source[i] != ')') {
|
||||
i += whiteCharParser(source, i).length
|
||||
val intResult = integerParser(source, i)
|
||||
if (numbers.isEmpty()) {
|
||||
if (intResult.data <= 0) {
|
||||
ExtSyntaxError.throwError(source, i, "positive integer")
|
||||
}
|
||||
} else {
|
||||
if (intResult.data <= numbers.last()) {
|
||||
ExtSyntaxError.throwError(source, i, ">" + numbers.last())
|
||||
}
|
||||
}
|
||||
i += intResult.length
|
||||
numbers.add(intResult.data)
|
||||
i += whiteCharParser(source, i).length
|
||||
if (source.getOrNull(i) == ',') {
|
||||
i++
|
||||
i += whiteCharParser(source, i).length
|
||||
// (1,2,3,) or (1, 2, 6)
|
||||
ExtSyntaxError.assert(source, i, integerParser.prefix + ")")
|
||||
}
|
||||
}
|
||||
ExtSyntaxError.assert(source, i, ")")
|
||||
i++
|
||||
ParserResult(TupleExpression(numbers), i - offset)
|
||||
}
|
||||
private val tupleExpressionReg = Regex("^\\(\\s*\\d+,.*$")
|
||||
val connectExpressionParser = Parser(polynomialExpressionParser.prefix) { source, offset, _ ->
|
||||
var i = offset
|
||||
if (tupleExpressionReg.matches(source.subSequence(offset, source.length))) {
|
||||
val tupleExpressionResult = tupleExpressionParser(source, i)
|
||||
i += tupleExpressionResult.length
|
||||
ParserResult(tupleExpressionResult.data, i - offset)
|
||||
} else {
|
||||
val polynomialExpressionResult = polynomialExpressionParser(source, offset)
|
||||
i += polynomialExpressionResult.length
|
||||
ParserResult(polynomialExpressionResult.data, i - offset)
|
||||
}
|
||||
}
|
||||
|
||||
// [+-><](a*n+b)
|
||||
// [+-><](1,2,3,4)
|
||||
val combinatorParser = Parser(combinatorOperatorParser.prefix) { source, offset, _ ->
|
||||
var i = offset
|
||||
val operatorResult = combinatorOperatorParser(source, i)
|
||||
i += operatorResult.length
|
||||
var expressionResult: ParserResult<PolynomialExpression>? = null
|
||||
if (i < source.length && polynomialExpressionParser.prefix.contains(source[i])) {
|
||||
expressionResult = polynomialExpressionParser(source, i)
|
||||
var expressionResult: ParserResult<ConnectExpression>? = null
|
||||
if (i < source.length && connectExpressionParser.prefix.contains(source[i])) {
|
||||
expressionResult = connectExpressionParser(source, i)
|
||||
i += expressionResult.length
|
||||
}
|
||||
ParserResult(
|
||||
|
@ -481,7 +537,7 @@ internal object ParserSet {
|
|||
i += whiteCharStrictParser(source, i).length
|
||||
combinatorResult.data
|
||||
} else {
|
||||
ConnectSegment(polynomialExpression = PolynomialExpression(1, 0))
|
||||
ConnectSegment(connectExpression = PolynomialExpression(1, 0))
|
||||
}
|
||||
val selectorResult = selectorUnitParser(source, i)
|
||||
i += selectorResult.length
|
||||
|
@ -492,7 +548,7 @@ internal object ParserSet {
|
|||
|
||||
val endParser = Parser { source, offset, _ ->
|
||||
if (offset != source.length) {
|
||||
ExtSyntaxError.throwError(source, offset, "end")
|
||||
ExtSyntaxError.throwError(source, offset, "EOF")
|
||||
}
|
||||
ParserResult(Unit, 0)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package li.songe.selector.util
|
||||
|
||||
internal class FilterIndexesSequence<T>(
|
||||
private val sequence: Sequence<T>,
|
||||
private val indexes: List<Int>,
|
||||
) : Sequence<T> {
|
||||
override fun iterator() = object : Iterator<T> {
|
||||
val iterator = sequence.iterator()
|
||||
var seqIndex = 0 // sequence
|
||||
var i = 0 // indexes
|
||||
var nextItem: T? = null
|
||||
|
||||
fun calcNext(): T? {
|
||||
if (seqIndex > indexes.last()) return null
|
||||
while (iterator.hasNext()) {
|
||||
val item = iterator.next()
|
||||
if (indexes[i] == seqIndex) {
|
||||
i++
|
||||
seqIndex++
|
||||
return item
|
||||
}
|
||||
seqIndex++
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun next(): T {
|
||||
val result = nextItem
|
||||
nextItem = null
|
||||
return result ?: calcNext() ?: throw NoSuchElementException()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
nextItem = nextItem ?: calcNext()
|
||||
return nextItem != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun <T> Sequence<T>.filterIndexes(indexes: List<Int>): Sequence<T> {
|
||||
return FilterIndexesSequence(this, indexes)
|
||||
}
|
Loading…
Reference in New Issue
Block a user