//! Abstract syntax tree for IEC 61131-3 Structured Text (ST). //! //! This is the security-relevant subset: POUs with their variable declarations //! and statement bodies, enough to run semantic control-logic rules over. It is //! deliberately not a full language model — declarations we don't reason about //! (e.g. exotic type definitions) are parsed loosely and kept as raw text. /// A Program Organization Unit: a PROGRAM, FUNCTION, or FUNCTION_BLOCK. #[derive(Debug, Clone)] pub struct Pou { pub name: String, pub kind: PouKind, /// The declared variables, across all VAR_* sections. pub vars: Vec, /// The statement body. pub body: Vec, /// 1-based line where the POU header appears (in the source that was parsed). pub line: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PouKind { Program, Function, FunctionBlock, } impl PouKind { pub fn label(self) -> &'static str { match self { PouKind::Program => "PROGRAM", PouKind::Function => "FUNCTION", PouKind::FunctionBlock => "FUNCTION_BLOCK", } } } /// A single declared variable. #[derive(Debug, Clone)] pub struct VarDecl { pub name: String, pub section: VarSection, /// The declared type as written (e.g. `BOOL`, `INT`, `ARRAY[0..9] OF INT`). pub type_name: String, /// Whether the type is an ARRAY, and its declared bounds `(lo, hi)` when /// they are literal integers — used by the array-bounds rule. pub array_bounds: Option<(i64, i64)>, /// The initializer expression, if any (`:= `). pub init: Option, pub line: u32, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum VarSection { Var, Input, Output, InOut, Global, Temp, External, } /// A statement. #[derive(Debug, Clone)] pub enum Stmt { Assign { target: Expr, value: Expr, line: u32, }, If { /// (condition, body) for IF and each ELSIF, in order. branches: Vec<(Expr, Vec)>, else_body: Option>, line: u32, }, Case { selector: Expr, /// (label expressions, body) per CASE arm. arms: Vec<(Vec, Vec)>, else_body: Option>, line: u32, }, For { var: String, from: Expr, to: Expr, by: Option, body: Vec, line: u32, }, While { cond: Expr, body: Vec, line: u32, }, Repeat { body: Vec, until: Expr, line: u32, }, /// A bare call statement, e.g. `TON1(IN := x, PT := T#5s);`. Call { callee: String, args: Vec, line: u32, }, Return { line: u32, }, Exit { line: u32, }, /// `JMP label;` — an unstructured jump. Jump { label: String, line: u32, }, /// `label:` — a jump target. Label { name: String, line: u32, }, } /// One argument in a call: positional (`name: None`) or named (`X := expr`). #[derive(Debug, Clone)] pub struct CallArg { pub name: Option, pub value: Expr, } /// An expression. #[derive(Debug, Clone)] pub enum Expr { Int(i64, u32), Real(f64, u32), Bool(bool, u32), /// A string literal, with the unquoted contents. Str(String, u32), /// A duration / date / time literal, kept as raw text (`T#5s`, `DT#...`). Time(String, u32), Ident(String, u32), /// `base[index]`. Index { base: Box, index: Box, line: u32, }, /// `base.field`. Member { base: Box, field: String, line: u32, }, Unary { op: UnOp, expr: Box, line: u32, }, Binary { op: BinOp, lhs: Box, rhs: Box, line: u32, }, /// A function call used as an expression, e.g. `LIMIT(a, b, c)`. Call { callee: String, args: Vec, line: u32, }, } impl Expr { /// The 1-based source line this expression starts on. pub fn line(&self) -> u32 { match self { Expr::Int(_, l) | Expr::Real(_, l) | Expr::Bool(_, l) | Expr::Str(_, l) | Expr::Time(_, l) | Expr::Ident(_, l) | Expr::Index { line: l, .. } | Expr::Member { line: l, .. } | Expr::Unary { line: l, .. } | Expr::Binary { line: l, .. } | Expr::Call { line: l, .. } => *l, } } /// If this expression is a plain identifier, its name. pub fn as_ident(&self) -> Option<&str> { match self { Expr::Ident(name, _) => Some(name.as_str()), _ => None, } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum UnOp { Not, Neg, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BinOp { Add, Sub, Mul, Div, Mod, Pow, Eq, Ne, Lt, Le, Gt, Ge, And, Or, Xor, }