Skip to content

Commit d430133

Browse files
Merge pull request #3 from BlockScience/refactor/ogs-use-gds-pipeline-stages
refactor(ogs): replace duplicated DFS with GDS pipeline stage calls
2 parents e33058a + 7edf31a commit d430133

1 file changed

Lines changed: 38 additions & 119 deletions

File tree

packages/gds-games/ogs/dsl/compile.py

Lines changed: 38 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,26 @@
55
1. **Flatten** — walks the composition tree to extract all atomic games.
66
2. **Wire** — extracts explicit flows and auto-wires sequential compositions.
77
3. **Map metadata** — converts DSL-level metadata into IR equivalents.
8+
9+
Stages 1 and 2 delegate to the reusable GDS pipeline functions
10+
(``flatten_blocks``, ``extract_wirings``), with OGS-specific callbacks
11+
to produce ``OpenGameIR`` and ``FlowIR`` respectively. Hierarchy
12+
extraction remains OGS-specific to handle ``CORECURSIVE`` composition.
813
"""
914

1015
from __future__ import annotations
1116

12-
from ogs.dsl.base import OpenGame
17+
from gds.blocks.base import Block
18+
from gds.compiler.compile import (
19+
StructuralWiring,
20+
WiringOrigin,
21+
extract_wirings,
22+
flatten_blocks,
23+
)
24+
1325
from ogs.dsl.composition import (
1426
CorecursiveLoop,
1527
FeedbackLoop,
16-
Flow,
1728
ParallelComposition,
1829
SequentialComposition,
1930
)
@@ -34,12 +45,11 @@
3445

3546
def compile_to_ir(pattern: Pattern) -> PatternIR:
3647
"""Compile a DSL Pattern into PatternIR."""
37-
# 1. Flatten games
38-
atomic_games = pattern.game.flatten()
39-
game_irs = [_compile_game(g) for g in atomic_games]
48+
# 1. Flatten games (GDS stage 1)
49+
game_irs = flatten_blocks(pattern.game, _compile_game)
4050

41-
# 2. Walk composition tree to extract flows
42-
flows = _extract_flows(pattern.game)
51+
# 2. Extract flows (GDS stage 2 with OGS emitter)
52+
flows: list[FlowIR] = extract_wirings(pattern.game, _ogs_wiring_emitter)
4353

4454
# 3. Map inputs and generate input flows
4555
input_irs = []
@@ -62,7 +72,7 @@ def compile_to_ir(pattern: Pattern) -> PatternIR:
6272
)
6373
)
6474

65-
# 4. Extract composition hierarchy
75+
# 4. Extract composition hierarchy (OGS-specific for CORECURSIVE)
6676
counter = [0]
6777
hierarchy = _extract_hierarchy(pattern.game, counter)
6878
hierarchy = _flatten_sequential_chains(hierarchy)
@@ -105,125 +115,34 @@ def _ports_to_sig(ports: tuple[Port, ...]) -> str:
105115
return " + ".join(p.name for p in ports)
106116

107117

108-
def _extract_flows(game: OpenGame) -> list[FlowIR]:
109-
"""Recursively walk the game tree and collect all flows."""
110-
flows: list[FlowIR] = []
111-
_walk_flows(game, flows)
112-
return flows
113-
118+
def _ogs_wiring_emitter(sw: StructuralWiring) -> FlowIR:
119+
"""Convert a GDS StructuralWiring into an OGS FlowIR."""
120+
if sw.direction == FlowDirection.CONTRAVARIANT:
121+
flow_type = FlowType.UTILITY_COUTILITY
122+
elif sw.origin == WiringOrigin.AUTO and _is_choice_port(sw.source_port):
123+
flow_type = FlowType.CHOICE_OBSERVATION
124+
else:
125+
flow_type = FlowType.OBSERVATION
114126

115-
def _walk_flows(game: OpenGame, flows: list[FlowIR]) -> None:
116-
"""Recursively walk the composition tree, collecting all flows."""
117-
if isinstance(game, AtomicGame):
118-
return
119-
120-
if isinstance(game, SequentialComposition):
121-
_walk_flows(game.first, flows)
122-
_walk_flows(game.second, flows)
123-
124-
for w in game.wiring:
125-
flows.append(_flow_to_ir(w))
126-
127-
if not game.wiring:
128-
_auto_wire_sequential(game.first, game.second, flows)
129-
130-
elif isinstance(game, ParallelComposition):
131-
_walk_flows(game.left, flows)
132-
_walk_flows(game.right, flows)
133-
134-
elif isinstance(game, FeedbackLoop):
135-
_walk_flows(game.inner, flows)
136-
for fw in game.feedback_wiring:
137-
flows.append(_flow_to_ir(fw, is_feedback=True))
138-
139-
elif isinstance(game, CorecursiveLoop):
140-
_walk_flows(game.inner, flows)
141-
for w in game.corecursive_wiring:
142-
flows.append(_flow_to_ir(w, is_corecursive=True))
143-
144-
145-
def _auto_wire_sequential(
146-
first: OpenGame, second: OpenGame, flows: list[FlowIR]
147-
) -> None:
148-
"""Auto-wire matching Y→X ports between sequentially composed games."""
149-
first_leaves = _get_leaf_names(first)
150-
second_leaves = _get_leaf_names(second)
151-
152-
for y_port in first.signature.y:
153-
for x_port in second.signature.x:
154-
if y_port.type_tokens & x_port.type_tokens:
155-
source = _find_port_owner(first, y_port, "y") or first_leaves[-1]
156-
target = _find_port_owner(second, x_port, "x") or second_leaves[0]
157-
flows.append(
158-
FlowIR(
159-
source=source,
160-
target=target,
161-
label=y_port.name,
162-
flow_type=_infer_flow_type(y_port, FlowDirection.COVARIANT),
163-
direction=FlowDirection.COVARIANT,
164-
)
165-
)
166-
167-
168-
def _flow_to_ir(
169-
flow: Flow,
170-
is_feedback: bool = False,
171-
is_corecursive: bool = False,
172-
) -> FlowIR:
173-
"""Convert a DSL Flow to an IR FlowIR."""
174-
flow_type = (
175-
FlowType.UTILITY_COUTILITY
176-
if flow.direction == FlowDirection.CONTRAVARIANT
177-
else FlowType.OBSERVATION
178-
)
179127
return FlowIR(
180-
source=flow.source_game,
181-
target=flow.target_game,
182-
label=flow.source_port,
128+
source=sw.source_block,
129+
target=sw.target_block,
130+
label=sw.source_port,
183131
flow_type=flow_type,
184-
direction=flow.direction,
185-
is_feedback=is_feedback,
186-
is_corecursive=is_corecursive,
132+
direction=sw.direction,
133+
is_feedback=(sw.origin == WiringOrigin.FEEDBACK),
134+
is_corecursive=(sw.origin == WiringOrigin.TEMPORAL),
187135
)
188136

189137

190-
def _infer_flow_type(port: Port, direction: FlowDirection) -> FlowType:
191-
"""Infer the semantic flow type from port name and direction."""
192-
if direction == FlowDirection.CONTRAVARIANT:
193-
return FlowType.UTILITY_COUTILITY
194-
195-
name_lower = port.name.lower()
196-
if "decision" in name_lower or "choice" in name_lower:
197-
return FlowType.CHOICE_OBSERVATION
198-
return FlowType.OBSERVATION
199-
200-
201-
def _get_leaf_names(game: OpenGame) -> list[str]:
202-
"""Get names of all leaf (atomic) games."""
203-
return [g.name for g in game.flatten()]
204-
205-
206-
def _find_port_owner(game: OpenGame, target_port: Port, slot: str) -> str | None:
207-
"""Find which leaf game owns a given port in the specified slot.
208-
209-
Maps game-theory slot names (x, y, r, s) to interface field names.
210-
"""
211-
slot_map = {
212-
"x": "forward_in",
213-
"y": "forward_out",
214-
"r": "backward_in",
215-
"s": "backward_out",
216-
}
217-
interface_slot = slot_map.get(slot, slot)
218-
for leaf in game.flatten():
219-
ports = getattr(leaf.interface, interface_slot)
220-
if target_port in ports:
221-
return leaf.name
222-
return None
138+
def _is_choice_port(port_name: str) -> bool:
139+
"""Check whether a port name indicates a choice/decision flow."""
140+
name_lower = port_name.lower()
141+
return "decision" in name_lower or "choice" in name_lower
223142

224143

225144
# ---------------------------------------------------------------------------
226-
# Hierarchy extraction
145+
# Hierarchy extraction (OGS-specific for CORECURSIVE composition type)
227146
# ---------------------------------------------------------------------------
228147

229148

@@ -234,7 +153,7 @@ def _sanitize_id(name: str) -> str:
234153
return re.sub(r"[^A-Za-z0-9_]", "_", name)
235154

236155

237-
def _extract_hierarchy(game: OpenGame, counter: list[int]) -> HierarchyNodeIR:
156+
def _extract_hierarchy(game: Block, counter: list[int]) -> HierarchyNodeIR:
238157
"""Recursively walk the composition tree and build a HierarchyNodeIR."""
239158
if isinstance(game, AtomicGame):
240159
return HierarchyNodeIR(

0 commit comments

Comments
 (0)