mxmlextrema_as3parser/diagnostics/
diagnostics.rs

1use std::any::Any;
2
3use realhydroper_path::FlexPath;
4use maplit::hashmap;
5use crate::ns::*;
6
7use lazy_regex::*;
8
9#[path = "diagnostics_english_resources.rs"]
10mod diagnostics_english_resources;
11
12/// Represents a diagnostic originated from a compilation unit.
13/// 
14/// Arguments are formatted using integer keys counted from 1 (one).
15#[derive(Clone)]
16pub struct Diagnostic {
17    pub(crate) location: Location,
18    pub(crate) kind: DiagnosticKind,
19    pub(crate) is_warning: bool,
20    pub(crate) is_verify_error: bool,
21    pub(crate) arguments: Vec<Rc<dyn DiagnosticArgument>>,
22    pub(crate) custom_kind: RefCell<Option<Rc<dyn Any>>>,
23}
24
25impl Eq for Diagnostic {}
26
27impl PartialEq for Diagnostic {
28    fn eq(&self, other: &Self) -> bool {
29        self.location == other.location &&
30        self.kind == other.kind
31    }
32}
33
34impl Ord for Diagnostic {
35    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
36        self.location.cmp(&other.location)
37    }
38}
39
40impl PartialOrd for Diagnostic {
41    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
42        self.location.partial_cmp(&other.location)
43    }
44}
45
46impl Diagnostic {
47    pub fn new_syntax_error(location: &Location, kind: DiagnosticKind, arguments: Vec<Rc<dyn DiagnosticArgument>>) -> Self {
48        Self {
49            location: location.clone(),
50            kind,
51            is_verify_error: false,
52            is_warning: false,
53            arguments,
54            custom_kind: RefCell::new(None),
55        }
56    }
57
58    pub fn new_verify_error(location: &Location, kind: DiagnosticKind, arguments: Vec<Rc<dyn DiagnosticArgument>>) -> Self {
59        Self {
60            location: location.clone(),
61            kind,
62            is_verify_error: true,
63            is_warning: false,
64            arguments,
65            custom_kind: RefCell::new(None),
66        }
67    }
68
69    pub fn new_warning(location: &Location, kind: DiagnosticKind, arguments: Vec<Rc<dyn DiagnosticArgument>>) -> Self {
70        Self {
71            location: location.clone(),
72            kind,
73            is_verify_error: false,
74            is_warning: true,
75            arguments,
76            custom_kind: RefCell::new(None),
77        }
78    }
79
80    pub fn location(&self) -> Location {
81        self.location.clone()
82    }
83
84    pub fn kind(&self) -> DiagnosticKind {
85        self.kind.clone()
86    }
87
88    pub fn is_warning(&self) -> bool {
89        self.is_warning
90    }
91
92    pub fn is_error(&self) -> bool {
93        !self.is_warning
94    }
95
96    pub fn is_syntax_error(&self) -> bool {
97        !self.is_verify_error && !self.is_warning
98    }
99
100    pub fn is_verify_error(&self) -> bool {
101        self.is_verify_error
102    }
103
104    pub fn arguments(&self) -> Vec<Rc<dyn DiagnosticArgument>> {
105        self.arguments.clone()
106    }
107
108    pub fn id(&self) -> i32 {
109        self.kind.id()
110    }
111
112    pub fn custom_kind(&self) -> Option<Rc<dyn Any>> {
113        self.custom_kind.borrow().clone()
114    }
115
116    pub fn set_custom_kind(&self, id: Option<Rc<dyn Any>>) {
117        self.custom_kind.replace(id);
118    }
119
120    /// Formats the diagnostic by overriding the message text.
121    pub fn format_with_message(&self, message: &str, id: Option<i32>) -> String {
122        self.format_with_message_and_base_path(message, id, None)
123    }
124
125    /// Formats the diagnostic by overriding the message text and providing a base Whack package's path
126    /// (to relativize the source path).
127    pub fn format_with_message_and_base_path(&self, message: &str, id: Option<i32>, base_path: Option<&str>) -> String {
128        let category = (if self.is_verify_error {
129            "Verify error"
130        } else if self.is_warning {
131            "Warning"
132        } else {
133            "Syntax error"
134        }).to_owned();
135
136        let mut file_path = self.location.compilation_unit.file_path.clone().map_or("".to_owned(), |s| format!("{s}:"));
137        if let Some(base_path) = base_path {
138            file_path = FlexPath::new_native(base_path).relative(&file_path).to_owned();
139        }
140        if regex_is_match!(r"^\\\\\?\\[uU][nN][cC][\\/]", &file_path) {
141            file_path = r"\\".to_owned() + &file_path[8..].to_owned();
142        } else if file_path.starts_with(r"\\?\") {
143            file_path = file_path[4..].to_owned();
144        }
145        let line = self.location.first_line_number();
146        let column = self.location.first_column() + 1;
147        if let Some(id) = id {
148            format!("{file_path}{line}:{column}: {category} #{}: {message}", id.to_string())
149        } else {
150            format!("{file_path}{line}:{column}: {category}: {message}")
151        }
152    }
153
154    /// Formats the diagnostic in English.
155    pub fn format_english(&self) -> String {
156        self.format_with_message(&self.format_message_english(), Some(self.id()))
157    }
158
159    /// Formats the diagnostic in English.
160    pub fn format_english_with_base_path(&self, base_path: &str) -> String {
161        self.format_with_message_and_base_path(&self.format_message_english(), Some(self.id()), Some(base_path))
162    }
163
164    pub fn format_message_english(&self) -> String {
165        self.format_message(&diagnostics_english_resources::DATA)
166    }
167
168    pub fn format_message(&self, messages: &HashMap<i32, String>) -> String {
169        let mut string_arguments: HashMap<String, String> = hashmap!{};
170        let mut i = 1;
171        for argument in &self.arguments {
172            string_arguments.insert(i.to_string(), argument.to_string());
173            i += 1;
174        }
175        use realhydroper_lateformat::LateFormat;
176        let Some(msg) = messages.get(&self.id()) else {
177            let id = self.id();
178            panic!("Message resource is missing for ID {id}");
179        };
180        msg.realhydroper_lateformat(string_arguments)
181    }
182}
183
184/// The `diagarg![...]` literal is used for initializing
185/// diagnostic arguments.
186/// 
187/// For example: `diagarg![token, "foo".to_owned()]`.
188pub macro diagarg {
189    ($($value:expr),*) => { vec![ $(Rc::new($value)),* ] },
190}
191
192pub trait DiagnosticArgument: Any + ToString + 'static {
193}
194
195impl DiagnosticArgument for String {}
196
197impl DiagnosticArgument for Token {}