Skip to content

Commit ef26846

Browse files
Switching from our own Peekable implementation to more-itertools.peekable. (frequenz-floss#1376)
Addresses frequenz-floss#1325 and frequenz-floss#1326. Switching to more-itertools.peekable as suggested in the [original thread](frequenz-floss#1295 (comment)).
2 parents b107813 + 5ae3819 commit ef26846

File tree

4 files changed

+37
-75
lines changed

4 files changed

+37
-75
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [
3838
"typing_extensions >= 4.14.1, < 5",
3939
"marshmallow >= 3.19.0, < 5",
4040
"marshmallow_dataclass >= 8.7.1, < 9",
41+
"more-itertools >= 10.8.0, < 11",
4142
]
4243
dynamic = ["version"]
4344

src/frequenz/sdk/timeseries/formulas/_lexer.py

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@
88

99
from collections.abc import Iterator
1010

11+
from more_itertools import peekable
1112
from typing_extensions import override
1213

1314
from . import _token
1415
from ._exceptions import FormulaSyntaxError
15-
from ._peekable import Peekable
1616

1717

1818
class Lexer(Iterator[_token.Token]):
@@ -25,33 +25,40 @@ def __init__(self, formula: str):
2525
formula: The formula string to lex.
2626
"""
2727
self._formula: str = formula
28-
self._iter: Peekable[tuple[int, str]] = Peekable(enumerate(iter(formula)))
28+
self._iter: peekable[tuple[int, str]] = peekable(enumerate(iter(formula)))
29+
30+
def _peek_char(self) -> tuple[int, str] | None:
31+
"""Return the next position and character, or None if _iter is at its end."""
32+
try:
33+
return self._iter.peek()
34+
except StopIteration:
35+
return None
2936

3037
def _read_integer(self) -> str:
3138
num_str = ""
32-
peek = self._iter.peek()
39+
peek = self._peek_char()
3340
while peek is not None and peek[1].isdigit():
3441
_, char = next(self._iter)
3542
num_str += char
36-
peek = self._iter.peek()
43+
peek = self._peek_char()
3744
return num_str
3845

3946
def _read_number(self) -> str:
4047
num_str = ""
41-
peek = self._iter.peek()
48+
peek = self._peek_char()
4249
while peek is not None and (peek[1].isdigit() or peek[1] == "."):
4350
_, char = next(self._iter)
4451
num_str += char
45-
peek = self._iter.peek()
52+
peek = self._peek_char()
4653
return num_str
4754

4855
def _read_symbol(self) -> str:
4956
word_str = ""
50-
peek = self._iter.peek()
57+
peek = self._peek_char()
5158
while peek is not None and peek[1].isalnum():
5259
_, char = next(self._iter)
5360
word_str += char
54-
peek = self._iter.peek()
61+
peek = self._peek_char()
5562
return word_str
5663

5764
@override
@@ -62,10 +69,10 @@ def __iter__(self) -> Lexer:
6269
@override
6370
def __next__(self) -> _token.Token: # pylint: disable=too-many-branches
6471
"""Return the next token from the formula string."""
65-
peek = self._iter.peek()
72+
peek = self._peek_char()
6673
while peek is not None and peek[1].isspace():
6774
_ = next(self._iter)
68-
peek = self._iter.peek()
75+
peek = self._peek_char()
6976

7077
if peek is None:
7178
raise StopIteration

src/frequenz/sdk/timeseries/formulas/_parser.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from frequenz.channels import Receiver
1313
from frequenz.client.common.microgrid.components import ComponentId
1414
from frequenz.quantities import Quantity
15+
from more_itertools import peekable
1516

1617
from frequenz.sdk.timeseries import Sample
1718
from frequenz.sdk.timeseries._base_types import QuantityT
@@ -22,7 +23,6 @@
2223
from ._formula import Formula
2324
from ._functions import FunCall, Function
2425
from ._lexer import Lexer
25-
from ._peekable import Peekable
2626
from ._resampled_stream_fetcher import ResampledStreamFetcher
2727

2828
_logger = logging.getLogger(__name__)
@@ -67,10 +67,17 @@ def __init__(
6767
"""Initialize the parser."""
6868
self._name: str = name
6969
self._formula: str = formula
70-
self._lexer: Peekable[_token.Token] = Peekable(Lexer(formula))
70+
self._lexer: peekable[_token.Token] = peekable(Lexer(formula))
7171
self._telemetry_fetcher: ResampledStreamFetcher = telemetry_fetcher
7272
self._create_method: Callable[[float], QuantityT] = create_method
7373

74+
def _peek_next_token(self) -> _token.Token | None:
75+
"""Get the next token from the lexer, or None if there is None."""
76+
try:
77+
return self._lexer.peek()
78+
except StopIteration:
79+
return None
80+
7481
def _parse_term(self) -> AstNode[QuantityT] | None:
7582
"""Parse a term.
7683
@@ -81,7 +88,7 @@ def _parse_term(self) -> AstNode[QuantityT] | None:
8188
if factor is None:
8289
return None
8390

84-
token: _token.Token | None = self._lexer.peek()
91+
token: _token.Token | None = self._peek_next_token()
8592
while token is not None and isinstance(token, (_token.Plus, _token.Minus)):
8693
token = next(self._lexer)
8794
next_factor = self._parse_factor()
@@ -98,7 +105,7 @@ def _parse_term(self) -> AstNode[QuantityT] | None:
98105
elif isinstance(token, _token.Minus):
99106
factor = _ast.Sub(left=factor, right=next_factor)
100107

101-
token = self._lexer.peek()
108+
token = self._peek_next_token()
102109

103110
return factor
104111

@@ -113,7 +120,7 @@ def _parse_factor(self) -> AstNode[QuantityT] | None:
113120
if unary is None:
114121
return None
115122

116-
token: _token.Token | None = self._lexer.peek()
123+
token: _token.Token | None = self._peek_next_token()
117124
while token is not None and isinstance(token, (_token.Mul, _token.Div)):
118125
token = next(self._lexer)
119126
next_unary = self._parse_unary()
@@ -129,7 +136,7 @@ def _parse_factor(self) -> AstNode[QuantityT] | None:
129136
elif isinstance(token, _token.Div):
130137
unary = _ast.Div(left=unary, right=next_unary)
131138

132-
token = self._lexer.peek()
139+
token = self._peek_next_token()
133140

134141
return unary
135142

@@ -139,7 +146,7 @@ def _parse_unary(self) -> AstNode[QuantityT] | None:
139146
A unary is any expression that does not contain any binary
140147
operators outside of parentheses.
141148
"""
142-
token: _token.Token | None = self._lexer.peek()
149+
token: _token.Token | None = self._peek_next_token()
143150
if token is not None and isinstance(token, _token.Minus):
144151
token = next(self._lexer)
145152
primary: AstNode[QuantityT] | None = self._parse_primary()
@@ -167,7 +174,7 @@ def _parse_bracketed(self) -> AstNode[QuantityT] | None:
167174
message="Expected expression",
168175
)
169176

170-
token: _token.Token | None = self._lexer.peek()
177+
token: _token.Token | None = self._peek_next_token()
171178
if token is None or not isinstance(token, _token.CloseParen):
172179
raise FormulaSyntaxError(
173180
formula=self._formula,
@@ -194,7 +201,7 @@ def _parse_function_call(self) -> AstNode[QuantityT] | None:
194201

195202
params: list[AstNode[QuantityT]] = []
196203

197-
token: _token.Token | None = self._lexer.peek()
204+
token: _token.Token | None = self._peek_next_token()
198205
if token is None or not isinstance(token, _token.OpenParen):
199206
raise FormulaSyntaxError(
200207
formula=self._formula,
@@ -213,7 +220,7 @@ def _parse_function_call(self) -> AstNode[QuantityT] | None:
213220
)
214221
params.append(param)
215222

216-
token = self._lexer.peek()
223+
token = self._peek_next_token()
217224
if token is None:
218225
raise FormulaSyntaxError(
219226
formula=self._formula,
@@ -245,7 +252,7 @@ def _parse_primary(self) -> AstNode[QuantityT] | None:
245252
- A function call
246253
- A bracketed expression
247254
"""
248-
token: _token.Token | None = self._lexer.peek()
255+
token: _token.Token | None = self._peek_next_token()
249256
if token is None:
250257
return None
251258

@@ -286,7 +293,7 @@ def parse(self) -> Formula[QuantityT]:
286293
message="Empty formula",
287294
)
288295
# There should not be any tokens left
289-
token = self._lexer.peek()
296+
token = self._peek_next_token()
290297
if token is not None:
291298
raise FormulaSyntaxError(
292299
formula=self._formula,

src/frequenz/sdk/timeseries/formulas/_peekable.py

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)