Skip to content

Commit 5e320a9

Browse files
committed
Update functions to fetch their own arguments
This would enable special forms like `coalesce` to fetch only what's necessary. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent a7dc25c commit 5e320a9

File tree

4 files changed

+35
-28
lines changed

4 files changed

+35
-28
lines changed

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,16 @@ class FunCall(AstNode):
5858
"""A function call in the formula."""
5959

6060
function: Function
61-
args: list[AstNode]
6261

6362
@override
6463
def evaluate(self) -> float | None:
6564
"""Evaluate the function call with its arguments."""
66-
return self.function(arg.evaluate() for arg in self.args)
65+
return self.function()
6766

6867
@override
6968
def format(self, wrap: bool = False) -> str:
7069
"""Return a string representation of the function call node."""
71-
args_str = ", ".join(str(arg) for arg in self.args)
72-
return f"{self.function.name}({args_str})"
70+
return self.function.format()
7371

7472

7573
@dataclass(kw_only=True)

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

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,7 @@ def coalesce(
284284
right_nodes.append(_ast.Constant(value=item.base_value))
285285

286286
new_root = _ast.FunCall(
287-
function=Coalesce(),
288-
args=[self.root] + right_nodes,
287+
function=Coalesce([self.root] + right_nodes),
289288
)
290289

291290
return FormulaBuilder(
@@ -317,8 +316,7 @@ def min(
317316
right_nodes.append(_ast.Constant(value=item.base_value))
318317

319318
new_root = _ast.FunCall(
320-
function=Min(),
321-
args=[self.root] + right_nodes,
319+
function=Min([self.root] + right_nodes),
322320
)
323321

324322
return FormulaBuilder(
@@ -350,8 +348,7 @@ def max(
350348
right_nodes.append(_ast.Constant(value=item.base_value))
351349

352350
new_root = _ast.FunCall(
353-
function=Max(),
354-
args=[self.root] + right_nodes,
351+
function=Max([self.root] + right_nodes),
355352
)
356353

357354
return FormulaBuilder(

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

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,43 @@
66
from __future__ import annotations
77

88
import abc
9-
from collections.abc import Iterable
9+
from dataclasses import dataclass
1010

1111
from typing_extensions import override
1212

13+
from ._base_ast_node import AstNode
1314

15+
16+
@dataclass
1417
class Function(abc.ABC):
1518
"""A function that can be called in a formula expression."""
1619

20+
params: list[AstNode]
21+
1722
@property
1823
@abc.abstractmethod
1924
def name(self) -> str:
2025
"""Return the name of the function."""
2126

2227
@abc.abstractmethod
23-
def __call__(self, args: Iterable[float | None]) -> float | None:
28+
def __call__(self) -> float | None:
2429
"""Call the function with the given arguments."""
2530

31+
def format(self) -> str:
32+
"""Return a string representation of the function."""
33+
params_str = ", ".join(str(param) for param in self.params)
34+
return f"{self.name}({params_str})"
35+
2636
@classmethod
27-
def from_string(cls, name: str) -> Function:
37+
def from_string(cls, name: str, params: list[AstNode]) -> Function:
2838
"""Create a function instance from its name."""
2939
match name.upper():
3040
case "COALESCE":
31-
return Coalesce()
41+
return Coalesce(params)
3242
case "MAX":
33-
return Max()
43+
return Max(params)
3444
case "MIN":
35-
return Min()
45+
return Min(params)
3646
case _:
3747
raise ValueError(f"Unknown function name: {name}")
3848

@@ -47,9 +57,10 @@ def name(self) -> str:
4757
return "COALESCE"
4858

4959
@override
50-
def __call__(self, args: Iterable[float | None]) -> float | None:
60+
def __call__(self) -> float | None:
5161
"""Return the first non-None argument."""
52-
for arg in args:
62+
for param in self.params:
63+
arg = param.evaluate()
5364
if arg is not None:
5465
return arg
5566
return None
@@ -65,10 +76,11 @@ def name(self) -> str:
6576
return "MAX"
6677

6778
@override
68-
def __call__(self, args: Iterable[float | None]) -> float | None:
79+
def __call__(self) -> float | None:
6980
"""Return the maximum of the arguments."""
7081
max_value: float | None = None
71-
for arg in args:
82+
for param in self.params:
83+
arg = param.evaluate()
7284
if arg is None:
7385
return None
7486
if max_value is None or arg > max_value:
@@ -86,10 +98,11 @@ def name(self) -> str:
8698
return "MIN"
8799

88100
@override
89-
def __call__(self, args: Iterable[float | None]) -> float | None:
101+
def __call__(self) -> float | None:
90102
"""Return the minimum of the arguments."""
91103
min_value: float | None = None
92-
for arg in args:
104+
for param in self.params:
105+
arg = param.evaluate()
93106
if arg is None:
94107
return None
95108
if min_value is None or arg < min_value:

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

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def _parse_bracketed(self) -> AstNode | None:
143143

144144
def _parse_function_call(self) -> AstNode | None:
145145
fn_name: _token.Token = next(self._lexer)
146-
args: list[AstNode] = []
146+
params: list[AstNode] = []
147147

148148
token: _token.Token | None = self._lexer.peek()
149149
if token is None or not isinstance(token, _token.OpenParen):
@@ -153,12 +153,12 @@ def _parse_function_call(self) -> AstNode | None:
153153

154154
_ = next(self._lexer) # consume '('
155155
while True:
156-
arg = self._parse_term()
157-
if arg is None:
156+
param = self._parse_term()
157+
if param is None:
158158
raise ValueError(
159159
f"Expected argument in function call at position {fn_name.span}"
160160
)
161-
args.append(arg)
161+
params.append(param)
162162

163163
token = self._lexer.peek()
164164
if token is not None and isinstance(token, _token.Comma):
@@ -173,8 +173,7 @@ def _parse_function_call(self) -> AstNode | None:
173173

174174
return _ast.FunCall(
175175
span=fn_name.span,
176-
function=Function.from_string(fn_name.value),
177-
args=args,
176+
function=Function.from_string(fn_name.value, params),
178177
)
179178

180179
def _parse_primary(self) -> AstNode | None:

0 commit comments

Comments
 (0)