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

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# 

24 

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. 

29 

30How the scopes of a netlist nest (e.g. a subcircuit call in the scope of its caller) is up to its flavour. 

31 

32NOTE: imports neither KLayout nor generated protobuf code, so that format-only paths can use it 

33""" 

34 

35from __future__ import annotations 

36from typing import * 

37 

38from .nodes import Expression, ExpressionError, NumberOrExpression, Scope 

39 

40 

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] = [] 

54 

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 

77 

78 def evaluate(self, value: NumberOrExpression) -> float: 

79 """ 

80 The value of a number or expression in this scope 

81 

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)