Skip to content

Commit a5e46b0

Browse files
authored
Builder context refactoring (#433)
Move context pushing for contents, generate into Block methods, instead of the top-level builder method. This becomes more consistent with Block metaclass init hook handling the `__init__` context. Also changes initializers to be stored as the raw value, instead of _to_expr_type. Adds explicit context to _name_from, since array element naming conventions are dependent on if accessing from outside (request required) or from inside (explicit elements required). Previously, this depended on the builder context.
1 parent f2b1a58 commit a5e46b0

File tree

10 files changed

+80
-60
lines changed

10 files changed

+80
-60
lines changed

edg/abstract_parts/IoController.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ def _make_pinning(self) -> Dict[str, CircuitPort]:
175175
allocation_list.append((io_port.elt_type(), self.get(io_port.requested())))
176176
elif isinstance(io_port, Port): # derive Port connections from is_connected
177177
if self.get(io_port.is_connected()):
178-
requested = [self._name_of_child(io_port)] # generate requested name from port name if connected
178+
requested = [self._name_of_child(io_port, self)] # generate requested name from port name if connected
179179
else:
180180
requested = []
181181
allocation_list.append((type(io_port), requested))

edg/core/Array.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,12 @@ def items(self) -> ItemsView[str, VectorType]:
130130
return self._elts.items()
131131

132132
# unlike most other LibraryElement types, the names are stored in _elts and _allocates
133-
def _name_of_child(self, subelt: Any, allow_unknown: bool = False) -> str:
133+
def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str:
134134
from .HierarchyBlock import Block
135135
block_parent = self._block_parent()
136136
assert isinstance(block_parent, Block)
137137

138-
if builder.get_enclosing_block() is block_parent or builder.get_enclosing_block() is None:
138+
if context is block_parent:
139139
# in block defining this port (direct elt definition), or in test top
140140
assert self._elts is not None, "can't get name on undefined vector"
141141
for (name, elt) in self._elts.items():
@@ -145,7 +145,7 @@ def _name_of_child(self, subelt: Any, allow_unknown: bool = False) -> str:
145145
return f"(unknown {subelt.__class__.__name__})"
146146
else:
147147
raise ValueError(f"no name for {subelt}")
148-
elif builder.get_enclosing_block() is block_parent._parent:
148+
elif context is block_parent._parent:
149149
# in block enclosing the block defining this port (allocate required)
150150
for (i, (suggested_name, allocate_elt)) in enumerate(self._requests):
151151
if subelt is allocate_elt:

edg/core/Binding.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,8 @@ def expr_to_proto(self, expr: ConstraintExpr, ref_map: IdentityDict[Refable, edg
108108

109109
class InitParamBinding(ParamBinding):
110110
"""Binding that indicates this is a parameter from an __init__ argument.
111-
Can optionally take a value, which would have a binding in the parent's scope."""
112-
def __init__(self, parent: ParamParentTypes, value: Optional[ConstraintExpr] = None):
111+
Can optionally take a value, which is the raw value passed into __init__."""
112+
def __init__(self, parent: ParamParentTypes, value: Optional[Any] = None):
113113
super().__init__(parent)
114114
self.value = value
115115

edg/core/Blocks.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -292,10 +292,16 @@ def _def_to_proto(self) -> BaseBlockEdgirType:
292292
raise NotImplementedError
293293

294294
def _elaborated_def_to_proto(self) -> BaseBlockEdgirType:
295-
assert self._elaboration_state == BlockElaborationState.post_init
296-
self._elaboration_state = BlockElaborationState.contents
297-
self.contents()
298-
self._elaboration_state = BlockElaborationState.post_contents
295+
prev_element = builder.push_element(self)
296+
assert prev_element is None
297+
try:
298+
assert self._elaboration_state == BlockElaborationState.post_init
299+
self._elaboration_state = BlockElaborationState.contents
300+
self.contents()
301+
self._elaboration_state = BlockElaborationState.post_contents
302+
finally:
303+
builder.pop_to(prev_element)
304+
299305
return self._def_to_proto()
300306

301307
def _populate_def_proto_block_base(self, pb: BaseBlockEdgirType) -> BaseBlockEdgirType:

edg/core/Builder.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,14 @@ def push_element(self, elt: BaseBlock) -> Optional[BaseBlock]:
2222
self.stack.append(elt)
2323
return prev_elt
2424

25-
def pop_to(self, elt: Optional[BaseBlock]) -> None:
26-
while (elt is None and self.stack) or (elt is not None and self.stack[-1] is not elt):
27-
self.stack.pop()
25+
def pop_to(self, prev: Optional[BaseBlock]) -> None:
26+
"""Pops at most one element from stack, expecting prev to be at the top of the stack.
27+
The pattern should be one pop for one push, and allowing that duplicate pushes are ignored."""
28+
if (prev is None and not self.stack) or (prev is not None and self.stack[-1] is prev):
29+
return
30+
31+
self.stack.pop()
32+
assert (prev is None and not self.stack) or (prev is not None and self.stack[-1] is prev)
2833

2934
def get_enclosing_block(self) -> Optional[BaseBlock]:
3035
if not self.stack:
@@ -39,8 +44,6 @@ def get_curr_context(self) -> Optional[BaseBlock]:
3944
def elaborate_toplevel(self, block: BaseBlock, *,
4045
is_generator: bool = False,
4146
generate_values: Iterable[Tuple[edgir.LocalPath, edgir.ValueLit]] = []) -> edgir.HierarchyBlock:
42-
assert self.get_enclosing_block() is None
43-
self.push_element(block)
4447
try:
4548
if is_generator: # TODO this is kind of nasty =(
4649
elaborated = block._generated_def_to_proto(generate_values) # type: ignore
@@ -50,8 +53,6 @@ def elaborate_toplevel(self, block: BaseBlock, *,
5053
return elaborated
5154
except Exception as e:
5255
raise Exception(f"While elaborating {block.__class__}") from e
53-
finally:
54-
self.pop_to(None)
5556

5657

5758
builder = Builder()

edg/core/Core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def __setattr__(self, name: str, value: Any) -> None:
209209
self.manager.add_element(name, value)
210210
super().__setattr__(name, value)
211211

212-
def _name_of_child(self, subelt: Any, allow_unknown: bool = False) -> str:
212+
def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str:
213213
self_name = self.manager.name_of(subelt)
214214
if self_name is not None:
215215
return self_name
@@ -224,7 +224,7 @@ def _path_from(self, base: LibraryElement, allow_unknown: bool = False) -> List[
224224
return []
225225
else:
226226
assert self._parent is not None, "can't get path / name for non-bound element"
227-
return self._parent._path_from(base, allow_unknown) + [self._parent._name_of_child(self, allow_unknown)]
227+
return self._parent._path_from(base, allow_unknown) + [self._parent._name_of_child(self, base, allow_unknown)]
228228

229229
def _name_from(self, base: LibraryElement, allow_unknown: bool = False) -> str:
230230
"""Returns the path name to (inclusive) this element from some starting point.

edg/core/DesignTop.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import TypeVar, Union, List, Tuple, Dict, Type
22

33
from .. import edgir
4+
from .Builder import builder
45
from .Ports import Port
56
from .ConstraintExpr import ConstraintExpr
67
from .HdlUserExceptions import BlockDefinitionError
@@ -51,11 +52,16 @@ def multipack(self):
5152

5253
# TODO make this non-overriding? - this needs to call multipack after contents
5354
def _elaborated_def_to_proto(self) -> edgir.HierarchyBlock:
54-
assert self._elaboration_state == BlockElaborationState.post_init
55-
self._elaboration_state = BlockElaborationState.contents
56-
self.contents()
57-
self.multipack()
58-
self._elaboration_state = BlockElaborationState.post_contents
55+
prev_element = builder.push_element(self)
56+
assert prev_element is None
57+
try:
58+
assert self._elaboration_state == BlockElaborationState.post_init
59+
self._elaboration_state = BlockElaborationState.contents
60+
self.contents()
61+
self.multipack()
62+
self._elaboration_state = BlockElaborationState.post_contents
63+
finally:
64+
builder.pop_to(prev_element)
5965
return self._def_to_proto()
6066

6167
def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.HierarchyBlock:
@@ -78,7 +84,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.
7884
else:
7985
raise TypeError
8086
assert isinstance(multipack_block, MultipackBlock)
81-
multipack_name = self._name_of_child(multipack_block)
87+
multipack_name = self._name_of_child(multipack_block, self)
8288
multipack_ref_base = edgir.LocalPath()
8389
multipack_ref_base.steps.add().name = multipack_name
8490
multipack_ref_map = multipack_block._get_ref_map(multipack_ref_base)
@@ -90,9 +96,9 @@ def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.
9096
packed_ref_map = multipack_part_block._get_ref_map(packed_ref_base)
9197

9298
if isinstance(multipack_part, Block):
93-
part_name = multipack_block._name_of_child(multipack_part)
99+
part_name = multipack_block._name_of_child(multipack_part, self)
94100
elif isinstance(multipack_part, PackedBlockAllocate):
95-
part_name = multipack_block._name_of_child(multipack_part.parent)
101+
part_name = multipack_block._name_of_child(multipack_part.parent, self)
96102
assert multipack_part.suggested_name, "multipack parts must have suggested name, for consistency"
97103
part_name += f"[{multipack_part.suggested_name}]"
98104
else:
@@ -105,7 +111,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.
105111
packed_port_port = packed_port.port
106112
else:
107113
raise TypeError
108-
packed_port_name = multipack_part_block._name_of_child(packed_port_port)
114+
packed_port_name = multipack_part_block._name_of_child(packed_port_port, self)
109115
exported_tunnel = edgir.add_pair(pb.constraints,
110116
f"(packed){multipack_name}.{part_name}.{packed_port_name}").exportedTunnel
111117
exported_tunnel.internal_block_port.ref.CopyFrom(multipack_ref_map[exterior_port])
@@ -117,13 +123,13 @@ def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.
117123

118124
for multipack_param, packed_param in packing_rule.tunnel_assigns.items():
119125
if isinstance(packed_param, ConstraintExpr):
120-
packed_param_name = multipack_part_block._name_of_child(packed_param)
126+
packed_param_name = multipack_part_block._name_of_child(packed_param, self)
121127
assign_tunnel = edgir.add_pair(pb.constraints,
122128
f"(packed){multipack_name}.{part_name}.{packed_param_name}").assignTunnel
123129
assign_tunnel.dst.CopyFrom(multipack_ref_map[multipack_param])
124130
assign_tunnel.src.ref.CopyFrom(packed_ref_map[packed_param])
125131
elif isinstance(packed_param, PackedBlockParamArray):
126-
multipack_param_name = multipack_block._name_of_child(multipack_param)
132+
multipack_param_name = multipack_block._name_of_child(multipack_param, self)
127133
constr_name = f"(packed){multipack_name}.{multipack_param_name}"
128134
packed_params.setdefault(constr_name, (multipack_ref_map[multipack_param], []))[1].append(
129135
packed_ref_map[packed_param.param])
@@ -132,14 +138,14 @@ def _populate_def_proto_block_contents(self, pb: edgir.HierarchyBlock) -> edgir.
132138

133139
for multipack_param, unpacked_param in packing_rule.tunnel_unpack_assigns.items():
134140
if isinstance(unpacked_param, ConstraintExpr):
135-
multipack_param_name = multipack_block._name_of_child(multipack_param)
141+
multipack_param_name = multipack_block._name_of_child(multipack_param, self)
136142
# TODO need better constraint naming scheme
137143
assign_tunnel = edgir.add_pair(pb.constraints,
138144
f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}").assignTunnel
139145
assign_tunnel.dst.CopyFrom(packed_ref_map[unpacked_param])
140146
assign_tunnel.src.ref.CopyFrom(multipack_ref_map[multipack_param])
141147
elif isinstance(unpacked_param, PackedBlockParam):
142-
multipack_param_name = multipack_block._name_of_child(multipack_param)
148+
multipack_param_name = multipack_block._name_of_child(multipack_param, self)
143149
# TODO need better constraint naming scheme
144150
assign_tunnel = edgir.add_pair(pb.constraints,
145151
f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}").assignTunnel

edg/core/Generator.py

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from deprecated import deprecated
66

77
from .. import edgir
8+
from .Builder import builder
89
from .Ports import BasePort, Port
910
from .PortTag import PortTag
1011
from .IdentityDict import IdentityDict
@@ -106,30 +107,34 @@ def _def_to_proto(self) -> edgir.HierarchyBlock:
106107

107108
def _generated_def_to_proto(self, generate_values: Iterable[Tuple[edgir.LocalPath, edgir.ValueLit]]) -> \
108109
edgir.HierarchyBlock:
109-
assert self._elaboration_state == BlockElaborationState.post_init # TODO dedup w/ elaborated_def_to_proto
110-
self._elaboration_state = BlockElaborationState.contents
111-
112-
self.contents()
113-
114-
self._elaboration_state = BlockElaborationState.generate
115-
116-
# Translate parameter values to function arguments
117-
ref_map = self._get_ref_map(edgir.LocalPath())
118-
generate_values_map = {path.SerializeToString(): value for (path, value) in generate_values}
119-
120-
assert (self.__class__, AbstractBlockProperty) not in self._elt_properties # abstract blocks can't generate
121-
if type(self).generate is not GeneratorBlock.generate:
122-
for param in self._generator_params_list:
123-
self._generator_param_values[param] = param._from_lit(generate_values_map[ref_map[param].SerializeToString()])
124-
self.generate()
125-
elif self._generator is not None: # legacy generator style
126-
fn_args = [arg_param._from_lit(generate_values_map[ref_map[arg_param].SerializeToString()])
127-
for arg_param in self._generator.fn_args]
128-
self._generator.fn(*fn_args)
129-
else:
130-
raise BlockDefinitionError(self, "Generator missing generate implementation", "define generate")
110+
prev_element = builder.push_element(self)
111+
assert prev_element is None
112+
113+
try:
114+
assert self._elaboration_state == BlockElaborationState.post_init # TODO dedup w/ elaborated_def_to_proto
115+
self._elaboration_state = BlockElaborationState.contents
116+
self.contents()
117+
self._elaboration_state = BlockElaborationState.generate
118+
119+
# Translate parameter values to function arguments
120+
ref_map = self._get_ref_map(edgir.LocalPath())
121+
generate_values_map = {path.SerializeToString(): value for (path, value) in generate_values}
122+
123+
assert (self.__class__, AbstractBlockProperty) not in self._elt_properties # abstract blocks can't generate
124+
if type(self).generate is not GeneratorBlock.generate:
125+
for param in self._generator_params_list:
126+
self._generator_param_values[param] = param._from_lit(generate_values_map[ref_map[param].SerializeToString()])
127+
self.generate()
128+
elif self._generator is not None: # legacy generator style
129+
fn_args = [arg_param._from_lit(generate_values_map[ref_map[arg_param].SerializeToString()])
130+
for arg_param in self._generator.fn_args]
131+
self._generator.fn(*fn_args)
132+
else:
133+
raise BlockDefinitionError(self, "Generator missing generate implementation", "define generate")
131134

132-
self._elaboration_state = BlockElaborationState.post_generate
135+
self._elaboration_state = BlockElaborationState.post_generate
136+
finally:
137+
builder.pop_to(prev_element)
133138

134139
return self._def_to_proto()
135140

edg/core/HierarchyBlock.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) ->
166166

167167
if isinstance(arg_value, ConstraintExpr): # otherwise, create a new arg
168168
if arg_value._is_bound():
169-
typed_arg_value: Optional[ConstraintExpr] = arg_type._to_expr_type(arg_value)
169+
typed_arg_value: Optional[ConstraintExpr] = arg_value
170170
elif arg_value.initializer is None:
171171
typed_arg_value = None
172172
else:
@@ -175,7 +175,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) ->
175175
"either leave default empty or pass in a value or uninitialized type " + \
176176
"(eg, 2.0, FloatExpr(), but NOT FloatExpr(2.0))")
177177
elif arg_value is not None:
178-
typed_arg_value = arg_type._to_expr_type(arg_value)
178+
typed_arg_value = arg_value
179179
else:
180180
typed_arg_value = None
181181

@@ -275,7 +275,8 @@ def _populate_def_proto_block_base(self, pb: edgir.HierarchyBlock) -> edgir.Hier
275275
for param_name, param in self._parameters.items():
276276
if isinstance(param.binding, InitParamBinding) and param.binding.value is not None:
277277
# default values can't depend on anything so the ref_map is empty
278-
pb.param_defaults[param_name].CopyFrom(param.binding.value._expr_to_proto(IdentityDict()))
278+
param_typed_value = param._to_expr_type(param.binding.value)
279+
pb.param_defaults[param_name].CopyFrom(param_typed_value._expr_to_proto(IdentityDict()))
279280

280281
return pb
281282

@@ -381,8 +382,9 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock) -> edgir.Hiera
381382
all_block_params.update(mixin._parameters.items())
382383
for (block_param_name, block_param) in all_block_params.items():
383384
if isinstance(block_param.binding, InitParamBinding) and block_param.binding.value is not None:
385+
param_typed_value = block_param._to_expr_type(block_param.binding.value)
384386
edgir.add_pair(pb.constraints, f'(init){block_name}.{block_param_name}').CopyFrom( # TODO better name
385-
AssignBinding.make_assign(block_param, block_param.binding.value, ref_map)
387+
AssignBinding.make_assign(block_param, param_typed_value, ref_map)
386388
)
387389

388390
return pb

edg/core/Ports.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType
183183
adapter_inst = enclosing_block.Block(adapter)
184184
adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else ""
185185
enclosing_block.manager.add_element(
186-
f"(adapter){block_parent._name_from(enclosing_block)}.{self._name_from(block_parent)}{adapter_name_suffix}",
186+
f"(adapter){self._name_from(enclosing_block)}{adapter_name_suffix}",
187187
adapter_inst)
188188
enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts
189189
self._adapter_count += 1

0 commit comments

Comments
 (0)