1use std::collections::HashMap;
2
3use crate::ast::{self, *};
4use crate::error::{CompileError, CompileResult};
5use crate::ir::*;
6
7pub fn analyze(program: &Program) -> CompileResult<SeqIR> {
9 let mut analyzer = Analyzer::new();
10 analyzer.analyze_program(program)
11}
12
13struct Analyzer {
14 channels: Vec<IRChannel>,
15 event_flags: Vec<IREventFlag>,
16 variables: Vec<IRVariable>,
17 var_to_channel: HashMap<String, usize>,
19 var_types: HashMap<String, IRType>,
20 ef_name_to_id: HashMap<String, usize>,
21 sync_map: HashMap<String, String>,
23 options: ProgramOptions,
25}
26
27impl Analyzer {
28 fn new() -> Self {
29 Self {
30 channels: Vec::new(),
31 event_flags: Vec::new(),
32 variables: Vec::new(),
33 var_to_channel: HashMap::new(),
34 var_types: HashMap::new(),
35 ef_name_to_id: HashMap::new(),
36 sync_map: HashMap::new(),
37 options: ProgramOptions::default(),
38 }
39 }
40
41 fn analyze_program(&mut self, program: &Program) -> CompileResult<SeqIR> {
42 for opt in &program.options {
44 match opt {
45 ProgramOption::Safe => self.options.safe_mode = true,
46 ProgramOption::Reentrant => self.options.reentrant = true,
47 ProgramOption::Main => self.options.main_flag = true,
48 }
49 }
50
51 for def in &program.definitions {
53 match def {
54 Definition::VarDecl(vd) => self.collect_var_decl(vd)?,
55 Definition::EvFlag(ef) => self.collect_evflag(ef)?,
56 Definition::Assign(a) => self.collect_assign(a)?,
57 Definition::Monitor(m) => self.collect_monitor(m)?,
58 Definition::Sync(s) => self.collect_sync(s)?,
59 Definition::Option(_) | Definition::CPreprocessor(_) | Definition::EmbeddedCode(_) => {}
60 }
61 }
62
63 self.resolve_sync()?;
65
66 let mut state_sets = Vec::new();
68 for (ss_id, ss) in program.state_sets.iter().enumerate() {
69 state_sets.push(self.analyze_state_set(ss, ss_id)?);
70 }
71
72 Ok(SeqIR {
73 program_name: program.name.clone(),
74 options: self.options.clone(),
75 channels: self.channels.clone(),
76 event_flags: self.event_flags.clone(),
77 variables: self.variables.clone(),
78 state_sets,
79 entry_block: program.entry.as_ref().map(|b| self.lower_block(b)),
80 exit_block: program.exit.as_ref().map(|b| self.lower_block(b)),
81 })
82 }
83
84 fn collect_var_decl(&mut self, vd: &VarDecl) -> CompileResult<()> {
85 let mut ir_type = self.convert_type(&vd.type_spec)?;
86 for dim_expr in vd.dimensions.iter().rev() {
88 let size = self.eval_const_expr(dim_expr);
89 ir_type = IRType::Array {
90 element: Box::new(ir_type),
91 size,
92 };
93 }
94 let init_value = vd.init.as_ref().map(|e| self.expr_to_rust(e));
95 self.var_types.insert(vd.name.clone(), ir_type.clone());
96 self.variables.push(IRVariable {
97 name: vd.name.clone(),
98 var_type: ir_type,
99 channel_id: None,
100 init_value,
101 });
102 Ok(())
103 }
104
105 fn eval_const_expr(&self, expr: &Expr) -> usize {
107 match expr {
108 Expr::IntLit(v, _) => *v as usize,
109 _ => 0, }
111 }
112
113 fn collect_evflag(&mut self, ef: &EvFlagDecl) -> CompileResult<()> {
114 let id = self.event_flags.len();
115 self.ef_name_to_id.insert(ef.name.clone(), id);
116 self.event_flags.push(IREventFlag {
117 id,
118 name: ef.name.clone(),
119 synced_channels: Vec::new(),
120 });
121 Ok(())
122 }
123
124 fn collect_assign(&mut self, assign: &Assign) -> CompileResult<()> {
125 let pv_name = assign
126 .pv_name
127 .as_deref()
128 .unwrap_or(&assign.var_name)
129 .to_string();
130
131 let ch_id = self.channels.len();
132 let var_type = self
133 .var_types
134 .get(&assign.var_name)
135 .cloned()
136 .ok_or_else(|| {
137 CompileError::semantic(
138 assign.span.clone(),
139 format!("assign: undefined variable '{}'", assign.var_name),
140 )
141 })?;
142
143 self.channels.push(IRChannel {
144 id: ch_id,
145 var_name: assign.var_name.clone(),
146 pv_name,
147 var_type,
148 monitored: false,
149 sync_ef: None,
150 });
151
152 self.var_to_channel.insert(assign.var_name.clone(), ch_id);
153
154 if let Some(var) = self.variables.iter_mut().find(|v| v.name == assign.var_name) {
156 var.channel_id = Some(ch_id);
157 }
158
159 Ok(())
160 }
161
162 fn collect_monitor(&mut self, monitor: &Monitor) -> CompileResult<()> {
163 if let Some(&ch_id) = self.var_to_channel.get(&monitor.var_name) {
164 self.channels[ch_id].monitored = true;
165 Ok(())
166 } else {
167 Err(CompileError::semantic(
168 monitor.span.clone(),
169 format!(
170 "monitor: variable '{}' is not assigned to a PV",
171 monitor.var_name
172 ),
173 ))
174 }
175 }
176
177 fn collect_sync(&mut self, sync: &Sync) -> CompileResult<()> {
178 self.sync_map
179 .insert(sync.var_name.clone(), sync.ef_name.clone());
180 Ok(())
181 }
182
183 fn resolve_sync(&mut self) -> CompileResult<()> {
184 for (var_name, ef_name) in &self.sync_map {
185 let ef_id = self
186 .ef_name_to_id
187 .get(ef_name)
188 .copied()
189 .ok_or_else(|| {
190 CompileError::Other(format!("sync: undefined event flag '{ef_name}'"))
191 })?;
192
193 let ch_id = self.var_to_channel.get(var_name).copied().ok_or_else(|| {
194 CompileError::Other(format!(
195 "sync: variable '{var_name}' is not assigned to a PV"
196 ))
197 })?;
198
199 self.channels[ch_id].sync_ef = Some(ef_id);
200 self.event_flags[ef_id].synced_channels.push(ch_id);
201 }
202 Ok(())
203 }
204
205 fn analyze_state_set(&self, ss: &StateSet, ss_id: usize) -> CompileResult<IRStateSet> {
206 let local_vars: Vec<IRVariable> = ss
207 .local_vars
208 .iter()
209 .map(|vd| {
210 let mut ir_type = self.convert_type(&vd.type_spec).unwrap();
211 for dim_expr in vd.dimensions.iter().rev() {
212 let size = self.eval_const_expr(dim_expr);
213 ir_type = IRType::Array {
214 element: Box::new(ir_type),
215 size,
216 };
217 }
218 IRVariable {
219 name: vd.name.clone(),
220 var_type: ir_type,
221 channel_id: None,
222 init_value: vd.init.as_ref().map(|e| self.expr_to_rust(e)),
223 }
224 })
225 .collect();
226
227 let mut states = Vec::new();
228 let state_name_to_id: HashMap<String, usize> = ss
229 .states
230 .iter()
231 .enumerate()
232 .map(|(i, s)| (s.name.clone(), i))
233 .collect();
234
235 for (state_id, state) in ss.states.iter().enumerate() {
236 let transitions = state
237 .transitions
238 .iter()
239 .map(|t| {
240 let condition = t.condition.as_ref().map(|e| self.condition_to_rust(e));
241 let action = self.lower_block(&t.body);
242 let target_state = match &t.target {
243 TransitionTarget::State(name) => {
244 Some(*state_name_to_id.get(name).unwrap_or(&0))
245 }
246 TransitionTarget::Exit => None,
247 };
248 IRTransition {
249 condition,
250 action,
251 target_state,
252 }
253 })
254 .collect();
255
256 states.push(IRState {
257 name: state.name.clone(),
258 id: state_id,
259 entry: state.entry.as_ref().map(|b| self.lower_block(b)),
260 transitions,
261 exit: state.exit.as_ref().map(|b| self.lower_block(b)),
262 });
263 }
264
265 Ok(IRStateSet {
266 name: ss.name.clone(),
267 id: ss_id,
268 local_vars,
269 states,
270 })
271 }
272
273 fn lower_block(&self, block: &ast::Block) -> IRBlock {
274 let mut code = String::new();
275 for stmt in &block.stmts {
276 code.push_str(&self.stmt_to_rust(stmt));
277 }
278 IRBlock { code }
279 }
280
281 fn condition_to_rust(&self, expr: &Expr) -> String {
284 self.expr_to_rust(expr)
285 }
286
287 fn expr_to_rust(&self, expr: &Expr) -> String {
288 match expr {
289 Expr::IntLit(v, _) => v.to_string(),
290 Expr::FloatLit(v, _) => format_float(*v),
291 Expr::StringLit(s, _) => format!("\"{s}\""),
292 Expr::Ident(name, _) => self.ident_to_rust(name),
293 Expr::BinaryOp(lhs, op, rhs, _) => {
294 let l = self.expr_to_rust(lhs);
295 let r = self.expr_to_rust(rhs);
296 let op_str = binop_to_rust(*op);
297 format!("{l} {op_str} {r}")
298 }
299 Expr::UnaryOp(op, e, _) => {
300 let e = self.expr_to_rust(e);
301 let op_str = match op {
302 UnaryOp::Neg => "-",
303 UnaryOp::Not => "!",
304 UnaryOp::BitNot => "!",
305 };
306 format!("{op_str}{e}")
307 }
308 Expr::Call(name, args, _) => {
309 let args_str: Vec<String> = args.iter().map(|a| self.expr_to_rust(a)).collect();
310 self.call_to_rust(name, &args_str)
311 }
312 Expr::Assign(lhs, rhs, _) => {
313 let l = self.expr_to_rust(lhs);
314 let r = self.expr_to_rust(rhs);
315 format!("{l} = {r}")
316 }
317 Expr::CompoundAssign(lhs, op, rhs, _) => {
318 let l = self.expr_to_rust(lhs);
319 let r = self.expr_to_rust(rhs);
320 let op_str = binop_to_rust(*op);
321 format!("{l} {op_str}= {r}")
322 }
323 Expr::Paren(e, _) => {
324 let inner = self.expr_to_rust(e);
325 format!("({inner})")
326 }
327 Expr::PostIncr(e, _) => {
328 let e = self.expr_to_rust(e);
329 format!("{{ {e} += 1; {e} - 1 }}")
330 }
331 Expr::PostDecr(e, _) => {
332 let e = self.expr_to_rust(e);
333 format!("{{ {e} -= 1; {e} + 1 }}")
334 }
335 Expr::PreIncr(e, _) => {
336 let e = self.expr_to_rust(e);
337 format!("{{ {e} += 1; {e} }}")
338 }
339 Expr::PreDecr(e, _) => {
340 let e = self.expr_to_rust(e);
341 format!("{{ {e} -= 1; {e} }}")
342 }
343 Expr::Ternary(cond, then_e, else_e, _) => {
344 let c = self.expr_to_rust(cond);
345 let t = self.expr_to_rust(then_e);
346 let e = self.expr_to_rust(else_e);
347 format!("if {c} {{ {t} }} else {{ {e} }}")
348 }
349 Expr::Field(e, field, _) => {
350 let e = self.expr_to_rust(e);
351 format!("{e}.{field}")
352 }
353 Expr::Index(e, idx, _) => {
354 let e = self.expr_to_rust(e);
355 let i = self.expr_to_rust(idx);
356 format!("{e}[{i}]")
357 }
358 Expr::Cast(_, e, _) => self.expr_to_rust(e),
359 Expr::ArrayInit(elements, _) => {
360 let elems: Vec<String> = elements.iter().map(|e| self.expr_to_rust(e)).collect();
361 format!("[{}]", elems.join(", "))
362 }
363 }
364 }
365
366 fn ident_to_rust(&self, name: &str) -> String {
367 if self.var_to_channel.contains_key(name) || self.var_types.contains_key(name) {
369 format!("ctx.local_vars.{name}")
370 } else {
371 name.to_string()
372 }
373 }
374
375 fn resolve_comp_type(&self, args: &[String], default: &str) -> String {
376 if args.len() >= 2 {
377 let comp_arg = args[1].trim();
378 match comp_arg {
379 "ASYNC" | "ctx.local_vars.ASYNC" => "CompType::Async".to_string(),
380 "SYNC" | "ctx.local_vars.SYNC" => "CompType::Sync".to_string(),
381 "DEFAULT" | "ctx.local_vars.DEFAULT" => "CompType::Default".to_string(),
382 _ => default.to_string(),
383 }
384 } else {
385 default.to_string()
386 }
387 }
388
389 fn resolve_var_name<'a>(&'a self, args: &'a [String]) -> &'a str {
390 args.first()
391 .map(|a| a.strip_prefix("ctx.local_vars.").unwrap_or(a))
392 .unwrap_or("")
393 }
394
395 fn call_to_rust(&self, name: &str, args: &[String]) -> String {
396 match name {
397 "delay" => format!("ctx.delay({})", args.join(", ")),
398 "pvGet" => {
399 let var_name = self.resolve_var_name(args);
400 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
401 let comp = self.resolve_comp_type(args, "CompType::Default");
402 format!("ctx.pv_get({ch_id}, {comp}).await")
403 } else {
404 format!("/* pvGet({}) - unresolved */", args.join(", "))
405 }
406 }
407 "pvPut" => {
408 let var_name = self.resolve_var_name(args);
409 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
410 let comp = self.resolve_comp_type(args, "CompType::Default");
411 format!("ctx.pv_put({ch_id}, {comp}).await")
412 } else {
413 format!("/* pvPut({}) - unresolved */", args.join(", "))
414 }
415 }
416 "pvGetComplete" => {
417 let var_name = self.resolve_var_name(args);
418 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
419 format!("ctx.pv_get_complete({ch_id}).await")
420 } else {
421 format!("/* pvGetComplete({}) - unresolved */", args.join(", "))
422 }
423 }
424 "pvPutComplete" => {
425 let var_name = self.resolve_var_name(args);
426 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
427 format!("ctx.pv_put_complete({ch_id}).await")
428 } else {
429 format!("/* pvPutComplete({}) - unresolved */", args.join(", "))
430 }
431 }
432 "pvGetCancel" => {
433 let var_name = self.resolve_var_name(args);
434 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
435 format!("ctx.pv_get_cancel({ch_id}).await")
436 } else {
437 format!("/* pvGetCancel({}) - unresolved */", args.join(", "))
438 }
439 }
440 "pvPutCancel" => {
441 let var_name = self.resolve_var_name(args);
442 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
443 format!("ctx.pv_put_cancel({ch_id}).await")
444 } else {
445 format!("/* pvPutCancel({}) - unresolved */", args.join(", "))
446 }
447 }
448 "pvStatus" => {
449 let var_name = self.resolve_var_name(args);
450 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
451 format!("ctx.pv_status({ch_id})")
452 } else {
453 format!("/* pvStatus({}) - unresolved */", args.join(", "))
454 }
455 }
456 "pvSeverity" => {
457 let var_name = self.resolve_var_name(args);
458 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
459 format!("ctx.pv_severity({ch_id})")
460 } else {
461 format!("/* pvSeverity({}) - unresolved */", args.join(", "))
462 }
463 }
464 "pvMessage" => {
465 let var_name = self.resolve_var_name(args);
466 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
467 format!("ctx.pv_message({ch_id})")
468 } else {
469 format!("/* pvMessage({}) - unresolved */", args.join(", "))
470 }
471 }
472 "pvAssignCount" => {
473 format!("{}", self.channels.len())
474 }
475 "pvMonitorCount" => {
476 let count = self.channels.iter().filter(|ch| ch.monitored).count();
477 format!("{count}")
478 }
479 "efSet" => {
480 let ef_name = self.resolve_var_name(args);
481 if let Some(&ef_id) = self.ef_name_to_id.get(ef_name) {
482 format!("ctx.ef_set({ef_id})")
483 } else {
484 format!("/* efSet({}) - unresolved */", args.join(", "))
485 }
486 }
487 "efTest" => {
488 let ef_name = self.resolve_var_name(args);
489 if let Some(&ef_id) = self.ef_name_to_id.get(ef_name) {
490 format!("ctx.ef_test({ef_id})")
491 } else {
492 format!("/* efTest({}) - unresolved */", args.join(", "))
493 }
494 }
495 "efClear" => {
496 let ef_name = self.resolve_var_name(args);
497 if let Some(&ef_id) = self.ef_name_to_id.get(ef_name) {
498 format!("ctx.ef_clear({ef_id})")
499 } else {
500 format!("/* efClear({}) - unresolved */", args.join(", "))
501 }
502 }
503 "efTestAndClear" => {
504 let ef_name = self.resolve_var_name(args);
505 if let Some(&ef_id) = self.ef_name_to_id.get(ef_name) {
506 format!("ctx.ef_test_and_clear({ef_id})")
507 } else {
508 format!("/* efTestAndClear({}) - unresolved */", args.join(", "))
509 }
510 }
511 "pvConnected" => {
512 let var_name = self.resolve_var_name(args);
513 if let Some(&ch_id) = self.var_to_channel.get(var_name) {
514 format!("ctx.pv_connected({ch_id})")
515 } else {
516 format!("/* pvConnected({}) - unresolved */", args.join(", "))
517 }
518 }
519 "pvConnectCount" => "ctx.pv_connect_count()".to_string(),
520 "pvChannelCount" => "ctx.pv_channel_count()".to_string(),
521 _ => format!("{name}({})", args.join(", ")),
522 }
523 }
524
525 fn stmt_to_rust(&self, stmt: &Stmt) -> String {
526 match stmt {
527 Stmt::Expr(e) => format!("{};\n", self.expr_to_rust(e)),
528 Stmt::VarDecl(vd) => {
529 let mut ir_type = self.convert_type(&vd.type_spec).unwrap();
530 for dim_expr in vd.dimensions.iter().rev() {
531 let size = self.eval_const_expr(dim_expr);
532 ir_type = IRType::Array {
533 element: Box::new(ir_type),
534 size,
535 };
536 }
537 let init = vd
538 .init
539 .as_ref()
540 .map(|e| self.expr_to_rust(e))
541 .unwrap_or_else(|| ir_type.default_value());
542 let rust_type = ir_type.rust_type();
543 format!("let mut {}: {rust_type} = {init};\n", vd.name)
544 }
545 Stmt::If(cond, then_b, else_b) => {
546 let c = self.expr_to_rust(cond);
547 let t = self.block_to_rust(then_b);
548 if let Some(else_block) = else_b {
549 let e = self.block_to_rust(else_block);
550 format!("if {c} {{\n{t}}} else {{\n{e}}}\n")
551 } else {
552 format!("if {c} {{\n{t}}}\n")
553 }
554 }
555 Stmt::While(cond, body) => {
556 let c = self.expr_to_rust(cond);
557 let b = self.block_to_rust(body);
558 format!("while {c} {{\n{b}}}\n")
559 }
560 Stmt::For(init, cond, step, body) => {
561 let mut code = String::new();
563 if let Some(init) = init {
564 code.push_str(&format!("{};\n", self.expr_to_rust(init)));
565 }
566 let cond_str = cond
567 .as_ref()
568 .map(|c| self.expr_to_rust(c))
569 .unwrap_or_else(|| "true".to_string());
570 let body_str = self.block_to_rust(body);
571 let step_str = step
572 .as_ref()
573 .map(|s| format!("{};\n", self.expr_to_rust(s)))
574 .unwrap_or_default();
575 code.push_str(&format!(
576 "while {cond_str} {{\n{body_str}{step_str}}}\n"
577 ));
578 code
579 }
580 Stmt::Break => "break;\n".to_string(),
581 Stmt::Return(val) => match val {
582 Some(e) => format!("return {};\n", self.expr_to_rust(e)),
583 None => "return;\n".to_string(),
584 },
585 Stmt::Block(b) => {
586 let inner = self.block_to_rust(b);
587 format!("{{\n{inner}}}\n")
588 }
589 Stmt::EmbeddedCode(code) => format!("{code}\n"),
590 }
591 }
592
593 fn block_to_rust(&self, block: &ast::Block) -> String {
594 let mut code = String::new();
595 for stmt in &block.stmts {
596 code.push_str(&self.stmt_to_rust(stmt));
597 }
598 code
599 }
600
601 fn convert_type(&self, ts: &TypeSpec) -> CompileResult<IRType> {
602 match ts {
603 TypeSpec::Int => Ok(IRType::Int),
604 TypeSpec::Short => Ok(IRType::Short),
605 TypeSpec::Long => Ok(IRType::Long),
606 TypeSpec::Float => Ok(IRType::Float),
607 TypeSpec::Double => Ok(IRType::Double),
608 TypeSpec::String => Ok(IRType::String),
609 TypeSpec::Char => Ok(IRType::Char),
610 TypeSpec::Unsigned(inner) => self.convert_type(inner), }
612 }
613}
614
615fn binop_to_rust(op: BinOp) -> &'static str {
616 match op {
617 BinOp::Add => "+",
618 BinOp::Sub => "-",
619 BinOp::Mul => "*",
620 BinOp::Div => "/",
621 BinOp::Mod => "%",
622 BinOp::Eq => "==",
623 BinOp::Ne => "!=",
624 BinOp::Lt => "<",
625 BinOp::Le => "<=",
626 BinOp::Gt => ">",
627 BinOp::Ge => ">=",
628 BinOp::And => "&&",
629 BinOp::Or => "||",
630 BinOp::BitAnd => "&",
631 BinOp::BitOr => "|",
632 BinOp::BitXor => "^",
633 BinOp::Shl => "<<",
634 BinOp::Shr => ">>",
635 }
636}
637
638fn format_float(v: f64) -> String {
639 let s = v.to_string();
640 if s.contains('.') || s.contains('e') || s.contains('E') {
641 s
642 } else {
643 format!("{s}.0")
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use crate::lexer::Lexer;
651 use crate::parser::Parser;
652
653 fn analyze_str(input: &str) -> SeqIR {
654 let tokens = Lexer::new(input).tokenize().unwrap();
655 let ast = Parser::new(tokens).parse_program().unwrap();
656 analyze(&ast).unwrap()
657 }
658
659 #[test]
660 fn test_basic_analysis() {
661 let ir = analyze_str(r#"
662 program test
663 option +s;
664 double x;
665 assign x to "PV:x";
666 monitor x;
667 evflag ef_x;
668 sync x to ef_x;
669 ss s1 {
670 state init {
671 when (efTestAndClear(ef_x)) {
672 x += 1.0;
673 pvPut(x);
674 } state init
675 when (delay(10.0)) {
676 } exit
677 }
678 }
679 "#);
680
681 assert_eq!(ir.program_name, "test");
682 assert!(ir.options.safe_mode);
683 assert_eq!(ir.channels.len(), 1);
684 assert_eq!(ir.channels[0].var_name, "x");
685 assert_eq!(ir.channels[0].pv_name, "PV:x");
686 assert!(ir.channels[0].monitored);
687 assert_eq!(ir.channels[0].sync_ef, Some(0));
688 assert_eq!(ir.event_flags.len(), 1);
689 assert_eq!(ir.event_flags[0].synced_channels, vec![0]);
690 assert_eq!(ir.state_sets.len(), 1);
691 assert_eq!(ir.state_sets[0].states.len(), 1);
692 assert_eq!(ir.state_sets[0].states[0].transitions.len(), 2);
693 }
694
695 #[test]
696 fn test_builtin_translation() {
697 let ir = analyze_str(r#"
698 program test
699 double v;
700 assign v to "PV:v";
701 monitor v;
702 evflag ef_v;
703 sync v to ef_v;
704 ss s1 {
705 state init {
706 when (delay(1.0)) {
707 pvGet(v);
708 pvPut(v);
709 efSet(ef_v);
710 efClear(ef_v);
711 } state init
712 }
713 }
714 "#);
715
716 let trans = &ir.state_sets[0].states[0].transitions[0];
717 assert_eq!(trans.condition.as_deref(), Some("ctx.delay(1.0)"));
718 assert!(trans.action.code.contains("ctx.pv_get(0, CompType::Default).await"));
719 assert!(trans.action.code.contains("ctx.pv_put(0, CompType::Default).await"));
720 assert!(trans.action.code.contains("ctx.ef_set(0)"));
721 assert!(trans.action.code.contains("ctx.ef_clear(0)"));
722 }
723
724 #[test]
725 fn test_async_builtins() {
726 let ir = analyze_str(r#"
727 program test
728 double v;
729 assign v to "PV:v";
730 ss s1 {
731 state init {
732 when (delay(1.0)) {
733 pvGet(v, ASYNC);
734 pvPut(v, SYNC);
735 } state wait
736 when (pvGetComplete(v)) {
737 } state done
738 }
739 state wait {
740 when (pvGetComplete(v)) {
741 } state done
742 }
743 state done {
744 when () { } exit
745 }
746 }
747 "#);
748
749 let trans = &ir.state_sets[0].states[0].transitions[0];
750 assert!(trans.action.code.contains("ctx.pv_get(0, CompType::Async).await"));
751 assert!(trans.action.code.contains("ctx.pv_put(0, CompType::Sync).await"));
752
753 let trans2 = &ir.state_sets[0].states[0].transitions[1];
754 assert_eq!(
755 trans2.condition.as_deref(),
756 Some("ctx.pv_get_complete(0).await")
757 );
758 }
759
760 #[test]
761 fn test_pv_status_builtins() {
762 let ir = analyze_str(r#"
763 program test
764 double v;
765 assign v to "PV:v";
766 monitor v;
767 ss s1 {
768 state init {
769 when (delay(1.0)) {
770 pvGet(v);
771 pvStatus(v);
772 pvSeverity(v);
773 pvMessage(v);
774 } state init
775 }
776 }
777 "#);
778
779 let trans = &ir.state_sets[0].states[0].transitions[0];
780 assert!(trans.action.code.contains("ctx.pv_status(0)"));
781 assert!(trans.action.code.contains("ctx.pv_severity(0)"));
782 assert!(trans.action.code.contains("ctx.pv_message(0)"));
783 }
784
785 #[test]
786 fn test_pv_assign_monitor_count() {
787 let ir = analyze_str(r#"
788 program test
789 double a;
790 double b;
791 assign a to "PV:a";
792 assign b to "PV:b";
793 monitor a;
794 ss s1 {
795 state init {
796 when (delay(1.0)) {
797 pvAssignCount();
798 pvMonitorCount();
799 } state init
800 }
801 }
802 "#);
803
804 let trans = &ir.state_sets[0].states[0].transitions[0];
805 assert!(trans.action.code.contains("2"));
807 assert!(trans.action.code.contains("1"));
809 }
810
811 #[test]
812 fn test_multi_state_set() {
813 let ir = analyze_str(r#"
814 program test
815 double counter;
816 assign counter to "PV:counter";
817 int light;
818 assign light to "PV:light";
819 ss counter_ss {
820 state counting {
821 when (delay(1.0)) {
822 counter += 1.0;
823 pvPut(counter);
824 } state counting
825 }
826 }
827 ss light_ss {
828 state idle {
829 when (delay(15.0)) {
830 } exit
831 }
832 }
833 "#);
834
835 assert_eq!(ir.state_sets.len(), 2);
836 assert_eq!(ir.channels.len(), 2);
837 assert_eq!(ir.variables.len(), 2);
838 }
839
840 #[test]
841 fn test_array_variable() {
842 let ir = analyze_str(r#"
843 program test
844 double arr[5];
845 ss s1 {
846 state init {
847 when () { } exit
848 }
849 }
850 "#);
851
852 assert_eq!(ir.variables.len(), 1);
853 assert!(matches!(
854 &ir.variables[0].var_type,
855 IRType::Array { element, size: 5 } if **element == IRType::Double
856 ));
857 assert_eq!(ir.variables[0].var_type.rust_type(), "[f64; 5]");
858 assert_eq!(ir.variables[0].var_type.default_value(), "[0.0; 5]");
859 }
860
861 #[test]
862 fn test_array_with_brace_init() {
863 let ir = analyze_str(r#"
864 program test
865 int vals[3] = {10, 20, 30};
866 ss s1 {
867 state init {
868 when () { } exit
869 }
870 }
871 "#);
872
873 assert_eq!(ir.variables[0].init_value, Some("[10, 20, 30]".to_string()));
874 }
875
876 #[test]
877 fn test_local_vars() {
878 let ir = analyze_str(r#"
879 program test
880 ss s1 {
881 int n = 0;
882 double avg = 0.0;
883 state init {
884 when () { } exit
885 }
886 }
887 "#);
888
889 assert_eq!(ir.state_sets[0].local_vars.len(), 2);
890 assert_eq!(ir.state_sets[0].local_vars[0].name, "n");
891 assert_eq!(ir.state_sets[0].local_vars[0].init_value, Some("0".to_string()));
892 }
893}