551. **Flatten** — walks the composition tree to extract all atomic games.
662. **Wire** — extracts explicit flows and auto-wires sequential compositions.
773. **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
1015from __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+
1325from ogs .dsl .composition import (
1426 CorecursiveLoop ,
1527 FeedbackLoop ,
16- Flow ,
1728 ParallelComposition ,
1829 SequentialComposition ,
1930)
3445
3546def 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