feat(selector): 连接选择器新增元组语法

This commit is contained in:
lisonge 2023-11-03 18:19:54 +08:00
parent 37804aee2b
commit 142c98a85c
7 changed files with 209 additions and 50 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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