1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
//  ERRORS.rs
//    by Lut99
//
//  Created:
//    17 Aug 2022, 11:29:00
//  Last edited:
//    16 Nov 2022, 16:39:28
//  Auto updated?
//    Yes
//
//  Description:
//!   Defines errors that occur in the `brane-dsl` crate. Additionally,
//!   provides some nice formatting options for parser errors.
//

use std::error::Error;
use std::fmt::{Display, Formatter, Result as FResult};

use nom::error::{VerboseError, VerboseErrorKind};

use crate::scanner::{Span, Tokens};
use crate::spec::{Language, TextRange};


/***** FORMATTING *****/
pub(crate) fn convert_parser_error(input: Tokens, e: VerboseError<Tokens>) -> String {
    use std::fmt::Write;

    let mut result = String::new();

    for (i, (tokens, kind)) in e.errors.iter().enumerate() {
        match kind {
            VerboseErrorKind::Char(c) => {
                if tokens.tok.is_empty() {
                    if let Some(mismatch) = input.tok.last() {
                        let mismatch = mismatch.inner();
                        let line = String::from_utf8(mismatch.get_line_beginning().to_vec()).unwrap();
                        let line_number = mismatch.location_line();
                        let column_number = mismatch.get_column() + 1;

                        write!(
                            &mut result,
                            "{i}: at line {line_number}:\n\n{line}\n{caret:>column$}\nexpected '{expected}', but encountered EOF\n\n",
                            i = i,
                            line_number = line_number,
                            line = line,
                            caret = '^',
                            column = column_number,
                            expected = c,
                        )
                        .unwrap();
                    } else {
                        write!(&mut result, "{i}: expected '{c}', but EOF\n\n",).unwrap();
                    }
                } else {
                    let mismatch = tokens.tok[0].inner();
                    let line = String::from_utf8(mismatch.get_line_beginning().to_vec()).unwrap();
                    let line_number = mismatch.location_line();
                    let column_number = mismatch.get_column();
                    let actual = mismatch.fragment();

                    write!(
                        &mut result,
                        "{i}: at line {line_number}:\n\n{line}\n{caret:>column$}\nexpected '{expected}', found '{actual}'\n\n",
                        i = i,
                        line_number = line_number,
                        line = line,
                        caret = '^',
                        column = column_number,
                        expected = c,
                        actual = actual,
                    )
                    .unwrap();
                }
            },
            VerboseErrorKind::Nom(nom::error::ErrorKind::Tag) => {
                let mismatch = tokens.tok[0].inner();
                let line = String::from_utf8(mismatch.get_line_beginning().to_vec()).unwrap();
                let line_number = mismatch.location_line();
                let column_number = mismatch.get_column();
                let actual = mismatch.fragment();

                write!(
                    &mut result,
                    "{i}: at line {line_number}:\n{line}\n{caret:>column$}\nunexpected token '{actual}'\n\n",
                    i = i,
                    line_number = line_number,
                    line = line,
                    caret = '^',
                    column = column_number,
                    actual = actual,
                )
                .unwrap();
            },
            VerboseErrorKind::Context(s) => {
                let mismatch = tokens.tok[0].inner();
                let line = String::from_utf8(mismatch.get_line_beginning().to_vec()).unwrap();

                writeln!(result, "{i} in section '{s}', at: {line}").unwrap()
            },
            e => {
                writeln!(result, "Compiler error: unkown error from parser: {e:?}").unwrap();
            },
        }
    }

    result
}

pub(crate) fn convert_scanner_error(input: Span, e: VerboseError<Span>) -> String {
    use std::fmt::Write;

    use nom::Offset;

    let mut result = String::new();

    for (i, (substring, kind)) in e.errors.iter().enumerate() {
        let offset = input.offset(substring);

        if input.is_empty() {
            match kind {
                VerboseErrorKind::Char(c) => write!(&mut result, "{i}: expected '{c}', got empty input\n\n"),
                VerboseErrorKind::Context(s) => write!(&mut result, "{i}: in {s}, got empty input\n\n"),
                VerboseErrorKind::Nom(e) => write!(&mut result, "{i}: in {e:?}, got empty input\n\n"),
            }
        } else {
            let prefix = &input.as_bytes()[..offset];

            // Count the number of newlines in the first `offset` bytes of input
            let line_number = prefix.iter().filter(|&&b| b == b'\n').count() + 1;

            // Find the line that includes the subslice:
            // Find the *last* newline before the substring starts
            let line_begin = prefix
                .iter()
                .rev()
                .position(|&b| b == b'\n')
                .map(|pos| offset - pos)
                .unwrap_or(0);

            // Find the full line after that newline
            let line = input[line_begin..]
                .lines()
                .next()
                .unwrap_or(&input[line_begin..])
                .trim_end();

            // The (1-indexed) column number is the offset of our substring into that line
            let column_number = line.offset(substring) + 1;

            match kind {
                VerboseErrorKind::Char(c) => {
                    if let Some(actual) = substring.chars().next() {
                        write!(
                            &mut result,
                            "{i}: at line {line_number}:\n\
                 {line}\n\
                 {caret:>column$}\n\
                 expected '{expected}', found {actual}\n\n",
                            i = i,
                            line_number = line_number,
                            line = line,
                            caret = '^',
                            column = column_number,
                            expected = c,
                            actual = actual,
                        )
                    } else {
                        write!(
                            &mut result,
                            "{i}: at line {line_number}:\n\
                 {line}\n\
                 {caret:>column$}\n\
                 expected '{expected}', got end of input\n\n",
                            i = i,
                            line_number = line_number,
                            line = line,
                            caret = '^',
                            column = column_number,
                            expected = c,
                        )
                    }
                }
                VerboseErrorKind::Context(s) => write!(
                    &mut result,
                    "{i}: at line {line_number}, in {context}:\n\
               {line}\n\
               {caret:>column$}\n\n",
                    i = i,
                    line_number = line_number,
                    context = s,
                    line = line,
                    caret = '^',
                    column = column_number,
                ),
                VerboseErrorKind::Nom(e) => write!(
                    &mut result,
                    "{i}: at line {line_number}, in {nom_err:?}:\n\
               {line}\n\
               {caret:>column$}\n\n",
                    i = i,
                    line_number = line_number,
                    nom_err = e,
                    line = line,
                    caret = '^',
                    column = column_number,
                ),
            }
        }
        // Because `write!` to a `String` is infallible, this `unwrap` is fine.
        .unwrap();
    }

    result
}





/***** ERRORS *****/
/// Defines errors that relate to the SymbolTable.
#[derive(Debug)]
pub enum SymbolTableError {
    /// A given function already existed in the SymbolTable and could not be easily shadowed.
    DuplicateFunction { name: String, existing: TextRange, got: TextRange },
    /// A given class already existed in the SymbolTable and could not be easily shadowed.
    DuplicateClass { name: String, existing: TextRange, got: TextRange },
    /// A given variable already existed in the SymbolTable and could not be easily shadowed.
    DuplicateVariable { name: String, existing: TextRange, got: TextRange },
    /// A given field (property or method) already existing in the given class.
    DuplicateField { c_name: String, name: String, existing: TextRange, got: TextRange },
}

impl Display for SymbolTableError {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        use SymbolTableError::*;
        match self {
            DuplicateFunction { name, .. } => write!(f, "Duplicate definition of function '{name}'"),
            DuplicateClass { name, .. } => write!(f, "Duplicate definition of class '{name}'"),
            DuplicateVariable { name, .. } => write!(f, "Duplicate definition of variable '{name}'"),
            DuplicateField { c_name, name, .. } => write!(f, "Duplicate definition of field '{name}' in class '{c_name}'"),
        }
    }
}

impl Error for SymbolTableError {}



/// Defines errors that occur when converting language identifiers to Language enums.
#[derive(Debug)]
pub enum LanguageParseError {
    /// Encountered an unknown language ID.
    UnknownLanguageId { raw: String },
}

impl Display for LanguageParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        use LanguageParseError::*;
        match self {
            UnknownLanguageId { raw } => write!(f, "Unknown language ID '{raw}'"),
        }
    }
}

impl Error for LanguageParseError {}

/// Defines errors that occur when converting patterns to calls.
#[derive(Debug)]
pub enum PatternError {
    /// The given pattern was unknown
    UnknownPattern { raw: String, range: TextRange },
}

impl Display for PatternError {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        use PatternError::*;
        match self {
            UnknownPattern { raw, .. } => write!(f, "Pattern '{raw}' is unknown (are you missing a package import?)"),
        }
    }
}

impl Error for PatternError {}



/// Defines errors that occur in the topmost process of conveting raw readers into Programs.
#[derive(Debug)]
pub enum ParseError {
    /// The scanner failed to scan.
    ScanError { err: String },
    /// Some non-Nom error occurred while scanning.
    ScannerError { err: String },
    /// Not all source was parsed (indicating a syntax error).
    LeftoverSourceError,

    /// The parser failed to parse.
    ParseError { lang: Language, err: String },
    /// Some non-Nom error occurred while parsing.
    ParserError { lang: Language, err: String },
    /// We did not have enough tokens to make an informed decision (i.e., an error occurred).
    Eof { lang: Language, err: String },
    /// Not all tokens were parsed (indicating an error).
    LeftoverTokensError { lang: Language },
}

impl Display for ParseError {
    #[inline]
    fn fmt(&self, f: &mut Formatter<'_>) -> FResult {
        use self::ParseError::*;
        match self {
            ScanError { err } => write!(f, "Syntax error: {err}"),
            ScannerError { err } => write!(f, "Syntax error: {err}"),
            LeftoverSourceError => write!(f, "Syntax error: not all input could be parsed"),

            ParseError { lang, err } => write!(f, "{lang} parse error: {err}"),
            ParserError { lang, err } => write!(f, "{lang} parse error: {err}"),
            Eof { lang, err } => write!(f, "{lang} parse error: reached end-of-file unexpectedly ({err})"),
            LeftoverTokensError { lang } => write!(f, "{lang} parse error: not all input could be parsed"),
        }
    }
}

impl Error for ParseError {}