Skip to main content

powerio_dist/glm/
project.rs

1use powerio_core::{Diagnostic, DiagnosticInfo, Error, Source, SourceId, SourceSpan};
2
3use super::{
4    GlmDialect, PreparedGlm, PreprocessOptions, RawGlm, Statement, parse_raw_source,
5    preprocess_source,
6};
7use crate::diagnostics::codes;
8
9/// Source project and expanded syntax, without electrical interpretation.
10/// Syntax spans address the expansion; [`Self::original_spans`] locates the
11/// corresponding original bytes, including substitutions and included files.
12#[derive(Clone, Debug)]
13pub struct GlmProject {
14    prepared: PreparedGlm,
15    syntax: RawGlm,
16    dialect: GlmDialect,
17}
18
19impl GlmProject {
20    #[must_use]
21    pub const fn dialect(&self) -> GlmDialect {
22        self.dialect
23    }
24
25    #[must_use]
26    pub fn source(&self) -> &Source {
27        &self.prepared.source
28    }
29
30    #[must_use]
31    pub fn statements(&self) -> &[Statement] {
32        &self.syntax.statements
33    }
34
35    #[must_use]
36    pub fn expanded_text(&self) -> &str {
37        self.prepared.text()
38    }
39
40    #[must_use]
41    pub fn dependencies(&self) -> &[SourceId] {
42        self.prepared.dependencies()
43    }
44
45    pub(super) fn has_absolute_include(&self) -> bool {
46        self.prepared.has_absolute_include()
47    }
48
49    /// Translate a span from this project's syntax into original source spans.
50    #[must_use]
51    pub fn original_spans(&self, span: &SourceSpan) -> Vec<SourceSpan> {
52        self.prepared
53            .original_spans(span.byte_start() as usize..span.byte_end() as usize)
54    }
55
56    pub(crate) fn fail(&self, span: &SourceSpan, message: impl Into<String>) -> Error {
57        mapped_error(&self.prepared, &codes::PARSE_GLM_OBJECT, span, message)
58    }
59
60    pub(super) fn electrical_error(&self, span: &SourceSpan, message: impl Into<String>) -> Error {
61        mapped_error(
62            &self.prepared,
63            &codes::READ_GLM_ELECTRICAL_UNSUPPORTED,
64            span,
65            message,
66        )
67    }
68
69    pub(super) fn quantity_error(&self, span: &SourceSpan, message: &str) -> Error {
70        mapped_error(&self.prepared, &codes::PARSE_GLM_QUANTITY, span, message)
71    }
72
73    pub(super) fn ignored_quantity_unit(
74        &self,
75        span: &SourceSpan,
76        suffix: &str,
77    ) -> Result<Diagnostic, Error> {
78        let mut diagnostic = Diagnostic::of(
79            &codes::READ_GLM_UNIT_IGNORED,
80            format!(
81                "native quantity conversion ignores suffix {suffix:?}; retained the native numeric value"
82            ),
83        );
84        for original in self
85            .prepared
86            .original_span_iter(span.byte_start() as usize..span.byte_end() as usize)
87            .take(powerio_core::limits::MAX_DIAGNOSTIC_SPANS + 1)
88        {
89            diagnostic = diagnostic.with_span(original)?;
90        }
91        Ok(diagnostic)
92    }
93}
94
95fn mapped_error(
96    prepared: &PreparedGlm,
97    info: &'static DiagnosticInfo,
98    span: &SourceSpan,
99    message: impl Into<String>,
100) -> Error {
101    let mut result = Error::new(info, message).with_source(prepared.source.clone());
102    for original in prepared
103        .original_span_iter(span.byte_start() as usize..span.byte_end() as usize)
104        .take(powerio_core::limits::MAX_DIAGNOSTIC_SPANS + 1)
105    {
106        result = result.with_span(original);
107    }
108    result
109}
110
111/// Preprocess the supported explicit profile and parse its complete syntax.
112/// Failures retain the original project and point into original buffers rather
113/// than presenting expanded byte offsets as if they belonged to an input file.
114pub fn parse_project(source: Source, options: PreprocessOptions) -> Result<GlmProject, Error> {
115    let dialect = options.dialect;
116    let prepared = preprocess_source(source, options)?;
117    let expanded = Source::from_memory("expanded.glm", prepared.text().as_bytes())?;
118    match parse_raw_source(expanded) {
119        Ok(syntax) => Ok(GlmProject {
120            prepared,
121            syntax,
122            dialect,
123        }),
124        Err(error) => {
125            let Some(first) = error.diagnostics().first() else {
126                return Err(error.with_source(prepared.source));
127            };
128            let info = first.registered_info().unwrap_or(&codes::PARSE_GLM_SYNTAX);
129            if let Some(span) = first.spans().first() {
130                Err(mapped_error(&prepared, info, span, first.message()))
131            } else {
132                Err(Error::new(info, first.message()).with_source(prepared.source))
133            }
134        }
135    }
136}