Skip to main content

carmen_lang/ast/
mod.rs

1//! Abstract Syntax Tree (AST) definitions.
2//!
3//! This module defines the core AST nodes that represent parsed Carmen programs.
4//! The AST is designed to capture both musical and programming constructs while
5//! preserving source location information for error reporting and tooling.
6//!
7//! # Structure
8//!
9//! The AST is built around three main concepts:
10//! - **Literals**: Primitive values like numbers, strings, pitches, durations, dynamics
11//! - **Expressions**: Complex expressions including function calls, musical events, control flow
12//! - **Statements**: Top-level constructs like variable declarations, function definitions
13//!
14//! All AST nodes are wrapped in [`Spanned`] to preserve source location information.
15
16pub mod inspector;
17pub mod spanned;
18
19use crate::errors::Span;
20pub use spanned::Spanned;
21use std::fmt::{Display, Formatter, Result as FmtResult};
22use std::str::FromStr;
23
24/// Literal values in the Carmen language.
25///
26/// Literals represent primitive values that can be directly written in source code.
27/// Carmen supports both traditional programming literals (numbers, strings, booleans)
28/// and musical literals (pitches, durations, dynamics, rests).
29#[derive(Debug, Clone, PartialEq)]
30pub enum Literal {
31    Number(f64),
32    String(String),
33    Boolean(bool),
34    Pitch {
35        note: String,
36        accidentals: i32,
37        octave: Option<i32>,
38    },
39    Duration {
40        numerator: u32,
41        denominator: u32,
42        dots: u32,
43    },
44    Dynamic(String),
45    Attribute(String),
46    Rest {
47        duration: Option<Box<Literal>>,
48    },
49}
50
51impl Literal {
52    /// Parse a pitch literal from text.
53    ///
54    /// Accepts formats like:
55    /// - `c`, `d`, `e` (pitch classes)
56    /// - `c4`, `f#5`, `bb3` (specific pitches)
57    /// - Accidentals can be `#`/`s` for sharps, `b` for flats
58    pub fn parse_pitch(text: &str) -> Option<Self> {
59        let chars: Vec<char> = text.chars().collect();
60        if chars.is_empty() {
61            return None;
62        }
63
64        let note = chars[0].to_ascii_lowercase().to_string();
65        if !matches!(note.as_str(), "a" | "b" | "c" | "d" | "e" | "f" | "g") {
66            return None;
67        }
68
69        let mut i = 1;
70        let mut accidentals = 0;
71
72        // Count accidentals
73        while i < chars.len() {
74            match chars[i] {
75                's' | '#' => accidentals += 1,
76                'b' => accidentals -= 1,
77                _ => break,
78            }
79            i += 1;
80        }
81
82        // Parse octave if present
83        let octave = if i < chars.len() {
84            let remaining: String = chars[i..].iter().collect();
85            // Only accept if the entire remaining string is a valid octave number
86            if remaining.chars().all(|c| c.is_ascii_digit()) {
87                remaining.parse().ok()
88            } else {
89                return None; // Invalid characters after note and accidentals
90            }
91        } else {
92            None
93        };
94
95        Some(Literal::Pitch {
96            note,
97            accidentals,
98            octave,
99        })
100    }
101
102    /// Check if the given text represents a valid dynamic marking.
103    ///
104    /// Recognizes standard dynamic markings from `pppp` to `ffff`.
105    pub fn is_dynamic(text: &str) -> bool {
106        matches!(
107            text,
108            "pppp" | "ppp" | "pp" | "p" | "mp" | "mf" | "f" | "ff" | "fff" | "ffff"
109        )
110    }
111
112    /// Check if the given text represents a valid performance attribute.
113    ///
114    /// Recognizes common articulation and expression markings.
115    pub fn is_attribute(text: &str) -> bool {
116        matches!(
117            text,
118            "staccato" | "legato" | "accent" | "tenuto" | "marcato" | "sforzando"
119        )
120    }
121
122    /// Parse a duration literal from text.
123    ///
124    /// Accepts formats like:
125    /// - `1/4` (quarter note)
126    /// - `1/2.` (dotted half note)
127    /// - `3/8` (three eighth notes)
128    /// - `2` (two whole notes)
129    ///
130    /// Dots are represented by trailing periods.
131    pub fn parse_duration(text: &str) -> Option<Self> {
132        let chars: Vec<char> = text.chars().collect();
133        if chars.is_empty() {
134            return None;
135        }
136
137        let mut dots = 0;
138        let mut base_end = chars.len();
139        while base_end > 0 && chars[base_end - 1] == '.' {
140            dots += 1;
141            base_end -= 1;
142        }
143        let base_str: String = chars[..base_end].iter().collect();
144
145        let (numerator, denominator) = if let Some(slash_pos) = base_str.find('/') {
146            let num_str = &base_str[..slash_pos];
147            let den_str = &base_str[slash_pos + 1..];
148            match (num_str.parse::<u32>(), den_str.parse::<u32>()) {
149                (Ok(num), Ok(den)) if den > 0 => (num, den),
150                _ => return None,
151            }
152        } else if let Ok(num) = base_str.parse::<u32>() {
153            (num, 1) // Handle whole numbers as x/1
154        } else {
155            return None;
156        };
157
158        Some(Literal::Duration {
159            numerator,
160            denominator,
161            dots,
162        })
163    }
164
165    /// Parse a rest literal from text.
166    ///
167    /// Accepts formats like:
168    /// - `~` (contextual rest)
169    /// - `~1/4` (quarter rest)
170    /// - `~1/2.` (dotted half rest)
171    pub fn parse_rest(text: &str) -> Option<Self> {
172        if !text.starts_with('~') {
173            return None;
174        }
175
176        if text == "~" {
177            // Inline rest
178            return Some(Literal::Rest { duration: None });
179        }
180
181        // Standalone rest with duration like ~1/4
182        let duration_str = &text[1..];
183        Self::parse_duration(duration_str)
184    }
185}
186
187/// Binary operators in Carmen expressions.
188#[derive(Debug, Clone, PartialEq)]
189pub enum BinaryOp {
190    /// Addition (`+`)
191    Add,
192    /// Subtraction (`-`)
193    Subtract,
194    /// Multiplication (`*`)
195    Multiply,
196    /// Division (`/`)
197    Divide,
198    /// Modulo (`%`)
199    Modulo,
200    /// Equality (`==`)
201    Equal,
202    /// Inequality (`!=`)
203    NotEqual,
204    /// Less than (`<`)
205    Less,
206    /// Greater than (`>`)
207    Greater,
208    /// Less than or equal (`<=`)
209    LessEqual,
210    /// Greater than or equal (`>=`)
211    GreaterEqual,
212    /// Logical AND (`and`)
213    And,
214    /// Logical OR (`or`)
215    Or,
216}
217
218/// Unary operators in Carmen expressions.
219#[derive(Debug, Clone, PartialEq)]
220pub enum UnaryOp {
221    /// Unary minus (`-`)
222    Minus,
223    /// Logical NOT (`not`)
224    Not,
225}
226
227/// Expressions in the Carmen language.
228///
229/// Expressions represent computations that produce values. This includes
230/// traditional programming constructs (arithmetic, function calls, control flow)
231/// as well as musical constructs (events, parts, scores).
232#[derive(Debug, Clone, PartialEq)]
233pub enum Expression {
234    /// Literal value
235    Literal(Literal),
236    /// Variable reference
237    Identifier(String),
238    /// Binary operation (e.g., `a + b`, `x == y`)
239    Binary {
240        left: Box<SpannedExpression>,
241        operator: BinaryOp,
242        right: Box<SpannedExpression>,
243    },
244    /// Unary operation (e.g., `-x`, `not flag`)
245    Unary {
246        operator: UnaryOp,
247        operand: Box<SpannedExpression>,
248    },
249    /// Function call with positional and named arguments
250    Call {
251        callee: Box<SpannedExpression>,
252        args: Vec<SpannedExpression>,
253        kwargs: Vec<(String, SpannedExpression)>,
254    },
255    /// Pipe operation for function composition (e.g., `data |> process`)
256    Pipe {
257        left: Box<SpannedExpression>,
258        right: Box<SpannedExpression>,
259    },
260    /// List literal (e.g., `[1, 2, 3]`)
261    List { elements: Vec<SpannedExpression> },
262    /// Tuple literal (e.g., `(1, 2, 3)`)
263    Tuple { elements: Vec<SpannedExpression> },
264    /// Set literal (e.g., `{1, 2, 3}`)
265    Set { elements: Vec<SpannedExpression> },
266    /// Block expression containing multiple statements
267    Block { statements: Vec<SpannedStatement> },
268    /// Conditional expression
269    If {
270        condition: Box<SpannedExpression>,
271        then_branch: Box<SpannedExpression>,
272        else_branch: Option<Box<SpannedExpression>>,
273    },
274    /// For loop expression
275    For {
276        variable: String,
277        iterable: Box<SpannedExpression>,
278        body: Box<SpannedExpression>,
279    },
280    /// While loop expression
281    While {
282        condition: Box<SpannedExpression>,
283        body: Box<SpannedExpression>,
284    },
285    /// Set of pitch classes for harmonic analysis
286    PitchClassSet { classes: Vec<u8> },
287    /// Musical event (note, chord, or rest with timing and expression)
288    MusicalEvent {
289        duration: Box<SpannedExpression>,
290        pitches: Box<SpannedExpression>,
291        dynamic: Option<Box<SpannedExpression>>,
292        attributes: Vec<SpannedExpression>,
293    },
294    /// Musical part containing a sequence of events
295    Part {
296        name: Option<Box<SpannedExpression>>,
297        body: Box<SpannedExpression>,
298    },
299    /// Staff within a part (for multi-staff instruments)
300    Staff {
301        number: Box<SpannedExpression>,
302        body: Box<SpannedExpression>,
303    },
304    /// Timeline for organizing musical events in time
305    Timeline { body: Box<SpannedExpression> },
306    /// Complete musical score
307    Score {
308        name: Option<Box<SpannedExpression>>,
309        body: Box<SpannedExpression>,
310    },
311    /// Movement within a larger work
312    Movement {
313        name: Option<Box<SpannedExpression>>,
314        body: Box<SpannedExpression>,
315    },
316}
317
318/// Statements in the Carmen language.
319///
320/// Statements represent actions or declarations that don't necessarily
321/// produce values. They form the top-level structure of Carmen programs.
322#[derive(Debug, Clone, PartialEq)]
323pub enum Statement {
324    /// Expression used as a statement
325    Expression(SpannedExpression),
326    /// Variable declaration (`let name = value`)
327    VariableDeclaration {
328        name: String,
329        value: SpannedExpression,
330    },
331    /// Function declaration (`fn name(params) { body }`)
332    FunctionDeclaration {
333        name: String,
334        params: Vec<String>,
335        body: Vec<SpannedStatement>,
336    },
337    /// Return statement (`return value`)
338    Return { value: Option<SpannedExpression> },
339    /// Metadata declaration for score information (`@key value`)
340    Metadata {
341        key: String,
342        value: SpannedExpression,
343    },
344}
345
346/// Expression with source location information
347pub type SpannedExpression = Spanned<Expression>;
348/// Statement with source location information
349pub type SpannedStatement = Spanned<Statement>;
350/// Literal with source location information
351pub type SpannedLiteral = Spanned<Literal>;
352
353/// Position of a comment relative to AST nodes.
354///
355/// Comments can appear in various positions relative to the code they
356/// document, and this enum captures those relationships for proper
357/// formatting and preservation during transformations.
358#[derive(Debug, Clone, PartialEq)]
359pub enum CommentPosition {
360    /// Comment appears before the associated AST node
361    Leading,
362    /// Comment appears after the associated AST node on the same line
363    Trailing,
364    /// Comment appears between elements in a container (index of preceding element)
365    Between(usize),
366    /// Standalone comment not associated with any specific AST node
367    Standalone,
368}
369
370/// Information about a comment in the source code.
371///
372/// Comments are preserved during parsing to enable formatting tools
373/// and documentation generation. This struct tracks both the comment
374/// content and its relationship to nearby AST nodes.
375#[derive(Debug, Clone, PartialEq)]
376pub struct CommentInfo {
377    /// Text content of the comment (without comment markers)
378    pub content: String,
379    /// Source location of the comment
380    pub span: Span,
381    /// Position relative to associated AST nodes
382    pub position: CommentPosition,
383    /// Span of the AST node this comment is associated with
384    pub associated_span: Option<Span>,
385}
386
387impl CommentInfo {
388    /// Create a new standalone comment.
389    pub fn new(content: String, span: Span) -> Self {
390        Self {
391            content,
392            span,
393            position: CommentPosition::Standalone,
394            associated_span: None,
395        }
396    }
397
398    /// Associate this comment with an AST node at the given position.
399    pub fn with_position(mut self, position: CommentPosition, associated_span: Span) -> Self {
400        self.position = position;
401        self.associated_span = Some(associated_span);
402        self
403    }
404
405    /// Check if this comment appears on the same line as the given span.
406    pub fn is_same_line(&self, other_span: &Span) -> bool {
407        self.span.start.line == other_span.end.line
408    }
409
410    /// Check if this comment appears before the given span.
411    pub fn is_before(&self, other_span: &Span) -> bool {
412        self.span.end.offset < other_span.end.offset
413    }
414}
415
416/// Root AST node representing a complete Carmen program.
417///
418/// A program consists of a sequence of statements and any comments
419/// found in the source. Comments are preserved to enable formatting
420/// and documentation tools.
421#[derive(Debug, Clone, PartialEq)]
422pub struct Program {
423    /// Top-level statements in the program
424    pub statements: Vec<SpannedStatement>,
425    /// All comments found in the source
426    pub comments: Vec<CommentInfo>,
427    /// Span covering the entire program
428    pub span: Span,
429}
430
431impl Program {
432    /// Get all comments that appear before the given span
433    pub fn leading_comments(&self, span: &Span) -> Vec<&CommentInfo> {
434        self.comments
435            .iter()
436            .filter(|comment| {
437                matches!(comment.position, CommentPosition::Leading)
438                    && (comment.associated_span.as_ref() == Some(span))
439            })
440            .collect()
441    }
442
443    /// Get all comments that appear after the given span on the same line
444    pub fn trailing_comments(&self, span: &Span) -> Vec<&CommentInfo> {
445        self.comments
446            .iter()
447            .filter(|comment| {
448                matches!(comment.position, CommentPosition::Trailing)
449                    && (comment.associated_span.as_ref() == Some(span))
450            })
451            .collect()
452    }
453
454    /// Get comments between elements in a list/tuple/etc
455    pub fn between_comments(
456        &self,
457        container_span: &Span,
458        element_index: usize,
459    ) -> Vec<&CommentInfo> {
460        self.comments
461            .iter()
462            .filter(|comment| {
463                matches!(comment.position, CommentPosition::Between(idx) if idx == element_index)
464                    && comment.associated_span.as_ref().is_some_and(|s| {
465                        s.start.offset >= container_span.start.offset
466                            && s.end.offset <= container_span.end.offset
467                    })
468            })
469            .collect()
470    }
471
472    /// Get all standalone comments (not associated with specific code)
473    pub fn standalone_comments(&self) -> Vec<&CommentInfo> {
474        self.comments
475            .iter()
476            .filter(|comment| matches!(comment.position, CommentPosition::Standalone))
477            .collect()
478    }
479
480    /// Get all comments within a given span range
481    pub fn comments_in_range(&self, start_offset: usize, end_offset: usize) -> Vec<&CommentInfo> {
482        self.comments
483            .iter()
484            .filter(|comment| {
485                comment.span.start.offset >= start_offset && comment.span.end.offset <= end_offset
486            })
487            .collect()
488    }
489
490    /// Check if there are any comments between two spans
491    pub fn has_comments_between(&self, span1: &Span, span2: &Span) -> bool {
492        self.comments.iter().any(|comment| {
493            comment.span.start.offset > span1.end.offset
494                && comment.span.end.offset < span2.start.offset
495        })
496    }
497}
498/// Display implementation for literals that produces valid Carmen syntax.
499impl Display for Literal {
500    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
501        match self {
502            Literal::Number(value) => write!(f, "{value}"),
503            Literal::String(value) => write!(f, "\"{value}\""),
504            Literal::Boolean(value) => write!(f, "{value}"),
505            Literal::Pitch {
506                note,
507                accidentals,
508                octave,
509                ..
510            } => {
511                write!(f, "{}", note.to_uppercase())?;
512                for _ in 0..*accidentals {
513                    write!(f, "#")?;
514                }
515                for _ in 0..accidentals.abs() {
516                    if *accidentals < 0 {
517                        write!(f, "b")?;
518                    }
519                }
520                if let Some(oct) = octave {
521                    write!(f, "{oct}")?;
522                }
523                Ok(())
524            }
525            Literal::Duration {
526                numerator,
527                denominator,
528                dots,
529            } => {
530                write!(f, "{numerator}/{denominator}")?;
531                for _ in 0..*dots {
532                    write!(f, ".")?;
533                }
534                Ok(())
535            }
536            Literal::Dynamic(value) => write!(f, "{value}"),
537            Literal::Attribute(value) => write!(f, "{value}"),
538            Literal::Rest { duration, .. } => {
539                write!(f, "~")?;
540                if let Some(dur) = duration {
541                    write!(f, "{dur}")?;
542                }
543                Ok(())
544            }
545        }
546    }
547}
548
549/// Display implementation for expressions that produces human-readable output.
550impl Display for Expression {
551    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
552        match self {
553            Expression::Literal(lit) => write!(f, "{lit}"),
554            Expression::Identifier(name) => write!(f, "{name}"),
555            Expression::List { elements, .. } => {
556                write!(f, "[")?;
557                for (i, elem) in elements.iter().enumerate() {
558                    if i > 0 {
559                        write!(f, ", ")?;
560                    }
561                    write!(f, "{elem}")?;
562                }
563                write!(f, "]")
564            }
565            Expression::Tuple { elements, .. } => {
566                write!(f, "(")?;
567                for (i, elem) in elements.iter().enumerate() {
568                    if i > 0 {
569                        write!(f, ", ")?;
570                    }
571                    write!(f, "{elem}")?;
572                }
573                write!(f, ")")
574            }
575            Expression::Set { elements, .. } => {
576                write!(f, "{{")?;
577                for (i, elem) in elements.iter().enumerate() {
578                    if i > 0 {
579                        write!(f, ", ")?;
580                    }
581                    write!(f, "{elem}")?;
582                }
583                write!(f, "}}")
584            }
585            Expression::PitchClassSet { classes, .. } => {
586                let names: Vec<String> = classes.iter().map(|&n| format!("{n}")).collect();
587                write!(f, "{{{}}}", names.join(", "))
588            }
589            Expression::MusicalEvent {
590                duration,
591                pitches,
592                dynamic,
593                attributes,
594                ..
595            } => {
596                write!(f, "{duration} {pitches}")?;
597                if let Some(dyn_val) = dynamic {
598                    write!(f, " {dyn_val}")?;
599                }
600                for attr in attributes {
601                    write!(f, " {attr}")?;
602                }
603                Ok(())
604            }
605            Expression::Binary {
606                left,
607                operator,
608                right,
609                ..
610            } => {
611                write!(f, "({} {} {})", left, format_binary_op(operator), right)
612            }
613            Expression::Call {
614                callee,
615                args,
616                kwargs,
617                ..
618            } => {
619                write!(f, "{callee}(")?;
620                for (i, arg) in args.iter().enumerate() {
621                    if i > 0 {
622                        write!(f, ", ")?;
623                    }
624                    write!(f, "{arg}")?;
625                }
626                for (name, value) in kwargs {
627                    if !args.is_empty() {
628                        write!(f, ", ")?;
629                    }
630                    write!(f, "{name}={value}")?;
631                }
632                write!(f, ")")
633            }
634            Expression::Pipe { left, right, .. } => {
635                write!(f, "{left} |> {right}")
636            }
637            Expression::Block { statements, .. } => {
638                write!(f, "{{ {} statements }}", statements.len())
639            }
640            _ => write!(
641                f,
642                "[{}]",
643                std::any::type_name::<Self>()
644                    .split("::")
645                    .last()
646                    .unwrap_or("Expression")
647            ),
648        }
649    }
650}
651
652/// Convert a binary operator to its string representation.
653fn format_binary_op(op: &BinaryOp) -> &'static str {
654    match op {
655        BinaryOp::Add => "+",
656        BinaryOp::Subtract => "-",
657        BinaryOp::Multiply => "*",
658        BinaryOp::Divide => "/",
659        BinaryOp::Modulo => "%",
660        BinaryOp::Equal => "==",
661        BinaryOp::NotEqual => "!=",
662        BinaryOp::Less => "<",
663        BinaryOp::Greater => ">",
664        BinaryOp::LessEqual => "<=",
665        BinaryOp::GreaterEqual => ">=",
666        BinaryOp::And => "and",
667        BinaryOp::Or => "or",
668    }
669}