powerio_dist/glm/
project.rs1use 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#[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 #[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
111pub 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}