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
use ariadne::ReportBuilder;
use lalrpop_util::{lalrpop_mod, ParseError};

use crate::{
  ast::{self, context::Context},
  lexer::{Lexer, LexicalError},
};

pub struct Parser;

lalrpop_mod!(pub compass_grammar, "/parser/compass_grammar.rs");

impl Parser {
  pub fn new() -> Self {
    Self
  }

  pub fn parse(&self, lexer: Lexer) -> Result<Vec<ast::Statement>, Box<dyn std::error::Error>> {
    use ariadne::{Color, ColorGenerator, Config, Fmt, Label, Report, ReportKind, Source};

    let mut colors = ColorGenerator::default();

    let filename = lexer.filepath.split('/').last().unwrap();
    let source = lexer.source_code;

    let mut report: ReportBuilder<(&str, std::ops::Range<usize>)> =
      Report::build(ReportKind::Error, filename, 12)
        .with_code(3)
        .with_config(Config::default().with_tab_width(2))
        .with_note(format!(
          "If you think this is a bug, please file an issue at {}",
          "github.com/celestial-hub/compass/issues".fg(Color::Blue)
        ));

    // NOTE: I should probably use this context in the codegen module
    let mut context = Context::new(0);
    match compass_grammar::ProgramParser::new().parse(&mut context, lexer) {
      Ok(ast) => Ok(ast),
      Err(err) => match err {
        ParseError::InvalidToken { location } => {
          report
            .with_message("Invalid token".fg(Color::Red))
            .with_label(
              Label::new((filename, location..location))
                .with_message("Invalid token")
                .with_color(colors.next()),
            )
            .finish()
            .print((filename, Source::from(source)))
            .unwrap();

          Err(Box::new(err))
        }
        ParseError::User { ref error } => {
          let (errors, help) = match error {
            LexicalError::WrongType { error, help } => (error, help),
            LexicalError::UnknownVariable { error, help } => (error, help),
            LexicalError::UnknownFunction { error, help } => (error, help),
            LexicalError::WrongArgumentCount { error, help } => (error, help),
            LexicalError::FunctionIsBuiltin { error, help } => (error, help),
            LexicalError::UnusedValue { error, help } => (error, help),
            LexicalError::InvalidToken => unreachable!(),
          };

          report = report.with_message("Error".fg(Color::Red));

          for error in errors {
            report = report.with_label(
              Label::new((filename, error.location.clone()))
                .with_message(error.message.clone())
                .with_color(colors.next()),
            );
          }

          if let Some(help) = help {
            report = report.with_help(help.clone());
          }

          report
            .finish()
            .print((filename, Source::from(source)))
            .unwrap();

          Err(Box::new(err))
        }
        ParseError::UnrecognizedToken {
          ref token,
          ref expected,
        } => {
          report
            .with_message("Unrecognized token".fg(Color::Red))
            .with_label(
              Label::new((filename, token.0..token.2))
                .with_message("Unrecognized token")
                .with_color(colors.next()),
            )
            .with_help(format!(
              "Expected one of the following: {}",
              expected
                .iter()
                .map(|token| {
                  // Remove surrounding quotes
                  let token = &token[1..token.len() - 1];

                  format!("{}", token.fg(Color::Yellow))
                })
                .collect::<Vec<String>>()
                .join(", ")
            ))
            .finish()
            .print((filename, Source::from(source)))
            .unwrap();
          Err(Box::new(err))
        }
        _ => Err(Box::new(err)),
      },
    }
  }
}

impl Default for Parser {
  fn default() -> Self {
    Self::new()
  }
}