1#![forbid(unsafe_code)]
2use mel_syntax::{TextRange, text_slice};
8
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
10pub struct SourceFile {
11 pub items: Vec<Item>,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum Item {
16 Proc(Box<ProcDef>),
17 Stmt(Box<Stmt>),
18}
19
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ProcDef {
22 pub return_type: Option<ProcReturnType>,
23 pub name_range: TextRange,
24 pub params: Vec<ProcParam>,
25 pub body: Stmt,
26 pub is_global: bool,
27 pub range: TextRange,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub enum Stmt {
32 Empty {
33 range: TextRange,
34 },
35 Proc {
36 proc_def: Box<ProcDef>,
37 range: TextRange,
38 },
39 Block {
40 statements: Vec<Stmt>,
41 range: TextRange,
42 },
43 Expr {
44 expr: Expr,
45 range: TextRange,
46 },
47 VarDecl {
48 decl: VarDecl,
49 range: TextRange,
50 },
51 If {
52 condition: Box<Expr>,
53 then_branch: Box<Stmt>,
54 else_branch: Option<Box<Stmt>>,
55 range: TextRange,
56 },
57 While {
58 condition: Box<Expr>,
59 body: Box<Stmt>,
60 range: TextRange,
61 },
62 DoWhile {
63 body: Box<Stmt>,
64 condition: Box<Expr>,
65 range: TextRange,
66 },
67 Switch {
68 control: Box<Expr>,
69 clauses: Vec<SwitchClause>,
70 range: TextRange,
71 },
72 For {
73 init: Option<Vec<Expr>>,
74 condition: Option<Box<Expr>>,
75 update: Option<Vec<Expr>>,
76 body: Box<Stmt>,
77 range: TextRange,
78 },
79 ForIn {
80 binding: Box<Expr>,
81 iterable: Box<Expr>,
82 body: Box<Stmt>,
83 range: TextRange,
84 },
85 Return {
86 expr: Option<Expr>,
87 range: TextRange,
88 },
89 Break {
90 range: TextRange,
91 },
92 Continue {
93 range: TextRange,
94 },
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum Expr {
99 Ident {
100 name_range: TextRange,
101 range: TextRange,
102 },
103 BareWord {
104 text: TextRange,
105 range: TextRange,
106 },
107 Int {
108 value: i64,
109 range: TextRange,
110 },
111 Float {
112 text: TextRange,
113 range: TextRange,
114 },
115 String {
116 text: TextRange,
117 range: TextRange,
118 },
119 Cast {
120 ty: TypeName,
121 expr: Box<Expr>,
122 range: TextRange,
123 },
124 VectorLiteral {
125 elements: Vec<Expr>,
126 range: TextRange,
127 },
128 ArrayLiteral {
129 elements: Vec<Expr>,
130 range: TextRange,
131 },
132 Unary {
133 op: UnaryOp,
134 expr: Box<Expr>,
135 range: TextRange,
136 },
137 Binary {
138 op: BinaryOp,
139 lhs: Box<Expr>,
140 rhs: Box<Expr>,
141 range: TextRange,
142 },
143 Ternary {
144 condition: Box<Expr>,
145 then_expr: Box<Expr>,
146 else_expr: Box<Expr>,
147 range: TextRange,
148 },
149 Index {
150 target: Box<Expr>,
151 index: Box<Expr>,
152 range: TextRange,
153 },
154 MemberAccess {
155 target: Box<Expr>,
156 member: TextRange,
157 range: TextRange,
158 },
159 ComponentAccess {
160 target: Box<Expr>,
161 component: VectorComponent,
162 range: TextRange,
163 },
164 Assign {
165 op: AssignOp,
166 lhs: Box<Expr>,
167 rhs: Box<Expr>,
168 range: TextRange,
169 },
170 PrefixUpdate {
171 op: UpdateOp,
172 expr: Box<Expr>,
173 range: TextRange,
174 },
175 PostfixUpdate {
176 op: UpdateOp,
177 expr: Box<Expr>,
178 range: TextRange,
179 },
180 Invoke(Box<InvokeExpr>),
181}
182
183impl Expr {
184 #[must_use]
185 pub const fn range(&self) -> TextRange {
186 match self {
187 Self::Ident { range, .. }
188 | Self::BareWord { range, .. }
189 | Self::Int { range, .. }
190 | Self::Float { range, .. }
191 | Self::String { range, .. }
192 | Self::Cast { range, .. }
193 | Self::VectorLiteral { range, .. }
194 | Self::ArrayLiteral { range, .. }
195 | Self::Unary { range, .. }
196 | Self::Binary { range, .. }
197 | Self::Ternary { range, .. }
198 | Self::Index { range, .. }
199 | Self::MemberAccess { range, .. }
200 | Self::ComponentAccess { range, .. }
201 | Self::Assign { range, .. }
202 | Self::PrefixUpdate { range, .. }
203 | Self::PostfixUpdate { range, .. } => *range,
204 Self::Invoke(invoke) => invoke.range,
205 }
206 }
207}
208
209#[derive(Debug, Clone, PartialEq, Eq)]
210pub struct VarDecl {
211 pub is_global: bool,
212 pub ty: TypeName,
213 pub declarators: Vec<Declarator>,
214 pub range: TextRange,
215}
216
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub struct ProcParam {
219 pub ty: TypeName,
220 pub name_range: TextRange,
221 pub is_array: bool,
222 pub range: TextRange,
223}
224
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub struct ProcReturnType {
227 pub ty: TypeName,
228 pub is_array: bool,
229 pub range: TextRange,
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
233pub struct SwitchClause {
234 pub label: SwitchLabel,
235 pub statements: Vec<Stmt>,
236 pub range: TextRange,
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
240pub enum SwitchLabel {
241 Case(Expr),
242 Default { range: TextRange },
243}
244
245#[derive(Debug, Clone, PartialEq, Eq)]
246pub enum TypeName {
247 Int,
248 Float,
249 String,
250 Vector,
251 Matrix,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
255pub struct Declarator {
256 pub name_range: TextRange,
257 pub array_size: Option<Option<Expr>>,
258 pub initializer: Option<Expr>,
259 pub range: TextRange,
260}
261
262#[derive(Debug, Clone, Copy, PartialEq, Eq)]
263pub enum VectorComponent {
264 X,
265 Y,
266 Z,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
270pub enum UnaryOp {
271 Not,
272 Negate,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
276pub enum BinaryOp {
277 Mul,
278 Div,
279 Rem,
280 Caret,
281 Add,
282 Sub,
283 Lt,
284 Le,
285 Gt,
286 Ge,
287 EqEq,
288 NotEq,
289 AndAnd,
290 OrOr,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub enum AssignOp {
295 Assign,
296 AddAssign,
297 SubAssign,
298 MulAssign,
299 DivAssign,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303pub enum UpdateOp {
304 Increment,
305 Decrement,
306}
307
308#[derive(Debug, Clone, PartialEq, Eq)]
309pub enum InvokeSurface {
310 Function {
311 head_range: TextRange,
312 args: Vec<Expr>,
313 },
314 ShellLike {
315 head_range: TextRange,
316 words: Vec<ShellWord>,
317 captured: bool,
318 },
319}
320
321#[derive(Debug, Clone, PartialEq, Eq)]
322pub enum ShellWord {
323 Flag {
324 text: TextRange,
325 range: TextRange,
326 },
327 NumericLiteral {
328 text: TextRange,
329 range: TextRange,
330 },
331 BareWord {
332 text: TextRange,
333 range: TextRange,
334 },
335 QuotedString {
336 text: TextRange,
337 range: TextRange,
338 },
339 Variable {
340 expr: Box<Expr>,
341 range: TextRange,
342 },
343 GroupedExpr {
344 expr: Box<Expr>,
345 range: TextRange,
346 },
347 BraceList {
348 expr: Box<Expr>,
349 range: TextRange,
350 },
351 VectorLiteral {
352 expr: Box<Expr>,
353 range: TextRange,
354 },
355 Capture {
356 invoke: Box<InvokeExpr>,
357 range: TextRange,
358 },
359}
360
361#[derive(Debug, Clone, PartialEq, Eq)]
362pub struct InvokeExpr {
363 pub surface: InvokeSurface,
364 pub range: TextRange,
365}
366
367impl ProcDef {
368 #[must_use]
369 pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
370 text_slice(source_text, self.name_range)
371 }
372}
373
374impl ProcParam {
375 #[must_use]
376 pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
377 text_slice(source_text, self.name_range)
378 }
379}
380
381impl Declarator {
382 #[must_use]
383 pub fn name_text<'a>(&self, source_text: &'a str) -> &'a str {
384 text_slice(source_text, self.name_range)
385 }
386}
387
388impl Expr {
389 #[must_use]
390 pub fn ident_text<'a>(&self, source_text: &'a str) -> Option<&'a str> {
391 match self {
392 Self::Ident { name_range, .. } => Some(text_slice(source_text, *name_range)),
393 _ => None,
394 }
395 }
396}
397
398impl InvokeSurface {
399 #[must_use]
400 pub fn head_text<'a>(&self, source_text: &'a str) -> Option<&'a str> {
401 match self {
402 Self::Function { head_range, .. } | Self::ShellLike { head_range, .. } => {
403 Some(text_slice(source_text, *head_range))
404 }
405 }
406 }
407}
408
409impl ShellWord {
410 #[must_use]
411 pub fn text_range(&self) -> Option<TextRange> {
412 match self {
413 Self::Flag { text, .. }
414 | Self::NumericLiteral { text, .. }
415 | Self::BareWord { text, .. }
416 | Self::QuotedString { text, .. } => Some(*text),
417 Self::Variable { .. }
418 | Self::GroupedExpr { .. }
419 | Self::BraceList { .. }
420 | Self::VectorLiteral { .. }
421 | Self::Capture { .. } => None,
422 }
423 }
424
425 #[must_use]
426 pub fn text<'a>(&'a self, source_text: &'a str) -> Option<&'a str> {
427 match self {
428 Self::Flag { text, .. }
429 | Self::NumericLiteral { text, .. }
430 | Self::BareWord { text, .. }
431 | Self::QuotedString { text, .. } => Some(text_slice(source_text, *text)),
432 Self::Variable { .. }
433 | Self::GroupedExpr { .. }
434 | Self::BraceList { .. }
435 | Self::VectorLiteral { .. }
436 | Self::Capture { .. } => None,
437 }
438 }
439}
440
441#[cfg(test)]
442mod tests {
443 use super::{Expr, InvokeExpr, InvokeSurface, ShellWord};
444 use mel_syntax::text_range;
445 use std::mem::size_of;
446
447 #[test]
448 fn unresolved_invoke_is_constructible() {
449 let invoke = InvokeExpr {
450 surface: InvokeSurface::ShellLike {
451 head_range: text_range(0, 2),
452 words: vec![
453 ShellWord::NumericLiteral {
454 text: text_range(3, 4),
455 range: text_range(3, 4),
456 },
457 ShellWord::Variable {
458 expr: Box::new(Expr::Ident {
459 name_range: text_range(5, 11),
460 range: text_range(5, 11),
461 }),
462 range: text_range(5, 11),
463 },
464 ],
465 captured: true,
466 },
467 range: text_range(0, 12),
468 };
469
470 assert!(matches!(invoke.surface, InvokeSurface::ShellLike { .. }));
471 assert_eq!(invoke.range, text_range(0, 12));
472 }
473
474 #[test]
475 fn boxed_stmt_payloads_keep_ast_layout_compact() {
476 assert_eq!(size_of::<super::Stmt>(), 72);
477 }
478}