Skip to content

Commit 5464575

Browse files
committed
Re-factor repetition production class to use an optional separator
Addition of a `separator` parameter allows arguably a neater taxonomy of production classes, with `CommaSeparatedRepetitionProduction` now extending `RepetitionProduction` and using a separator value. This also will allow expressing repetition productions that employ arbitrary separators. It also reduces amount of code required for expressing productions of both kinds and then some.
1 parent b930beb commit 5464575

2 files changed

Lines changed: 23 additions & 34 deletions

File tree

src/csspring/selectors.py

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -67,29 +67,6 @@ def parse_any_value(input: TokenStream) -> Product | None:
6767
else:
6868
return None
6969

70-
@parse.register
71-
def _(production: CommaSeparatedRepetitionProduction, input: TokenStream) -> Product | None:
72-
"""Variant of `parse` for productions of the `#` multiplier variety (see https://drafts.csswg.org/css-values-4/#mult-comma)."""
73-
result: list[Product | Token] = []
74-
input.mark()
75-
while True:
76-
value: Product | Token | None
77-
if result:
78-
value = parse(production.delimiter, input)
79-
if value is None:
80-
break
81-
result.append(value)
82-
value = parse(production.element, input)
83-
if value is None:
84-
break
85-
result.append(value)
86-
if result:
87-
input.discard_mark()
88-
return result
89-
else:
90-
input.restore_mark()
91-
return None
92-
9370
@parse.register
9471
def _(production: ConcatenationProduction, input: TokenStream) -> Product | None:
9572
"""Variant of `parse` for productions of the ` ` combinator variety (see "juxtaposing components" at https://drafts.csswg.org/css-values-4/#component-combinators)."""
@@ -126,9 +103,21 @@ def _(production: RepetitionProduction, input: TokenStream) -> Product | None:
126103
result: list[Product | Token] = []
127104
input.mark()
128105
while True:
106+
if result and production.separator:
107+
input.mark()
108+
separator = parse(production.separator, input)
109+
if separator is None:
110+
input.restore_mark()
111+
break
129112
value = parse(production.element, input)
130113
if value is None:
114+
if result and production.separator:
115+
input.restore_mark()
131116
break
117+
if result and production.separator:
118+
assert separator is not None
119+
result.append(separator)
120+
input.discard_mark()
132121
result.append(value)
133122
if len(result) == production.max:
134123
break

src/csspring/values.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -80,21 +80,25 @@ class RepetitionProduction(Production):
8080
8181
Implements the `*` notation as defined at http://drafts.csswg.org/css-values-4/#mult-zero-plus.
8282
"""
83+
separator: Production | None = None
8384
element: Production
8485
min: int
8586
max: int | None
86-
def __init__(self, element: Production, min: int = 0, max: int | None = None):
87+
def __init__(self, element: Production, min: int = 0, max: int | None = None, *, separator: Production | None = None):
8788
"""
8889
:param element: The production expressing the repeating part of this production
8990
:param min: The minimum amount of times the parser must accept input, i.e. the minimum number of repetitions of token sequences accepted by the parser
9091
:param max: The maximum amount of times the parser will be called, i.e. the maximum number of repetitions that may be consumed in the input; the value of `None` implies no maximum (i.e. no upper bound on repetition)
92+
:param separator: A production expressing the "delimiting" part between any two repetitions of the `element` production; if omitted or `None`, there's _no_ delimiting part -- repetitions are _adjacent_
9193
"""
9294
assert min >= 0
9395
assert max is None or max > 0
9496
assert max is None or min <= max
9597
self.min = min
9698
self.max = max
9799
self.element = element
100+
if separator:
101+
self.separator = separator
98102

99103
class OptionalProduction(RepetitionProduction):
100104
"""Class of productions equivalent to `RepetitionProduction` with no lower bound and accepting no repetition of the element, meaning the element is expressed at most once.
@@ -117,22 +121,18 @@ def __init__(self, type: builtins.type[Token], **attributes):
117121
self.type = type
118122
self.attributes = attributes
119123

124+
OWS = optional_whitespace = RepetitionProduction(TokenProduction(WhitespaceToken))
120125
whitespace = RepetitionProduction(TokenProduction(WhitespaceToken), min=1) # The white-space production; presence of white-space expressed with this production, is _mandatory_ (`min=1`); the definition was "hoisted" here because a) it depends on `RepetitionProduction` and `TokenProduction` definitions, which must thus precede it, and b) because the `CommaSeparatedRepetitionParser` definition that follows, depends on it, in turn
121126

122-
class CommaSeparatedRepetitionProduction(Production):
127+
class CommaSeparatedRepetitionProduction(RepetitionProduction):
123128
"""Class of productions that express a non-empty comma-separated repetition (CSR) of a production element.
124129
125-
Unlike `RepetitionProduction` which permits arbitrary number of the production element, this class does not currently implement arbitrary repetition bounds. The delimiting part (a comma optionally surrounded by white-space) is mandatory, which implies at least one repetition (two expressions of the element). Disregarding the delimiting behaviour, productions of this class thus behave like those of `RepetitionProduction` with `2` for `min` and `None` for `max` property values.
126-
127130
Implements the `#` notation as defined at http://drafts.csswg.org/css-values-4/#mult-comma.
128131
"""
129-
delimiter = ConcatenationProduction(OptionalProduction(AlternativesProduction(whitespace, TokenProduction(CommentToken))), TokenProduction(CommaToken), OptionalProduction(AlternativesProduction(whitespace, TokenProduction(CommentToken)))) # The production expressing the delimiter to use with the repetition, a comma with [optional] white-space around it
130-
element: Production
131-
def __init__(self, element: Production):
132-
"""
133-
:param element: A production to use for expressing the repeating part in this production
134-
"""
135-
self.element = element
132+
separator = ConcatenationProduction(OWS, TokenProduction(CommaToken), OWS) # A comma with [optional] white-space around it
133+
def __init__(self, element: Production, min: int = 1, max: int | None = None):
134+
assert min >= 1 # "one or more times" (ref. definition); the spec. does not define whether a minimum of zero is permitted, so we err on the safer side
135+
super().__init__(element, min, max)
136136

137137
class Formatter:
138138
"""Class of objects that offer procedures for serializing productions into streams of text formatted per the [value definition syntax](https://drafts.csswg.org/css-values-4/#value-defs)."""

0 commit comments

Comments
 (0)