Skip to main content

sim_lib_music_notation/
model.rs

1use sim_kernel::{Diagnostic, Severity, SourceId, Span};
2use sim_lib_music_core::{MusicError, Score};
3use thiserror::Error;
4
5/// Result of a notation operation paired with any diagnostics produced.
6///
7/// Carries the converted `value` alongside the diagnostics gathered while
8/// importing or exporting, so callers can inspect warnings without losing the
9/// successful result.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct NotationReport<T> {
12    /// Converted value (an exported string or an imported `Score`).
13    pub value: T,
14    /// Diagnostics gathered during the operation.
15    pub diagnostics: Vec<Diagnostic>,
16}
17
18/// Error raised while importing or exporting LilyPond-subset notation.
19#[derive(Debug, Error, Clone, PartialEq, Eq)]
20pub enum NotationError {
21    /// A duration could not be expressed in the supported note-value set.
22    #[error("unsupported duration {0}")]
23    UnsupportedDuration(String),
24    /// A music object kind has no LilyPond-subset rendering.
25    #[error("unsupported music object {0}")]
26    UnsupportedMusicObject(&'static str),
27    /// A key signature string could not be parsed.
28    #[error("invalid key signature {0}")]
29    InvalidKey(String),
30    /// The LilyPond source used syntax outside the supported subset.
31    #[error("unsupported lilypond syntax")]
32    UnsupportedSyntax {
33        /// Diagnostics describing the offending syntax.
34        diagnostics: Vec<Diagnostic>,
35    },
36    /// An error surfaced from the underlying music-core model.
37    #[error(transparent)]
38    Music(#[from] MusicError),
39}
40
41/// Codec converting between a `Score` and its LilyPond-subset text rendering.
42///
43/// Acts as the stateless entry point for the notation surface; each method
44/// delegates to the import or export pipeline.
45#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
46pub struct NotationCodec;
47
48impl NotationCodec {
49    /// Exports a score to LilyPond text, returning the rendering with diagnostics.
50    pub fn export_lilypond_report(
51        &self,
52        score: &Score,
53    ) -> Result<NotationReport<String>, NotationError> {
54        crate::export::export_lilypond_report(score)
55    }
56
57    /// Exports a score to LilyPond text, discarding diagnostics.
58    pub fn export_lilypond(&self, score: &Score) -> Result<String, NotationError> {
59        crate::export::export_lilypond(score)
60    }
61
62    /// Imports a score from LilyPond text, returning the score with diagnostics.
63    pub fn import_lilypond_report(
64        &self,
65        source: &str,
66    ) -> Result<NotationReport<Score>, NotationError> {
67        crate::import::import_lilypond_report(source)
68    }
69
70    /// Imports a score from LilyPond text, discarding diagnostics.
71    pub fn import_lilypond(&self, source: &str) -> Result<Score, NotationError> {
72        crate::import::import_lilypond(source)
73    }
74}
75
76pub(crate) fn error_at(message: impl Into<String>, span: Span) -> NotationError {
77    NotationError::UnsupportedSyntax {
78        diagnostics: vec![Diagnostic {
79            severity: Severity::Error,
80            message: message.into(),
81            source: Some(SourceId("notation:lilypond".to_owned())),
82            span: Some(span),
83            code: None,
84            related: Vec::new(),
85        }],
86    }
87}