OR-1 dataflow CPU sketch
1"""Progressive pipeline runner for dfasm assembly.
2
3Runs assembler passes individually, capturing the deepest successful IRGraph
4even when later passes fail. This enables partial graph visualisation.
5"""
6
7from __future__ import annotations
8
9from dataclasses import dataclass
10from enum import Enum
11from typing import Optional
12
13from lark import Lark
14from lark.exceptions import UnexpectedInput
15from pathlib import Path
16
17from asm.ir import IRGraph
18from asm.lower import lower
19from asm.resolve import resolve
20from asm.place import place
21from asm.allocate import allocate
22from asm.errors import AssemblyError
23
24
25_GRAMMAR_PATH = Path(__file__).parent.parent / "dfasm.lark"
26_parser: Optional[Lark] = None
27
28
29def _get_parser() -> Lark:
30 """Lazily initialize and cache the Lark parser."""
31 global _parser
32 if _parser is None:
33 _parser = Lark(
34 _GRAMMAR_PATH.read_text(),
35 parser="earley",
36 propagate_positions=True,
37 )
38 return _parser
39
40
41class PipelineStage(Enum):
42 """Enumeration of pipeline stages."""
43 PARSE_ERROR = "parse_error"
44 LOWER = "lower"
45 RESOLVE = "resolve"
46 PLACE = "place"
47 ALLOCATE = "allocate"
48
49
50@dataclass(frozen=True)
51class PipelineResult:
52 """Result of running the progressive pipeline.
53
54 Attributes:
55 graph: The IRGraph at the deepest successful stage, or None if parse failed
56 stage: The stage where progress stopped
57 errors: All AssemblyErrors accumulated from the pipeline
58 parse_error: String representation of parse error, if stage is PARSE_ERROR
59 """
60 graph: Optional[IRGraph]
61 stage: PipelineStage
62 errors: list[AssemblyError]
63 parse_error: Optional[str] = None
64
65
66def run_progressive(source: str) -> PipelineResult:
67 """Run the assembly pipeline progressively, capturing errors at each stage.
68
69 Unlike asm._run_pipeline() which raises on first error, this function runs
70 each pass independently and accumulates errors, allowing partial graphs to
71 be captured for visualization.
72
73 Pipeline stages:
74 1. Parse: Convert source to Lark CST (may raise lark.exceptions.UnexpectedInput)
75 2. Lower: CST to IRGraph (may accumulate errors but doesn't stop)
76 3. Resolve: Validate edge endpoints and scope (may accumulate errors)
77 4. Place: Validate/auto-place nodes on PEs (stops before this if resolve failed)
78 5. Allocate: Assign IRAM offsets and context slots (stops before this if place failed)
79
80 Args:
81 source: dfasm source code as a string
82
83 Returns:
84 PipelineResult containing the deepest graph, stage reached, and all errors
85 """
86 # Stage 1: Parse
87 try:
88 tree = _get_parser().parse(source)
89 except UnexpectedInput as exc:
90 return PipelineResult(
91 graph=None,
92 stage=PipelineStage.PARSE_ERROR,
93 errors=[],
94 parse_error=str(exc),
95 )
96
97 # Stage 2: Lower
98 graph = lower(tree)
99 stage = PipelineStage.LOWER
100
101 # Stage 3: Resolve
102 # Note: resolve() always runs after lower (matches asm._run_pipeline behaviour)
103 graph = resolve(graph)
104 stage = PipelineStage.RESOLVE
105
106 # Stage 4: Place (skip if resolve accumulated errors)
107 if not graph.errors:
108 graph = place(graph)
109 stage = PipelineStage.PLACE
110
111 # Stage 5: Allocate (skip if place accumulated errors)
112 if not graph.errors:
113 graph = allocate(graph)
114 stage = PipelineStage.ALLOCATE
115
116 return PipelineResult(
117 graph=graph,
118 stage=stage,
119 errors=list(graph.errors),
120 )