//! ast.rs — Typed Abstract Syntax Tree for RPG IV free-format programs. //! //! This module defines the in-memory representation produced by the lowering //! pass (`lower.rs`) and consumed by the LLVM code-generator (`codegen.rs`). //! //! Only the subset of the language that is needed to compile `hello.rpg` (and //! small programs like it) is fully fleshed out. Everything else is kept as //! placeholder variants so the lowering pass can represent the whole parse tree //! without panicking, and the codegen can skip unimplemented nodes gracefully. // ───────────────────────────────────────────────────────────────────────────── // Top-level program // ───────────────────────────────────────────────────────────────────────────── /// A complete RPG IV source file. #[derive(Debug, Clone)] pub struct Program { /// Zero or more top-level declarations (CTL-OPT, DCL-S, DCL-C, DCL-DS, /// file declarations, subroutines …). pub declarations: Vec, /// Zero or more procedure definitions (`DCL-PROC … END-PROC`). pub procedures: Vec, } // ───────────────────────────────────────────────────────────────────────────── // Declarations // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub enum Declaration { /// `CTL-OPT keyword-list;` ControlSpec(ControlSpec), /// `DCL-S name type [keywords];` Standalone(StandaloneDecl), /// `DCL-C name literal;` or `DCL-C name CONST(literal);` Constant(ConstantDecl), /// `DCL-C name *named-constant;` NamedConstantDecl(NamedConstantDecl), /// `DCL-DS name … END-DS;` DataStructure(DataStructureDecl), /// `DCL-F name …;` File(FileDecl), /// `BEG-SR name; … END-SR;` Subroutine(Subroutine), } // ── Control spec ────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct ControlSpec { pub keywords: Vec, } #[derive(Debug, Clone)] pub enum CtlKeyword { DftActGrp(bool), // *YES / *NO NoMain, Main(String), Other(String), // catch-all for keywords we don't generate code for } // ── Standalone variable ─────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct StandaloneDecl { pub name: String, pub ty: TypeSpec, pub keywords: Vec, } // ── Constant declaration ────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct ConstantDecl { pub name: String, pub value: Literal, } #[derive(Debug, Clone)] pub struct NamedConstantDecl { pub name: String, pub value: NamedConstant, } // ── Data structure ──────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct DataStructureDecl { pub name: String, pub keywords: Vec, pub fields: Vec, } #[derive(Debug, Clone)] pub enum DsKeyword { Qualified, Template, Other(String), } #[derive(Debug, Clone)] pub struct DsField { pub name: String, pub ty: TypeSpec, pub keywords: Vec, } // ── File declaration ────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct FileDecl { pub name: String, pub keywords: Vec, // simplified — not code-gen'd } // ── Subroutine ──────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct Subroutine { pub name: String, pub body: Vec, } // ───────────────────────────────────────────────────────────────────────────── // Type specifications // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq)] pub enum TypeSpec { /// `CHAR(n)` — fixed-length character field. Char(Box), /// `VARCHAR(n)` — variable-length character. VarChar(Box), /// `INT(n)` — signed integer (n = 3, 5, 10, or 20). Int(Box), /// `UNS(n)` — unsigned integer. Uns(Box), /// `FLOAT(n)` — floating-point. Float(Box), /// `PACKED(digits:decimals)` Packed(Box, Box), /// `ZONED(digits:decimals)` Zoned(Box, Box), /// `BINDEC(digits:decimals)` Bindec(Box, Box), /// `IND` — indicator (boolean). Ind, /// `DATE [(*fmt)]` Date, /// `TIME [(*fmt)]` Time, /// `TIMESTAMP` Timestamp, /// `POINTER` Pointer, /// `LIKE(name)` Like(String), /// `LIKEDS(name)` LikeDs(String), /// Unrecognised / not yet implemented type. Unknown(String), } impl TypeSpec { /// Return the number of bytes this type occupies at runtime on a 64-bit /// Linux host. Returns `None` for types whose size is not statically known. pub fn byte_size(&self) -> Option { match self { TypeSpec::Char(expr) | TypeSpec::VarChar(expr) => { if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() { Some(*n as u64) } else { None } } TypeSpec::Int(expr) | TypeSpec::Uns(expr) => { if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() { Some(match n { 3 => 1, 5 => 2, 10 => 4, 20 => 8, _ => 8, // default to 8 bytes }) } else { None } } TypeSpec::Float(expr) => { if let Expression::Literal(Literal::Integer(n)) = expr.as_ref() { Some(if *n <= 4 { 4 } else { 8 }) } else { None } } TypeSpec::Ind => Some(1), TypeSpec::Pointer => Some(8), TypeSpec::Packed(digits, _) => { if let Expression::Literal(Literal::Integer(n)) = digits.as_ref() { Some((*n as u64 / 2) + 1) } else { None } } _ => None, } } } // ───────────────────────────────────────────────────────────────────────────── // Variable / declaration keywords // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub enum VarKeyword { /// `INZ` — default initialisation. Inz, /// `INZ(expr)` — explicit initialisation value. InzExpr(Expression), /// `INZ(*named-constant)` — initialise to named constant. InzNamed(NamedConstant), Static, Other(String), } // ───────────────────────────────────────────────────────────────────────────── // Procedures // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct Procedure { pub name: String, pub exported: bool, pub pi: Option, /// Local declarations (DCL-S, DCL-C, etc.) inside the procedure. pub locals: Vec, pub body: Vec, } /// Procedure Interface specification (`DCL-PI … END-PI`). #[derive(Debug, Clone)] pub struct PiSpec { pub name: String, pub return_ty: Option, pub params: Vec, } #[derive(Debug, Clone)] pub struct PiParam { pub name: String, pub ty: TypeSpec, pub keywords: Vec, } #[derive(Debug, Clone)] pub enum ParamKeyword { Value, Const, Other(String), } // ───────────────────────────────────────────────────────────────────────────── // Statements // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub enum Statement { /// `lvalue = expr;` or `EVAL lvalue = expr;` Assign(AssignStmt), /// `IF expr; … [ELSEIF …] [ELSE …] ENDIF;` If(IfStmt), /// `DOW expr; … ENDDO;` DoWhile(DoWhileStmt), /// `DOU expr; … ENDDO;` DoUntil(DoUntilStmt), /// `FOR i = start TO/DOWNTO end [BY step]; … ENDFOR;` For(ForStmt), /// `SELECT; WHEN … [OTHER …] ENDSL;` Select(SelectStmt), /// `MONITOR; … ON-ERROR … ENDMON;` Monitor(MonitorStmt), /// `CALLP name(args);` or bare procedure call `name(args);` CallP(CallPStmt), /// `RETURN [expr];` Return(ReturnStmt), /// `LEAVE;` Leave, /// `ITER;` Iter, /// `LEAVESR;` LeaveSr, /// `EXSR name;` ExSr(String), /// `DSPLY expr;` Dsply(DsplyStmt), /// `RESET lvalue;` / `RESET *ALL;` Reset(ResetStmt), /// `CLEAR lvalue;` Clear(LValue), /// Any I/O statement (READ, WRITE, CHAIN, etc.) — kept as opaque for now. Io(IoStatement), /// Catch-all for statements not yet lowered. Unimplemented(String), } // ── Assignment ──────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct AssignStmt { pub target: LValue, pub value: Expression, } // ── If / ElseIf / Else ──────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct IfStmt { pub condition: Expression, pub then_body: Vec, pub elseifs: Vec, pub else_body: Option>, } #[derive(Debug, Clone)] pub struct ElseIf { pub condition: Expression, pub body: Vec, } // ── DOW loop ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct DoWhileStmt { pub condition: Expression, pub body: Vec, } // ── DOU loop ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct DoUntilStmt { pub condition: Expression, pub body: Vec, } // ── FOR loop ────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct ForStmt { pub var: String, pub start: Expression, pub limit: Expression, pub step: Option, pub downto: bool, pub body: Vec, } // ── SELECT / WHEN ───────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct SelectStmt { pub whens: Vec, pub other: Option>, } #[derive(Debug, Clone)] pub struct WhenClause { pub condition: Expression, pub body: Vec, } // ── MONITOR ─────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct MonitorStmt { pub body: Vec, pub handlers: Vec, } #[derive(Debug, Clone)] pub struct OnError { pub codes: Vec, pub body: Vec, } #[derive(Debug, Clone)] pub enum ErrorCode { Integer(u32), Program, File, All, } // ── CALLP ───────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct CallPStmt { pub name: String, pub args: Vec, } // ── RETURN ──────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct ReturnStmt { pub value: Option, } // ── DSPLY ───────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub struct DsplyStmt { /// The expression to display. pub expr: Expression, /// Optional message queue identifier (two-operand form). pub msg_q: Option, pub response: Option, } // ── RESET ───────────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub enum ResetStmt { Target(LValue), All, } // ── I/O (opaque) ────────────────────────────────────────────────────────────── #[derive(Debug, Clone)] pub enum IoStatement { Read { file: String }, ReadP { file: String }, Write { record: String }, Update { record: String }, Delete { key: Expression, file: String }, Chain { key: Expression, file: String }, SetLL { key: SetKey, file: String }, SetGT { key: SetKey, file: String }, Open { file: String }, Close { file: Option }, // None = *ALL Except { format: Option }, ExFmt { format: String }, Post { file: String }, Feod { file: String }, Unlock { file: String }, } #[derive(Debug, Clone)] pub enum SetKey { Expr(Expression), Start, End, } // ───────────────────────────────────────────────────────────────────────────── // L-values // ───────────────────────────────────────────────────────────────────────────── /// An assignable location. #[derive(Debug, Clone, PartialEq)] pub enum LValue { /// Simple or dotted name: `myVar` or `ds.field`. Name(QualifiedName), /// Array element: `arr(i)`. Index(QualifiedName, Vec), } impl LValue { /// Return the base name (first component of the qualified name). pub fn base_name(&self) -> &str { match self { LValue::Name(q) | LValue::Index(q, _) => &q.parts[0], } } } // ───────────────────────────────────────────────────────────────────────────── // Expressions // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq)] pub enum Expression { Literal(Literal), Named(NamedConstant), Special(SpecialValue), Variable(QualifiedName), /// Array / function-style subscript: `name(idx)`. Index(QualifiedName, Vec), /// Procedure / built-in call as expression: `name(args)`. Call(String, Vec), BuiltIn(BuiltIn), UnaryMinus(Box), UnaryPlus(Box), BinOp(BinOp, Box, Box), Not(Box), Paren(Box), } #[derive(Debug, Clone, PartialEq, Eq)] pub enum BinOp { Add, Sub, Mul, Div, Pow, Eq, Ne, Lt, Le, Gt, Ge, And, Or, } // ───────────────────────────────────────────────────────────────────────────── // Literals // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq)] pub enum Literal { String(String), Integer(i64), Float(f64), Hex(Vec), /// `*ON` / `*OFF` as a literal. Indicator(bool), } // ───────────────────────────────────────────────────────────────────────────── // Named constants (`*ON`, `*OFF`, `*BLANK`, …) // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq, Eq)] pub enum NamedConstant { On, Off, Blank, Blanks, Zero, Zeros, HiVal, LoVal, Null, } // ───────────────────────────────────────────────────────────────────────────── // Special values (`*IN`, `*START`, …) // ───────────────────────────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq)] pub enum SpecialValue { /// `*IN(n)` — indicator by number. In(Box), InAll, On, Off, Blank, Blanks, Zero, Zeros, HiVal, LoVal, Null, /// `*ALL'string'` All(String), Omit, This, Same, Start, End, } // ───────────────────────────────────────────────────────────────────────────── // Built-in functions // ───────────────────────────────────────────────────────────────────────────── /// The RPG IV `%BUILTIN(…)` functions we actually lower to code. /// All others are wrapped in `Other`. #[derive(Debug, Clone, PartialEq)] pub enum BuiltIn { /// `%LEN(identifier)` — byte length of a field. Len(Box), /// `%TRIM(expr)` — trim leading and trailing blanks. Trim(Box), /// `%TRIML(expr)` — trim leading blanks. TrimL(Box), /// `%TRIMR(expr)` — trim trailing blanks. TrimR(Box), /// `%CHAR(expr)` — convert to character string. Char(Box), /// `%INT(expr)` — convert to integer. Int(Box), /// `%DEC(expr:digits:decimals)` — convert to packed decimal. Dec(Box, Box, Box), /// `%ABS(expr)` — absolute value. Abs(Box), /// `%SQRT(expr)` — square root. Sqrt(Box), /// `%EOF[(file)]` Eof(Option), /// `%FOUND[(file)]` Found(Option), /// `%ERROR()` Error, /// `%SUBST(str:start:len)` or `%SUBST(str:start)`. Subst(Box, Box, Option>), /// `%SCAN(pattern:source[:start])`. Scan(Box, Box, Option>), /// `%SIZE(identifier)`. Size(Box), /// `%ADDR(identifier)`. Addr(Box), /// `%ALLOC(size)`. Alloc(Box), /// `%REM(a:b)`. Rem(Box, Box), /// `%DIV(a:b)`. Div(Box, Box), /// Any built-in we haven't individually modelled. Other(String, Vec), } // ───────────────────────────────────────────────────────────────────────────── // Qualified names and argument lists // ───────────────────────────────────────────────────────────────────────────── /// A dot-separated name: `ds.subDs.leaf`. #[derive(Debug, Clone, PartialEq)] pub struct QualifiedName { pub parts: Vec, } impl QualifiedName { pub fn simple(name: impl Into) -> Self { QualifiedName { parts: vec![name.into()] } } pub fn is_simple(&self) -> bool { self.parts.len() == 1 } /// Return the leaf (last) component. pub fn leaf(&self) -> &str { self.parts.last().map(|s| s.as_str()).unwrap_or("") } } impl std::fmt::Display for QualifiedName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.parts.join(".")) } } /// A call argument. #[derive(Debug, Clone, PartialEq)] pub enum Arg { Expr(Expression), Omit, }