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
use logos::{Logos, SpannedIter};

use self::tokens::Token;

pub mod tokens;
pub mod traits;
pub mod types;

pub struct Lexer<'input> {
  pub token_stream: SpannedIter<'input, Token>,
  pub filepath: &'input str,
  pub source_code: &'input str,
}

#[derive(Debug)]
pub struct ErrorTip {
  pub message: String,
  pub location: std::ops::Range<usize>,
}

#[derive(Debug)]
pub enum LexicalError {
  InvalidToken,
  WrongType {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
  UnknownVariable {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
  UnknownFunction {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
  WrongArgumentCount {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
  FunctionIsBuiltin {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
  UnusedValue {
    error: Vec<ErrorTip>,
    help: Option<String>,
  },
}

impl<'input> Lexer<'input> {
  pub fn new(source_code: &'input str, filepath: &'input str) -> Result<Self, LexicalError> {
    let lexer = Self {
      token_stream: Token::lexer(source_code).spanned(),
      filepath,
      source_code,
    };

    lexer.validate()?;

    Ok(lexer)
  }
}

impl<'input> Lexer<'input> {
  fn validate(&self) -> Result<(), LexicalError> {
    let token_stream = self.token_stream.clone();
    let mut error = false;

    let filename = self.filepath.split('/').last().unwrap();

    for (token, span) in token_stream.spanned() {
      if token.is_err() {
        use ariadne::{Color, ColorGenerator, Config, Fmt, Label, Report, ReportKind, Source};

        let mut colors = ColorGenerator::default();
        let color = colors.next();

        Report::build(ReportKind::Error, filename, 12)
          .with_code(3)
          .with_config(Config::default().with_tab_width(2))
          .with_message("Invalid token".fg(Color::Red))
          .with_label(
            Label::new((filename, span))
              .with_message("Invalid token")
              .with_color(color),
          )
          .with_help("You probably added a character that is not allowed in the language")
          .with_note(format!(
            "If you think this is a bug, please file an issue at {}",
            "github.com/celestial-hub/compass/issues".fg(Color::Blue)
          ))
          .finish()
          .print((filename, Source::from(self.source_code)))
          .unwrap();

        error = true;
      }
    }

    if error {
      return Err(LexicalError::InvalidToken);
    }

    Ok(())
  }
}