Skip to main content

snc_core/
ir.rs

1/// Lowered IR — the intermediate representation between AST and Rust codegen.
2///
3/// IR is a simplified, flat representation of the SNL program:
4/// - All names are resolved to indices
5/// - All types are concrete
6/// - No expression parsing needed (expressions stored as string fragments for now)
7
8/// Top-level IR for an entire SNL program.
9#[derive(Debug, Clone)]
10pub struct SeqIR {
11    pub program_name: String,
12    pub options: ProgramOptions,
13    pub channels: Vec<IRChannel>,
14    pub event_flags: Vec<IREventFlag>,
15    pub variables: Vec<IRVariable>,
16    pub state_sets: Vec<IRStateSet>,
17    pub entry_block: Option<IRBlock>,
18    pub exit_block: Option<IRBlock>,
19}
20
21#[derive(Debug, Clone, Default)]
22pub struct ProgramOptions {
23    pub safe_mode: bool,
24    pub reentrant: bool,
25    pub main_flag: bool,
26}
27
28#[derive(Debug, Clone)]
29pub struct IRChannel {
30    pub id: usize,
31    pub var_name: String,
32    pub pv_name: String,
33    pub var_type: IRType,
34    pub monitored: bool,
35    pub sync_ef: Option<usize>,
36}
37
38#[derive(Debug, Clone)]
39pub struct IREventFlag {
40    pub id: usize,
41    pub name: String,
42    pub synced_channels: Vec<usize>,
43}
44
45#[derive(Debug, Clone)]
46pub struct IRVariable {
47    pub name: String,
48    pub var_type: IRType,
49    /// If assigned to a channel, the channel id.
50    pub channel_id: Option<usize>,
51    pub init_value: Option<String>,
52}
53
54/// Supported variable types.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub enum IRType {
57    Int,
58    Short,
59    Long,
60    Float,
61    Double,
62    String,
63    Char,
64    Array {
65        element: Box<IRType>,
66        size: usize,
67    },
68}
69
70impl IRType {
71    /// Rust type name for codegen.
72    pub fn rust_type(&self) -> String {
73        match self {
74            IRType::Int => "i32".into(),
75            IRType::Short => "i16".into(),
76            IRType::Long => "i32".into(),
77            IRType::Float => "f32".into(),
78            IRType::Double => "f64".into(),
79            IRType::String => "String".into(),
80            IRType::Char => "u8".into(),
81            IRType::Array { element, size } => {
82                format!("[{}; {size}]", element.rust_type())
83            }
84        }
85    }
86
87    /// Default value expression for codegen.
88    pub fn default_value(&self) -> String {
89        match self {
90            IRType::Int | IRType::Short | IRType::Long => "0".into(),
91            IRType::Float => "0.0f32".into(),
92            IRType::Double => "0.0".into(),
93            IRType::String => "String::new()".into(),
94            IRType::Char => "0u8".into(),
95            IRType::Array { element, size } => {
96                format!("[{}; {size}]", element.default_value())
97            }
98        }
99    }
100
101    /// EpicsValue constructor for codegen.
102    pub fn to_epics_value_expr(&self, var_expr: &str) -> String {
103        match self {
104            IRType::Double => format!("EpicsValue::Double({var_expr})"),
105            IRType::Float => format!("EpicsValue::Float({var_expr})"),
106            IRType::Int | IRType::Long => format!("EpicsValue::Long({var_expr})"),
107            IRType::Short => format!("EpicsValue::Short({var_expr})"),
108            IRType::String => format!("EpicsValue::String({var_expr}.clone())"),
109            IRType::Char => format!("EpicsValue::Char({var_expr})"),
110            IRType::Array { element, .. } => {
111                match element.as_ref() {
112                    IRType::Double => format!("EpicsValue::DoubleArray({var_expr}.to_vec())"),
113                    IRType::Float => format!("EpicsValue::FloatArray({var_expr}.to_vec())"),
114                    IRType::Int | IRType::Long => format!("EpicsValue::LongArray({var_expr}.to_vec())"),
115                    IRType::Short => format!("EpicsValue::ShortArray({var_expr}.to_vec())"),
116                    _ => format!("EpicsValue::Double(0.0) /* unsupported array type */"),
117                }
118            }
119        }
120    }
121
122    /// Expression to extract from EpicsValue for codegen.
123    pub fn from_epics_value_expr(&self, val_expr: &str) -> String {
124        match self {
125            IRType::Double => format!("{val_expr}.to_f64().unwrap_or(0.0)"),
126            IRType::Float => format!("{val_expr}.to_f64().unwrap_or(0.0) as f32"),
127            IRType::Int | IRType::Long => format!("{val_expr}.to_f64().unwrap_or(0.0) as i32"),
128            IRType::Short => format!("{val_expr}.to_f64().unwrap_or(0.0) as i16"),
129            IRType::Char => format!("{val_expr}.to_f64().unwrap_or(0.0) as u8"),
130            IRType::String => format!("format!(\"{{}}\", {val_expr})"),
131            IRType::Array { element, size } => {
132                let elem_type = element.rust_type();
133                let elem_extract = match element.as_ref() {
134                    IRType::Double => format!(
135                        "{{ let arr = {val_expr}.to_f64_array(); let mut out = [0.0{elem_type}; {size}]; let n = arr.len().min({size}); out[..n].copy_from_slice(&arr[..n]); out }}"
136                    ),
137                    _ => format!(
138                        "[{default}; {size}] /* array extract not fully supported */",
139                        default = element.default_value()
140                    ),
141                };
142                elem_extract
143            }
144        }
145    }
146
147    /// Get the element type for arrays.
148    pub fn element_type(&self) -> Option<&IRType> {
149        match self {
150            IRType::Array { element, .. } => Some(element),
151            _ => None,
152        }
153    }
154}
155
156#[derive(Debug, Clone)]
157pub struct IRStateSet {
158    pub name: String,
159    pub id: usize,
160    pub local_vars: Vec<IRVariable>,
161    pub states: Vec<IRState>,
162}
163
164#[derive(Debug, Clone)]
165pub struct IRState {
166    pub name: String,
167    pub id: usize,
168    pub entry: Option<IRBlock>,
169    pub transitions: Vec<IRTransition>,
170    pub exit: Option<IRBlock>,
171}
172
173#[derive(Debug, Clone)]
174pub struct IRTransition {
175    /// Condition expression as Rust code string.
176    /// None means unconditional (always true).
177    pub condition: Option<String>,
178    /// Action block as Rust code string.
179    pub action: IRBlock,
180    /// Target state index, or None for `exit`.
181    pub target_state: Option<usize>,
182}
183
184/// A code block — stored as a string of Rust code for now.
185/// The codegen will emit this verbatim into the generated function.
186#[derive(Debug, Clone)]
187pub struct IRBlock {
188    pub code: String,
189}
190
191impl IRBlock {
192    pub fn new(code: impl Into<String>) -> Self {
193        Self { code: code.into() }
194    }
195}