diff --git a/selector/src/commonMain/kotlin/li/songe/selector/NodeFc.kt b/selector/src/commonMain/kotlin/li/songe/selector/NodeFc.kt index d6f0dbadf..7b559b6f6 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/NodeFc.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/NodeFc.kt @@ -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 invoke(node: T, transform: Transform): Boolean } -internal interface NodeSequenceFc { - operator fun invoke(sequence: Sequence): Sequence +interface NodeSequenceFc { + operator fun invoke(sq: Sequence): Sequence +} + +internal val emptyNodeSequence = object : NodeSequenceFc { + override fun invoke(sq: Sequence) = emptySequence() } internal interface NodeTraversalFc { diff --git a/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectExpression.kt b/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectExpression.kt new file mode 100644 index 000000000..46f2669f5 --- /dev/null +++ b/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectExpression.kt @@ -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 +} diff --git a/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectSegment.kt b/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectSegment.kt index cbfaa5820..cb879ebba 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectSegment.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/data/ConnectSegment.kt @@ -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 invoke(node: T, transform: Transform): Sequence = 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 invoke(node: T, transform: Transform): Sequence { - return polynomialExpression.traversal( + return connectExpression.traversal( operator.traversal(node, transform) ) } diff --git a/selector/src/commonMain/kotlin/li/songe/selector/data/PolynomialExpression.kt b/selector/src/commonMain/kotlin/li/songe/selector/data/PolynomialExpression.kt index 25a3556ed..f50c690b0 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/data/PolynomialExpression.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/data/PolynomialExpression.kt @@ -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 invoke(sequence: Sequence): Sequence { - return emptySequence() + val numbers = if (a < 0) { + if (b < 0) { + emptyList() + } else if (b > 0) { + if (b <= -a) { + emptyList() + } else { + val list = mutableListOf() + 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 invoke(sequence: Sequence): Sequence { - 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 invoke(sq: Sequence): Sequence { + 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 diff --git a/selector/src/commonMain/kotlin/li/songe/selector/data/TupleExpression.kt b/selector/src/commonMain/kotlin/li/songe/selector/data/TupleExpression.kt new file mode 100644 index 000000000..342d2b7aa --- /dev/null +++ b/selector/src/commonMain/kotlin/li/songe/selector/data/TupleExpression.kt @@ -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, +) : 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 invoke(sq: Sequence): Sequence { + return sq.filterIndexes(indexes) + } + } + + override fun toString(): String { + if (numbers.size == 1) { + return if (numbers.first() == 1) { + "" + } else { + numbers.first().toString() + } + } + return "(${numbers.joinToString(",")})" + } +} diff --git a/selector/src/commonMain/kotlin/li/songe/selector/parser/ParserSet.kt b/selector/src/commonMain/kotlin/li/songe/selector/parser/ParserSet.kt index 310bd1429..fa1eefa78 100644 --- a/selector/src/commonMain/kotlin/li/songe/selector/parser/ParserSet.kt +++ b/selector/src/commonMain/kotlin/li/songe/selector/parser/ParserSet.kt @@ -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) + } + + val tupleExpressionParser = Parser { source, offset, _ -> + var i = offset + ExtSyntaxError.assert(source, i, "(") + i++ + val numbers = mutableListOf() + 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) + // [+-><](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? = null - if (i < source.length && polynomialExpressionParser.prefix.contains(source[i])) { - expressionResult = polynomialExpressionParser(source, i) + var expressionResult: ParserResult? = 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) } diff --git a/selector/src/commonMain/kotlin/li/songe/selector/util/FilterIndexesSequence.kt b/selector/src/commonMain/kotlin/li/songe/selector/util/FilterIndexesSequence.kt new file mode 100644 index 000000000..69c11e8ad --- /dev/null +++ b/selector/src/commonMain/kotlin/li/songe/selector/util/FilterIndexesSequence.kt @@ -0,0 +1,42 @@ +package li.songe.selector.util + +internal class FilterIndexesSequence( + private val sequence: Sequence, + private val indexes: List, +) : Sequence { + override fun iterator() = object : Iterator { + 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 Sequence.filterIndexes(indexes: List): Sequence { + return FilterIndexesSequence(this, indexes) +} \ No newline at end of file