| 1 | //! Expression AST nodes. |
| 2 | //! |
| 3 | //! Represents all Fortran expression forms: literals, names, operators, |
| 4 | //! function calls, array constructors, component access, and more. |
| 5 | |
| 6 | /// A Fortran expression. |
| 7 | #[derive(Debug, Clone, PartialEq)] |
| 8 | #[allow(clippy::enum_variant_names)] |
| 9 | pub enum Expr { |
| 10 | // ---- Literals ---- |
| 11 | /// Integer literal: `42`, `42_8`, `42_int64` |
| 12 | IntegerLiteral { text: String, kind: Option<String> }, |
| 13 | /// Real literal: `3.14`, `1.0d0`, `6.022e23`, `1.0_8`, `.5`, `5.` |
| 14 | RealLiteral { text: String, kind: Option<String> }, |
| 15 | /// String literal: `'hello'`, `"hello"`, `'it''s'` |
| 16 | StringLiteral { value: String, kind: Option<String> }, |
| 17 | /// Logical literal: `.true.`, `.false.`, `.true._4` |
| 18 | LogicalLiteral { value: bool, kind: Option<String> }, |
| 19 | /// Complex literal: `(1.0, 2.0)` |
| 20 | ComplexLiteral { |
| 21 | real: Box<SpannedExpr>, |
| 22 | imag: Box<SpannedExpr>, |
| 23 | }, |
| 24 | /// BOZ literal: `B'1010'`, `O'777'`, `Z'FF'` |
| 25 | BozLiteral { text: String, base: BozBase }, |
| 26 | |
| 27 | // ---- Names and access ---- |
| 28 | /// Simple name (variable, function, type, etc.) |
| 29 | Name { name: String }, |
| 30 | /// Component access: `x%field`, `x%inner%deep` |
| 31 | ComponentAccess { |
| 32 | base: Box<SpannedExpr>, |
| 33 | component: String, |
| 34 | }, |
| 35 | |
| 36 | // ---- Operations ---- |
| 37 | /// Unary operation: `-x`, `.not. x`, `+x` |
| 38 | UnaryOp { |
| 39 | op: UnaryOp, |
| 40 | operand: Box<SpannedExpr>, |
| 41 | }, |
| 42 | /// Binary operation: `a + b`, `x .and. y`, `s // t` |
| 43 | BinaryOp { |
| 44 | op: BinaryOp, |
| 45 | left: Box<SpannedExpr>, |
| 46 | right: Box<SpannedExpr>, |
| 47 | }, |
| 48 | |
| 49 | // ---- Calls and subscripts ---- |
| 50 | // Note: A(I) is ambiguous — could be array access, function call, or substring. |
| 51 | // The parser produces FunctionCall for all of them; sema disambiguates. |
| 52 | /// Function call or array access: `sin(x)`, `a(i,j)`, `s(1:5)` |
| 53 | FunctionCall { |
| 54 | callee: Box<SpannedExpr>, |
| 55 | args: Vec<Argument>, |
| 56 | }, |
| 57 | |
| 58 | // ---- Array constructors ---- |
| 59 | /// Array constructor: `[1, 2, 3]` or `(/ 1, 2, 3 /)` |
| 60 | ArrayConstructor { |
| 61 | type_spec: Option<String>, // [integer :: 1, 2, 3] |
| 62 | values: Vec<AcValue>, |
| 63 | }, |
| 64 | |
| 65 | // ---- Parenthesized ---- |
| 66 | /// Parenthesized expression: `(x + y)` |
| 67 | ParenExpr { inner: Box<SpannedExpr> }, |
| 68 | } |
| 69 | |
| 70 | /// An expression with source location. |
| 71 | pub type SpannedExpr = super::Spanned<Expr>; |
| 72 | |
| 73 | impl SpannedExpr { |
| 74 | /// Pretty-print the expression with full parenthesization for testing. |
| 75 | pub fn to_sexpr(&self) -> String { |
| 76 | match &self.node { |
| 77 | Expr::IntegerLiteral { text, .. } => text.clone(), |
| 78 | Expr::RealLiteral { text, .. } => text.clone(), |
| 79 | Expr::StringLiteral { value, .. } => format!("'{}'", value), |
| 80 | Expr::LogicalLiteral { value, .. } => { |
| 81 | if *value { |
| 82 | ".true.".into() |
| 83 | } else { |
| 84 | ".false.".into() |
| 85 | } |
| 86 | } |
| 87 | Expr::ComplexLiteral { real, imag } => { |
| 88 | format!("({}, {})", real.to_sexpr(), imag.to_sexpr()) |
| 89 | } |
| 90 | Expr::BozLiteral { text, .. } => text.clone(), |
| 91 | Expr::Name { name } => name.clone(), |
| 92 | Expr::ComponentAccess { base, component } => { |
| 93 | format!("{}%{}", base.to_sexpr(), component) |
| 94 | } |
| 95 | Expr::UnaryOp { op, operand } => { |
| 96 | format!("({} {})", op, operand.to_sexpr()) |
| 97 | } |
| 98 | Expr::BinaryOp { op, left, right } => { |
| 99 | format!("({} {} {})", left.to_sexpr(), op, right.to_sexpr()) |
| 100 | } |
| 101 | Expr::FunctionCall { callee, args } => { |
| 102 | let args_str: Vec<String> = args |
| 103 | .iter() |
| 104 | .map(|a| { |
| 105 | if let Some(kw) = &a.keyword { |
| 106 | format!("{}={}", kw, a.value.to_sexpr()) |
| 107 | } else { |
| 108 | a.value.to_sexpr() |
| 109 | } |
| 110 | }) |
| 111 | .collect(); |
| 112 | format!("{}({})", callee.to_sexpr(), args_str.join(", ")) |
| 113 | } |
| 114 | Expr::ArrayConstructor { values, type_spec } => { |
| 115 | let vals: Vec<String> = values |
| 116 | .iter() |
| 117 | .map(|v| match v { |
| 118 | AcValue::Expr(e) => e.to_sexpr(), |
| 119 | AcValue::ImpliedDo(ido) => { |
| 120 | let vals: Vec<String> = ido |
| 121 | .values |
| 122 | .iter() |
| 123 | .map(|v| match v { |
| 124 | AcValue::Expr(e) => e.to_sexpr(), |
| 125 | AcValue::ImpliedDo(_) => "(nested-implied-do)".into(), |
| 126 | }) |
| 127 | .collect(); |
| 128 | let step_str = ido |
| 129 | .step |
| 130 | .as_ref() |
| 131 | .map_or(String::new(), |s| format!(", {}", s.to_sexpr())); |
| 132 | format!( |
| 133 | "({}, {}={}, {}{})", |
| 134 | vals.join(", "), |
| 135 | ido.var, |
| 136 | ido.start.to_sexpr(), |
| 137 | ido.end.to_sexpr(), |
| 138 | step_str |
| 139 | ) |
| 140 | } |
| 141 | }) |
| 142 | .collect(); |
| 143 | if let Some(ts) = type_spec { |
| 144 | format!("[{} :: {}]", ts, vals.join(", ")) |
| 145 | } else { |
| 146 | format!("[{}]", vals.join(", ")) |
| 147 | } |
| 148 | } |
| 149 | Expr::ParenExpr { inner } => { |
| 150 | format!("({})", inner.to_sexpr()) |
| 151 | } |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | /// BOZ literal base. |
| 157 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 158 | pub enum BozBase { |
| 159 | Binary, |
| 160 | Octal, |
| 161 | Hex, |
| 162 | } |
| 163 | |
| 164 | /// Unary operators. |
| 165 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 166 | pub enum UnaryOp { |
| 167 | Plus, // + |
| 168 | Minus, // - |
| 169 | Not, // .not. |
| 170 | Defined(String), // .myop. |
| 171 | } |
| 172 | |
| 173 | impl std::fmt::Display for UnaryOp { |
| 174 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 175 | match self { |
| 176 | UnaryOp::Plus => write!(f, "+"), |
| 177 | UnaryOp::Minus => write!(f, "-"), |
| 178 | UnaryOp::Not => write!(f, ".not."), |
| 179 | UnaryOp::Defined(s) => write!(f, ".{}.", s), |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | /// Binary operators. |
| 185 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 186 | pub enum BinaryOp { |
| 187 | // Arithmetic |
| 188 | Add, // + |
| 189 | Sub, // - |
| 190 | Mul, // * |
| 191 | Div, // / |
| 192 | Pow, // ** |
| 193 | |
| 194 | // String |
| 195 | Concat, // // |
| 196 | |
| 197 | // Comparison |
| 198 | Eq, // == or .eq. |
| 199 | Ne, // /= or .ne. |
| 200 | Lt, // < or .lt. |
| 201 | Le, // <= or .le. |
| 202 | Gt, // > or .gt. |
| 203 | Ge, // >= or .ge. |
| 204 | |
| 205 | // Logical |
| 206 | And, // .and. |
| 207 | Or, // .or. |
| 208 | Eqv, // .eqv. |
| 209 | Neqv, // .neqv. |
| 210 | |
| 211 | // User-defined |
| 212 | Defined(String), // .myop. |
| 213 | } |
| 214 | |
| 215 | impl std::fmt::Display for BinaryOp { |
| 216 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 217 | match self { |
| 218 | BinaryOp::Add => write!(f, "+"), |
| 219 | BinaryOp::Sub => write!(f, "-"), |
| 220 | BinaryOp::Mul => write!(f, "*"), |
| 221 | BinaryOp::Div => write!(f, "/"), |
| 222 | BinaryOp::Pow => write!(f, "**"), |
| 223 | BinaryOp::Concat => write!(f, "//"), |
| 224 | BinaryOp::Eq => write!(f, "=="), |
| 225 | BinaryOp::Ne => write!(f, "/="), |
| 226 | BinaryOp::Lt => write!(f, "<"), |
| 227 | BinaryOp::Le => write!(f, "<="), |
| 228 | BinaryOp::Gt => write!(f, ">"), |
| 229 | BinaryOp::Ge => write!(f, ">="), |
| 230 | BinaryOp::And => write!(f, ".and."), |
| 231 | BinaryOp::Or => write!(f, ".or."), |
| 232 | BinaryOp::Eqv => write!(f, ".eqv."), |
| 233 | BinaryOp::Neqv => write!(f, ".neqv."), |
| 234 | BinaryOp::Defined(s) => write!(f, ".{}.", s), |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | /// A function/subroutine argument (positional or keyword). |
| 240 | /// The value is a SectionSubscript to support both plain expressions |
| 241 | /// and range subscripts like a(1:5) or a(1:10:2). |
| 242 | #[derive(Debug, Clone, PartialEq)] |
| 243 | pub struct Argument { |
| 244 | pub keyword: Option<String>, |
| 245 | pub value: SectionSubscript, |
| 246 | } |
| 247 | |
| 248 | /// Array constructor value — either an expression or an implied-do loop. |
| 249 | #[derive(Debug, Clone, PartialEq)] |
| 250 | pub enum AcValue { |
| 251 | Expr(SpannedExpr), |
| 252 | ImpliedDo(Box<ImpliedDoLoop>), |
| 253 | } |
| 254 | |
| 255 | /// Implied-do loop contents (boxed to shrink AcValue from ~288 to ~16 bytes). |
| 256 | #[derive(Debug, Clone, PartialEq)] |
| 257 | pub struct ImpliedDoLoop { |
| 258 | pub values: Vec<AcValue>, |
| 259 | pub var: String, |
| 260 | pub start: SpannedExpr, |
| 261 | pub end: SpannedExpr, |
| 262 | pub step: Option<SpannedExpr>, |
| 263 | } |
| 264 | |
| 265 | /// Section subscript — either a single element or a range (for array sections/substrings). |
| 266 | #[derive(Debug, Clone, PartialEq)] |
| 267 | pub enum SectionSubscript { |
| 268 | Element(SpannedExpr), |
| 269 | Range { |
| 270 | start: Option<SpannedExpr>, |
| 271 | end: Option<SpannedExpr>, |
| 272 | stride: Option<SpannedExpr>, |
| 273 | }, |
| 274 | } |
| 275 | |
| 276 | impl SectionSubscript { |
| 277 | pub fn to_sexpr(&self) -> String { |
| 278 | match self { |
| 279 | SectionSubscript::Element(e) => e.to_sexpr(), |
| 280 | SectionSubscript::Range { start, end, stride } => { |
| 281 | let s = start.as_ref().map_or(String::new(), |e| e.to_sexpr()); |
| 282 | let e = end.as_ref().map_or(String::new(), |e| e.to_sexpr()); |
| 283 | if let Some(st) = stride { |
| 284 | format!("{}:{}:{}", s, e, st.to_sexpr()) |
| 285 | } else { |
| 286 | format!("{}:{}", s, e) |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | } |
| 291 | } |
| 292 |