1pub 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#[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 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 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 let octave = if i < chars.len() {
84 let remaining: String = chars[i..].iter().collect();
85 if remaining.chars().all(|c| c.is_ascii_digit()) {
87 remaining.parse().ok()
88 } else {
89 return None; }
91 } else {
92 None
93 };
94
95 Some(Literal::Pitch {
96 note,
97 accidentals,
98 octave,
99 })
100 }
101
102 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 pub fn is_attribute(text: &str) -> bool {
116 matches!(
117 text,
118 "staccato" | "legato" | "accent" | "tenuto" | "marcato" | "sforzando"
119 )
120 }
121
122 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) } else {
155 return None;
156 };
157
158 Some(Literal::Duration {
159 numerator,
160 denominator,
161 dots,
162 })
163 }
164
165 pub fn parse_rest(text: &str) -> Option<Self> {
172 if !text.starts_with('~') {
173 return None;
174 }
175
176 if text == "~" {
177 return Some(Literal::Rest { duration: None });
179 }
180
181 let duration_str = &text[1..];
183 Self::parse_duration(duration_str)
184 }
185}
186
187#[derive(Debug, Clone, PartialEq)]
189pub enum BinaryOp {
190 Add,
192 Subtract,
194 Multiply,
196 Divide,
198 Modulo,
200 Equal,
202 NotEqual,
204 Less,
206 Greater,
208 LessEqual,
210 GreaterEqual,
212 And,
214 Or,
216}
217
218#[derive(Debug, Clone, PartialEq)]
220pub enum UnaryOp {
221 Minus,
223 Not,
225}
226
227#[derive(Debug, Clone, PartialEq)]
233pub enum Expression {
234 Literal(Literal),
236 Identifier(String),
238 Binary {
240 left: Box<SpannedExpression>,
241 operator: BinaryOp,
242 right: Box<SpannedExpression>,
243 },
244 Unary {
246 operator: UnaryOp,
247 operand: Box<SpannedExpression>,
248 },
249 Call {
251 callee: Box<SpannedExpression>,
252 args: Vec<SpannedExpression>,
253 kwargs: Vec<(String, SpannedExpression)>,
254 },
255 Pipe {
257 left: Box<SpannedExpression>,
258 right: Box<SpannedExpression>,
259 },
260 List { elements: Vec<SpannedExpression> },
262 Tuple { elements: Vec<SpannedExpression> },
264 Set { elements: Vec<SpannedExpression> },
266 Block { statements: Vec<SpannedStatement> },
268 If {
270 condition: Box<SpannedExpression>,
271 then_branch: Box<SpannedExpression>,
272 else_branch: Option<Box<SpannedExpression>>,
273 },
274 For {
276 variable: String,
277 iterable: Box<SpannedExpression>,
278 body: Box<SpannedExpression>,
279 },
280 While {
282 condition: Box<SpannedExpression>,
283 body: Box<SpannedExpression>,
284 },
285 PitchClassSet { classes: Vec<u8> },
287 MusicalEvent {
289 duration: Box<SpannedExpression>,
290 pitches: Box<SpannedExpression>,
291 dynamic: Option<Box<SpannedExpression>>,
292 attributes: Vec<SpannedExpression>,
293 },
294 Part {
296 name: Option<Box<SpannedExpression>>,
297 body: Box<SpannedExpression>,
298 },
299 Staff {
301 number: Box<SpannedExpression>,
302 body: Box<SpannedExpression>,
303 },
304 Timeline { body: Box<SpannedExpression> },
306 Score {
308 name: Option<Box<SpannedExpression>>,
309 body: Box<SpannedExpression>,
310 },
311 Movement {
313 name: Option<Box<SpannedExpression>>,
314 body: Box<SpannedExpression>,
315 },
316}
317
318#[derive(Debug, Clone, PartialEq)]
323pub enum Statement {
324 Expression(SpannedExpression),
326 VariableDeclaration {
328 name: String,
329 value: SpannedExpression,
330 },
331 FunctionDeclaration {
333 name: String,
334 params: Vec<String>,
335 body: Vec<SpannedStatement>,
336 },
337 Return { value: Option<SpannedExpression> },
339 Metadata {
341 key: String,
342 value: SpannedExpression,
343 },
344}
345
346pub type SpannedExpression = Spanned<Expression>;
348pub type SpannedStatement = Spanned<Statement>;
350pub type SpannedLiteral = Spanned<Literal>;
352
353#[derive(Debug, Clone, PartialEq)]
359pub enum CommentPosition {
360 Leading,
362 Trailing,
364 Between(usize),
366 Standalone,
368}
369
370#[derive(Debug, Clone, PartialEq)]
376pub struct CommentInfo {
377 pub content: String,
379 pub span: Span,
381 pub position: CommentPosition,
383 pub associated_span: Option<Span>,
385}
386
387impl CommentInfo {
388 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 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 pub fn is_same_line(&self, other_span: &Span) -> bool {
407 self.span.start.line == other_span.end.line
408 }
409
410 pub fn is_before(&self, other_span: &Span) -> bool {
412 self.span.end.offset < other_span.end.offset
413 }
414}
415
416#[derive(Debug, Clone, PartialEq)]
422pub struct Program {
423 pub statements: Vec<SpannedStatement>,
425 pub comments: Vec<CommentInfo>,
427 pub span: Span,
429}
430
431impl Program {
432 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 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 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 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 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 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}
498impl 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
549impl 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
652fn 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}