1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright 2024-2026 Jonathan Shook
// SPDX-License-Identifier: Apache-2.0
//! Polydat compiler diagnostic event stream.
//!
//! The compiler emits typed events for each step: parsing, binding
//! resolution, module inlining, type adaptation, constant folding,
//! fusion, and compilation level selection.
//!
//! Events are tagged with severity levels:
//! - **Info**: normal compilation steps (parsed, resolved, folded)
//! - **Advisory**: type coercions, widenings, and implicit conversions
//! that the user should be aware of for module design quality
//! - **Warning**: potential performance or correctness issues
//! - **Error**: compilation failures (surfaced as Result::Err, not events)
/// Severity level for compiler diagnostic events.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EventLevel {
/// Normal compilation step — informational only.
Info,
/// Design advisory — implicit conversion or coercion that the user
/// should review for module quality. Query with `--diagnose`.
Advisory,
/// Potential performance or correctness issue.
Warning,
}
/// A diagnostic event from the Polydat compilation pipeline.
#[derive(Debug, Clone)]
pub enum CompileEvent {
/// DSL source parsed into AST.
Parsed { statements: usize },
/// A binding was resolved from DSL to a node.
BindingResolved { name: String, node_type: String },
/// A module was loaded and inlined.
ModuleInlined { name: String, nodes_added: usize },
/// A legacy binding chain was translated to Polydat source.
LegacyTranslated { name: String, polydat_expr: String },
/// Type adapter inserted between mismatched ports.
TypeAdapterInserted { from_node: String, to_node: String, adapter: String },
/// Init-time constant folded (SRD 44).
ConstantFolded { node: String, value: String },
/// Fusion pattern matched and applied (SRD 36).
FusionApplied { pattern: String, nodes_replaced: usize },
/// Output declared.
OutputDeclared { name: String },
/// Compilation level selected for a node.
CompileLevelSelected { node: String, level: String },
/// Workload parameter injected as constant.
ParamInjected { name: String, value: String },
/// Config wire connected to a cycle-time source (performance warning).
ConfigWireCycleWarning { node: String, port: String },
/// Auto-widening type coercion inserted by the compiler.
TypeWidening { from: &'static str, to: &'static str, context: String },
/// Warning during compilation.
Warning { message: String },
/// Summary of the compiled program.
Summary { nodes: usize, outputs: usize, constants_folded: usize },
/// A module-level pragma was acknowledged. Recorded once per
/// recognised `// @pragma: <name>` directive at the top of the
/// source. Lets `--diagnose` show which graph transforms the
/// module asked for.
PragmaAcknowledged { name: String, line: usize },
/// An unrecognised module-level pragma was seen. Pragmas are
/// forward-compatible: an old binary parses a newer module
/// that opts into features it doesn't support, and the only
/// effect is this advisory.
UnknownPragma { name: String, line: usize },
/// Strict-wire mode auto-inserted an assertion node between
/// `from_node` and `to_node`. SRD 15 §"Strict Wire Mode".
AssertionInserted {
from_node: String,
to_node: String,
kind: String,
},
/// Strict-wire mode considered inserting an assertion but
/// proved it redundant. The reason field names which skip
/// rule applied (constant source, upstream assertion, etc.).
AssertionSkipped {
from_node: String,
to_node: String,
reason: String,
},
}
impl CompileEvent {
/// The severity level of this event.
pub fn level(&self) -> EventLevel {
match self {
// Info: normal steps
CompileEvent::Parsed { .. } => EventLevel::Info,
CompileEvent::BindingResolved { .. } => EventLevel::Info,
CompileEvent::ModuleInlined { .. } => EventLevel::Info,
CompileEvent::OutputDeclared { .. } => EventLevel::Info,
CompileEvent::CompileLevelSelected { .. } => EventLevel::Info,
CompileEvent::ParamInjected { .. } => EventLevel::Info,
CompileEvent::ConstantFolded { .. } => EventLevel::Info,
CompileEvent::FusionApplied { .. } => EventLevel::Info,
CompileEvent::Summary { .. } => EventLevel::Info,
// Advisory: implicit conversions the user should review
CompileEvent::TypeAdapterInserted { .. } => EventLevel::Advisory,
CompileEvent::TypeWidening { .. } => EventLevel::Advisory,
CompileEvent::LegacyTranslated { .. } => EventLevel::Advisory,
CompileEvent::PragmaAcknowledged { .. } => EventLevel::Advisory,
CompileEvent::AssertionInserted { .. } => EventLevel::Advisory,
CompileEvent::AssertionSkipped { .. } => EventLevel::Advisory,
// Warning: potential issues
CompileEvent::ConfigWireCycleWarning { .. } => EventLevel::Warning,
CompileEvent::Warning { .. } => EventLevel::Warning,
CompileEvent::UnknownPragma { .. } => EventLevel::Warning,
}
}
}
/// Collects diagnostic events during compilation.
#[derive(Debug, Default)]
pub struct CompileEventLog {
events: Vec<CompileEvent>,
}
impl CompileEventLog {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn push(&mut self, event: CompileEvent) {
self.events.push(event);
}
pub fn events(&self) -> &[CompileEvent] {
&self.events
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
/// Return only advisory-level events (type coercions, widenings).
/// These are the "module design quality" messages users query with --diagnose.
pub fn advisories(&self) -> Vec<&CompileEvent> {
self.events.iter().filter(|e| e.level() == EventLevel::Advisory).collect()
}
/// Return only warning-level events.
pub fn warnings(&self) -> Vec<&CompileEvent> {
self.events.iter().filter(|e| e.level() == EventLevel::Warning).collect()
}
/// Format all events as human-readable diagnostic lines.
/// Each line is prefixed with the severity tag.
pub fn format(&self) -> String {
self.events.iter().map(|e| {
let tag = match e.level() {
EventLevel::Info => "info",
EventLevel::Advisory => "advisory",
EventLevel::Warning => "warning",
};
let msg = match e {
CompileEvent::Parsed { statements } =>
format!("parsed {statements} statement(s)"),
CompileEvent::BindingResolved { name, node_type } =>
format!("resolved '{name}' → {node_type}"),
CompileEvent::ModuleInlined { name, nodes_added } =>
format!("module '{name}' inlined ({nodes_added} nodes)"),
CompileEvent::LegacyTranslated { name, polydat_expr } =>
format!("legacy '{name}' → {polydat_expr}"),
CompileEvent::TypeAdapterInserted { from_node, to_node, adapter } =>
format!("type adapter {adapter}: {from_node} → {to_node}"),
CompileEvent::ConstantFolded { node, value } =>
format!("constant folded: {node} → {value}"),
CompileEvent::FusionApplied { pattern, nodes_replaced } =>
format!("fusion: {pattern} ({nodes_replaced} nodes replaced)"),
CompileEvent::OutputDeclared { name } =>
format!("output '{name}'"),
CompileEvent::CompileLevelSelected { node, level } =>
format!("{node} → {level}"),
CompileEvent::ParamInjected { name, value } =>
format!("param '{name}' = {value}"),
CompileEvent::ConfigWireCycleWarning { node, port } =>
format!("config wire '{port}' on '{node}' connected to cycle-time source"),
CompileEvent::TypeWidening { from, to, context } =>
format!("widening {from} → {to} in {context}"),
CompileEvent::Warning { message } =>
message.to_string(),
CompileEvent::Summary { nodes, outputs, constants_folded } =>
format!("{nodes} nodes, {outputs} outputs, {constants_folded} constant(s) folded"),
CompileEvent::PragmaAcknowledged { name, line } =>
format!("pragma '{name}' acknowledged (line {line})"),
CompileEvent::UnknownPragma { name, line } =>
format!("unknown pragma '{name}' at line {line}; ignored"),
CompileEvent::AssertionInserted { from_node, to_node, kind } =>
format!("assertion inserted: {from_node} → {to_node} ({kind})"),
CompileEvent::AssertionSkipped { from_node, to_node, reason } =>
format!("assertion skipped: {from_node} → {to_node} ({reason})"),
};
format!("polydat[{tag}]: {msg}")
}).collect::<Vec<_>>().join("\n")
}
}