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
// faye, a pretty lil lisp
// Copyright (c) 2023 fawn
//
// SPDX-License-Identifier: Apache-2.0

use lexer::{Lexer, Token, TokenKind};

pub mod eval;
pub mod lexer;
pub mod parser;
pub mod repl;

#[derive(Default)]
pub struct Highlighter {
    match_brackets: bool,
}

impl Highlighter {
    #[must_use]
    pub const fn new(match_brackets: bool) -> Self {
        Self { match_brackets }
    }

    #[must_use]
    pub fn highlight(&self, line: &str) -> String {
        let mut lex = Lexer::new(line);
        let mut colors = Vec::new();

        let mut paren_idx = 0;
        let paren_colors = [
            "\x1b[92m", "\x1b[93m", "\x1b[94m", "\x1b[96m", "\x1b[95m", "\x1b[90m",
        ];

        let mut loc = (0, 0);
        let mut is_fn = false;
        while let Some(res) = lex.next() {
            let color = match &res {
                Ok(Token(kind, ..)) => match kind {
                    TokenKind::Comment(_) => "\x1b[3;90m",
                    TokenKind::OpenParen | TokenKind::CloseParen if !self.match_brackets => {
                        "\x1b[90m"
                    }
                    TokenKind::OpenParen => {
                        if paren_idx > paren_colors.len() - 1 {
                            paren_idx = 0;
                        }
                        let c = paren_colors[paren_idx];
                        paren_idx += 1;
                        c
                    }
                    TokenKind::CloseParen => {
                        if paren_idx < 1 {
                            paren_idx = paren_colors.len();
                        }
                        paren_idx -= 1;
                        paren_colors[paren_idx]
                    }
                    TokenKind::Number(_) | TokenKind::Bool(_) => "\x1b[36m",
                    TokenKind::String(_) => "\x1b[33m",
                    TokenKind::Symbol(_) if is_fn => "\x1b[35m",
                    TokenKind::Symbol(_) => "\x1b[32m",
                    TokenKind::Keyword(_) => "\x1b[34m",
                    TokenKind::Nil => "\x1b[37m",
                },
                Err(_) => "\x1b[31m",
            };
            colors.push((loc, color));
            loc = lex.location();
            is_fn = matches!(res, Ok(Token(TokenKind::OpenParen, ..)));
        }

        let mut lines = line.split('\n').map(String::from).collect::<Vec<_>>();
        for (loc, c) in colors.iter().rev() {
            lines[loc.0].insert_str(loc.1, c);
        }

        lines.join("\n") + "\x1b[0m"
    }
}