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
//! Shared stuff between the tokenizer and the parser.

/// Error codes we might receive when tokenizing and parsing.
#[derive(PartialEq, Debug)]
pub enum ErrorCode {
    UnterminatedNormalString,
    UnterminatedExtendedString,
    UnterminatedMultiLineComment,
    UnexpectedClosingDelimiter,
    DelimiterMismatch,
    PotentialErroneousClosingDelimiterFollowedByComment,
    UnclosedDelimiter,
}

/// Collection of error information returned to the user if there's a problem tokenizing or parsing
/// the input.
///
/// NOTE: maybe find a better name?  This is both for tokenizing and parsing.
#[derive(Debug)]
pub struct ParsingError {
    pub error_code: ErrorCode,
    pub error_message: String,
}

impl ParsingError {
    /// This is for tokenization
    ///
    /// TODO: maybe need differnt ones for parsing since we have access to different kind of
    /// information.
    ///
    /// TODO: in future add hints or support for hints.
    #[inline]
    pub fn new(
        code: &[u8],
        start_index: usize,
        end_index: usize,
        error_code: ErrorCode,
        error_message: &str,
    ) -> ParsingError {
        // &[u8] -> ... -> String
        // TODO: in future remove these attributes
        #![allow(unused_variables, unused_assignments)]

        // TODO: add better error messages in the future

        let code = String::from_utf8_lossy(code);

        let error_message = String::from(error_message);

        let si = start_index;

        let mut sl = 0;

        let mut sc = 0;

        let ei = end_index;
        let mut el = 0;
        let mut ec = 0;

        // Find line and column for start and end index

        {
            let mut code_index = 0;
            let mut line_index = 0;
            let mut column_index = 0;

            let mut count = 2;

            for c in code.chars() {
                if count == 0 {
                    break;
                }

                if si == code_index {
                    // TODO: in future remove this attribute

                    sl = line_index;
                    sc = column_index;

                    count -= 1;
                }

                if ei == code_index {
                    el = line_index;
                    ec = column_index;

                    count -= 1;
                }

                // End

                code_index += 1;

                if c == '\n' {
                    line_index += 1;
                    column_index = 0;
                } else {
                    column_index += 1;
                }
            }
        }

        let lines: Vec<&str> = code.split("\n").collect();

        let mut msg: Vec<String> = Vec::new();

        let end_line_number_string = format!("{}", el + 1);

        let mut gutter_size = 0;
        gutter_size = std::cmp::max(gutter_size, end_line_number_string.len());

        let e_line = lines.get(el).unwrap();

        msg.push(String::from(""));
        msg.push(format!(" {} | {}", end_line_number_string, e_line));
        msg.push(format!(
            " {} | {}^",
            " ".repeat(gutter_size),
            " ".repeat(ec)
        ));

        msg.push(String::from(""));
        msg.push(format!("{}", error_message));

        let error_message = msg.join("\n");

        ParsingError {
            error_code,
            error_message,
        }
    }
}

// TODO: write a function that creates an error message based off of the error code and etc.