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
use rustpython_parser::error::{LexicalErrorType, ParseError, ParseErrorType};
use rustpython_parser::location::Location;
use rustpython_parser::token::Tok;

use std::error::Error;
use std::fmt;

#[derive(Debug)]
pub struct CompileError {
    pub statement: Option<String>,
    pub error: CompileErrorType,
    pub location: Location,
    pub source_path: Option<String>,
}

impl CompileError {
    pub fn update_statement_info(&mut self, statement: String) {
        self.statement = Some(statement);
    }

    pub fn update_source_path(&mut self, source_path: &str) {
        debug_assert!(self.source_path.is_none());
        self.source_path = Some(source_path.to_owned());
    }
}

impl From<ParseError> for CompileError {
    fn from(error: ParseError) -> Self {
        CompileError {
            statement: None,
            error: CompileErrorType::Parse(error.error),
            location: error.location,
            source_path: None,
        }
    }
}

#[derive(Debug)]
pub enum CompileErrorType {
    /// Invalid assignment, cannot store value in target.
    Assign(&'static str),
    /// Invalid delete
    Delete(&'static str),
    /// Expected an expression got a statement
    ExpectExpr,
    /// Parser error
    Parse(ParseErrorType),
    SyntaxError(String),
    /// Multiple `*` detected
    StarArgs,
    /// Break statement outside of loop.
    InvalidBreak,
    /// Continue statement outside of loop.
    InvalidContinue,
    InvalidReturn,
    InvalidYield,
}

impl CompileError {
    pub fn is_indentation_error(&self) -> bool {
        if let CompileErrorType::Parse(parse) = &self.error {
            match parse {
                ParseErrorType::Lexical(LexicalErrorType::IndentationError) => true,
                ParseErrorType::UnrecognizedToken(token, expected) => {
                    *token == Tok::Indent || expected.clone() == Some("Indent".to_owned())
                }
                _ => false,
            }
        } else {
            false
        }
    }

    pub fn is_tab_error(&self) -> bool {
        if let CompileErrorType::Parse(parse) = &self.error {
            if let ParseErrorType::Lexical(lex) = parse {
                if let LexicalErrorType::TabError = lex {
                    return true;
                }
            }
        }
        false
    }
}

impl fmt::Display for CompileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let error_desc = match &self.error {
            CompileErrorType::Assign(target) => format!("can't assign to {}", target),
            CompileErrorType::Delete(target) => format!("can't delete {}", target),
            CompileErrorType::ExpectExpr => "Expecting expression, got statement".to_owned(),
            CompileErrorType::Parse(err) => err.to_string(),
            CompileErrorType::SyntaxError(err) => err.to_string(),
            CompileErrorType::StarArgs => "Two starred expressions in assignment".to_owned(),
            CompileErrorType::InvalidBreak => "'break' outside loop".to_owned(),
            CompileErrorType::InvalidContinue => "'continue' outside loop".to_owned(),
            CompileErrorType::InvalidReturn => "'return' outside function".to_owned(),
            CompileErrorType::InvalidYield => "'yield' outside function".to_owned(),
        };

        if let Some(statement) = &self.statement {
            if self.location.column() > 0 {
                if let Some(line) = statement.lines().nth(self.location.row() - 1) {
                    // visualize the error, when location and statement are provided
                    return write!(f, "\n{}\n{}", line, self.location.visualize(&error_desc));
                }
            }
        }

        // print line number
        write!(f, "{} at {}", error_desc, self.location)
    }
}

impl Error for CompileError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        None
    }
}