Coverage for klayout_pex/netlist/expression/scope.py: 100%
30 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-06 13:14 +0000
1#
2# --------------------------------------------------------------------------------
3# SPDX-FileCopyrightText: 2024-2026 Martin Jan Köhler and Harald Pretl
4# Johannes Kepler University, Institute for Integrated Circuits.
5#
6# This file is part of KPEX
7# (see https://github.com/iic-jku/klayout-pex).
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <http://www.gnu.org/licenses/>.
21# SPDX-License-Identifier: GPL-3.0-or-later
22# --------------------------------------------------------------------------------
23#
25"""
26The scope of parameters, e.g. of a subcircuit call: their definitions, numbers or expressions,
27evaluated when first asked for, in that scope. A definition that refers to its own name (e.g. w=w*2)
28refers to the parameter of that name in the parent scope.
30How the scopes of a netlist nest (e.g. a subcircuit call in the scope of its caller) is up to its flavour.
32NOTE: imports neither KLayout nor generated protobuf code, so that format-only paths can use it
33"""
35from __future__ import annotations
36from typing import *
38from .nodes import Expression, ExpressionError, NumberOrExpression, Scope
41class ParameterScope:
42 def __init__(self,
43 definitions: Mapping[str, NumberOrExpression],
44 parent: Optional[Scope] = None):
45 """
46 :param definitions: the parameters by name, as the expressions name them
47 (e.g. lowercase for a flavour that ignores case), in any order
48 :param parent: the scope of the parameters these definitions don't define
49 """
50 self.definitions = definitions
51 self.parent = parent
52 self._values: Dict[str, float] = {}
53 self._evaluating: List[str] = []
55 def value(self, name: str) -> float:
56 """
57 :raises ExpressionError: if the parameter is not defined, or its definition has no value
58 (e.g. it depends on itself)
59 """
60 value = self._values.get(name)
61 if value is not None:
62 return value
63 if name not in self.definitions or self._evaluating[-1:] == [name]:
64 if self.parent is None:
65 raise ExpressionError(f"the parameter {name} is not defined")
66 return self.parent.value(name)
67 if name in self._evaluating:
68 cycle = self._evaluating[self._evaluating.index(name):] + [name]
69 raise ExpressionError(f"the parameter {name} depends on itself ({' → '.join(cycle)})")
70 self._evaluating.append(name)
71 try:
72 value = self.evaluate(self.definitions[name])
73 finally:
74 self._evaluating.pop()
75 self._values[name] = value
76 return value
78 def evaluate(self, value: NumberOrExpression) -> float:
79 """
80 The value of a number or expression in this scope
82 :raises ExpressionError: if the expression has no value in this scope
83 """
84 if isinstance(value, Expression):
85 return value.evaluate(self)
86 return float(value)