1pub mod ast;
5pub mod bms;
6pub mod copy;
7pub mod csd;
8mod debugging;
9pub mod dli;
10pub mod extended;
11pub mod feedback;
12pub mod jni;
13pub mod lexer;
14pub mod parser;
15pub mod report;
16pub mod source;
17pub mod sql;
18pub mod system;
19
20use std::fmt;
21
22pub use rt::vocab::Pos;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub enum Severity {
28 Informational,
30 Warning,
32 Error,
34 Severe,
36 Unrecoverable,
38}
39
40impl Severity {
41 pub const fn return_code(self) -> u8 {
43 match self {
44 Self::Informational => 0,
45 Self::Warning => 4,
46 Self::Error => 8,
47 Self::Severe => 12,
48 Self::Unrecoverable => 16,
49 }
50 }
51
52 pub const fn label(self) -> Option<&'static str> {
54 match self {
55 Self::Informational => Some("informational"),
56 Self::Warning => Some("warning"),
57 Self::Error | Self::Severe | Self::Unrecoverable => None,
58 }
59 }
60}
61
62#[derive(Clone, Debug, PartialEq, Eq)]
64pub struct Error {
65 pub pos: Pos,
66 pub message: String,
67 pub file: Option<String>,
69 pub severity: Severity,
70}
71
72impl Error {
73 pub fn at(pos: Pos, message: impl Into<String>) -> Self {
74 Self { pos, message: message.into(), file: None, severity: Severity::Severe }
75 }
76
77 pub fn warning(pos: Pos, message: impl Into<String>) -> Self {
78 Self::at(pos, message).graded(Severity::Warning)
79 }
80
81 pub fn graded(mut self, severity: Severity) -> Self {
82 self.severity = severity;
83 self
84 }
85
86 pub fn in_files(mut self, files: &[String]) -> Self {
88 self.file = files.get(self.pos.file as usize).filter(|f| !f.is_empty()).cloned();
89 self
90 }
91
92 pub fn place(&self, main: &str) -> String {
95 match (&self.file, self.pos.line) {
96 (Some(f), _) => format!("{f}:{self}"),
97 (None, 0) => format!("{main}: {}", self.labelled()),
98 (None, _) => format!("{main}:{self}"),
99 }
100 }
101
102 pub fn labelled(&self) -> String {
104 match self.severity.label() {
105 Some(label) => format!("{label}: {}", self.message),
106 None => self.message.clone(),
107 }
108 }
109}
110
111impl fmt::Display for Error {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(f, "{}: {}", self.pos, self.labelled())
114 }
115}
116
117pub fn return_code(messages: &[Error]) -> u8 {
119 messages.iter().map(|m| m.severity.return_code()).max().unwrap_or(0)
120}
121
122pub fn most_severe(messages: &[Error]) -> Option<&Error> {
124 messages.iter().rev().max_by_key(|m| m.severity)
125}
126
127impl std::error::Error for Error {}
128
129pub fn parse(text: &str) -> Result<ast::Program, Error> {
131 parse_with(text, ©::Libraries::default())
132}
133
134pub fn parse_with(text: &str, libraries: ©::Libraries) -> Result<ast::Program, Error> {
136 Ok(parse_all_with(text, libraries)?.remove(0))
137}
138
139pub fn parse_all_with(text: &str, libraries: ©::Libraries) -> Result<Vec<ast::Program>, Error> {
142 let compliance = libraries.compliance();
143 let mut files = vec![String::new()];
144 let mut source = source::read_under(text, 0, false, compliance).and_then(|s| copy::expand(s, libraries, &mut files)).and_then(copy::replace).map_err(|e| e.in_files(&files))?;
145 let mut tokens = lexer::lex_under(&source, compliance).map_err(|e| e.in_files(&files))?;
146 if debugging::requested(&tokens) {
147 files.truncate(1);
148 source = source::read_under(text, 0, true, compliance).and_then(|s| copy::expand(s, libraries, &mut files)).and_then(copy::replace).map_err(|e| e.in_files(&files))?;
149 let lexed = lexer::lex_under(&source, compliance).map_err(|e| e.in_files(&files))?;
150 tokens = debugging::keep(lexed, source.debugging.as_deref().unwrap_or_default());
151 }
152 if compliance == numeric::Compliance::Extended {
153 tokens = extended::rewrite(tokens, &source.options).map_err(|e| e.in_files(&files))?;
154 }
155 let mut programs = parser::parse(&tokens, source.options).map_err(|e| e.in_files(&files))?;
156 for p in &mut programs {
157 p.sources = files.clone();
158 }
159 Ok(programs)
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 const ALL: [Severity; 5] = [Severity::Informational, Severity::Warning, Severity::Error, Severity::Severe, Severity::Unrecoverable];
167
168 fn message(severity: Severity, line: u32) -> Error {
169 Error::at(Pos { file: 0, line, col: 8 }, format!("m{line}")).graded(severity)
170 }
171
172 #[test]
173 fn severities_rise_with_ibms_return_codes() {
174 assert_eq!(ALL.map(Severity::return_code), [0, 4, 8, 12, 16]);
175 assert!(ALL.windows(2).all(|w| w[0] < w[1]));
176 assert_eq!(Error::at(Pos::default(), "m").severity, Severity::Severe);
177 assert_eq!(Error::warning(Pos::default(), "m").severity, Severity::Warning);
178 }
179
180 #[test]
181 fn a_compilation_returns_the_highest_code_of_its_messages() {
182 let codes = |severities: &[Severity]| return_code(&severities.iter().enumerate().map(|(i, &s)| message(s, i as u32 + 1)).collect::<Vec<_>>());
183 use Severity::*;
184 assert_eq!(codes(&[]), 0);
185 assert_eq!(codes(&[Informational, Informational]), 0);
186 assert_eq!(codes(&[Informational, Warning]), 4);
187 assert_eq!(codes(&[Warning, Error, Informational]), 8);
188 assert_eq!(codes(&[Error, Severe, Warning]), 12);
189 assert_eq!(codes(&[Informational, Warning, Error, Severe]), 12);
190 assert_eq!(codes(&[Severe, Unrecoverable]), 16);
191 }
192
193 #[test]
194 fn the_message_a_refusal_names_is_the_first_of_the_most_severe() {
195 let messages = [message(Severity::Warning, 1), message(Severity::Severe, 2), message(Severity::Error, 3), message(Severity::Severe, 4)];
196 assert_eq!(most_severe(&messages).map(|m| m.pos.line), Some(2));
197 assert!(most_severe(&[]).is_none());
198 }
199
200 #[test]
201 fn an_error_line_carries_no_label_and_a_warning_or_informational_one_says_which_it_is() {
202 let lines: Vec<String> = ALL.map(|s| message(s, 7).place("p.cbl")).to_vec();
203 assert_eq!(lines, ["p.cbl:7:8: informational: m7", "p.cbl:7:8: warning: m7", "p.cbl:7:8: m7", "p.cbl:7:8: m7", "p.cbl:7:8: m7"]);
204 let unplaced = |s: Severity| Error::at(Pos::default(), "m").graded(s).place("p.cbl");
205 assert_eq!((unplaced(Severity::Severe), unplaced(Severity::Warning)), ("p.cbl: m".to_string(), "p.cbl: warning: m".to_string()));
206 let in_member = Error::warning(Pos { file: 1, line: 3, col: 8 }, "m3").in_files(&[String::new(), "COPYA".into()]);
207 assert_eq!(in_member.place("p.cbl"), "COPYA:3:8: warning: m3");
208 assert_eq!(message(Severity::Severe, 3).to_string(), "3:8: m3");
209 }
210}