Skip to content

Commit

Permalink
feat(selector): 连接选择器新增元组语法
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Nov 3, 2023
1 parent 37804ae commit 142c98a
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 50 deletions.
12 changes: 6 additions & 6 deletions selector/src/commonMain/kotlin/li/songe/selector/NodeFc.kt
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Original file line number Diff line number Diff line change
@@ -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(",")})"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ->
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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<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)
// [+-><](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(
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 142c98a

Please sign in to comment.