Skip to main content

abyss_core/parser/
mod.rs

1pub use helpers::{LineMap, abyss_whitespace, attach_line_info, scrub_comments_preserve_layout};
2pub use span::SimpleSpan;
3pub use tokens::{SpannedToken, Token, lexer};
4mod diagnostics;
5mod grammar;
6mod helpers;
7mod span;
8mod tokens;
9
10pub use diagnostics::{ParserDiagnostic, emit_diagnostics};
11
12use std::sync::Arc;
13
14use chumsky::{Parser, input::IterInput};
15use ordered_float::OrderedFloat;
16
17use crate::ast::AST;
18
19use diagnostics::convert_rich_error;
20use grammar::build_parser;
21pub struct ParseOutcome {
22    pub ast: Vec<AST>,
23    pub diagnostics: Vec<ParserDiagnostic>,
24}
25
26pub fn parse(source: &str) -> ParseOutcome {
27    let map = Arc::new(LineMap::new(source));
28
29    let scrubbed_source = scrub_comments_preserve_layout(source);
30
31    let (maybe_tokens, lex_errors) = lexer().parse(scrubbed_source.as_str()).into_output_errors();
32
33    let mut diagnostics: Vec<ParserDiagnostic> = lex_errors
34        .into_iter()
35        .map(|err| convert_rich_error(err, &map, "Incantation unravelled at lexing stage"))
36        .collect();
37
38    let tokens = match maybe_tokens {
39        Some(tokens) => normalize_negative_literals(tokens),
40        None => {
41            return ParseOutcome {
42                ast: Vec::new(),
43                diagnostics,
44            };
45        }
46    };
47
48    let len = source.len();
49    let token_input = IterInput::new(tokens.into_iter(), SimpleSpan::new(len, len));
50
51    let parser = build_parser(map.clone());
52    let (maybe_ast, parse_errors) = parser.parse(token_input).into_output_errors();
53
54    diagnostics.extend(
55        parse_errors
56            .into_iter()
57            .map(|err| convert_rich_error(err, &map, "Spell error: Incantation failed")),
58    );
59
60    let ast = maybe_ast.unwrap_or_default();
61
62    ParseOutcome { ast, diagnostics }
63}
64
65fn normalize_negative_literals(tokens: Vec<SpannedToken>) -> Vec<SpannedToken> {
66    use Token::*;
67
68    fn allows_unary(prev: Option<&Token>) -> bool {
69        match prev {
70            None => true,
71            Some(token) => matches!(
72                token,
73                Assign
74                    | AddAssign
75                    | SubAssign
76                    | MulAssign
77                    | DivAssign
78                    | ModAssign
79                    | PowArcanaAssign
80                    | PowAetherAssign
81                    | Plus
82                    | Minus
83                    | Star
84                    | Slash
85                    | Percent
86                    | Caret
87                    | DoubleStar
88                    | DoublePipe
89                    | DoubleAmpersand
90                    | Equal
91                    | NotEqual
92                    | LessThan
93                    | LessThanOrEqual
94                    | GreaterThan
95                    | GreaterThanOrEqual
96                    | OpenParen
97                    | OpenBrace
98                    | Comma
99                    | Colon
100                    | Semicolon
101                    | Arrow
102                    | FatArrow
103                    | Bang
104            ),
105        }
106    }
107
108    let mut result = Vec::with_capacity(tokens.len() + 4);
109    let mut prev_token: Option<Token> = None;
110
111    for (token, span) in tokens {
112        match token {
113            Arcana(value) if value < 0 && !allows_unary(prev_token.as_ref()) => {
114                let abs_val = -value;
115                let minus_span = SimpleSpan::new(span.start(), span.start() + 1);
116                let literal_span = SimpleSpan::new(span.start() + 1, span.end());
117                result.push((Minus, minus_span));
118                result.push((Arcana(abs_val), literal_span));
119                prev_token = Some(Arcana(abs_val));
120            }
121            Aether(value)
122                if value < OrderedFloat::from(0.0) && !allows_unary(prev_token.as_ref()) =>
123            {
124                let abs_val = OrderedFloat::from(value.into_inner().abs());
125                let minus_span = SimpleSpan::new(span.start(), span.start() + 1);
126                let literal_span = SimpleSpan::new(span.start() + 1, span.end());
127                result.push((Minus, minus_span));
128                result.push((Aether(abs_val), literal_span));
129                prev_token = Some(Aether(abs_val));
130            }
131            other => {
132                prev_token = Some(other.clone());
133                result.push((other, span));
134            }
135        }
136    }
137
138    result
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144    use ordered_float::OrderedFloat;
145
146    fn span(start: usize, end: usize) -> SimpleSpan<usize> {
147        SimpleSpan::new(start, end)
148    }
149
150    #[test]
151    fn splits_negative_arcana_after_value_tokens() {
152        let tokens = vec![
153            (Token::Arcana(1), span(0, 1)),
154            (Token::Arcana(-2), span(1, 3)),
155        ];
156
157        let normalized = normalize_negative_literals(tokens);
158
159        let expected = vec![
160            (Token::Arcana(1), span(0, 1)),
161            (Token::Minus, span(1, 2)),
162            (Token::Arcana(2), span(2, 3)),
163        ];
164
165        assert_eq!(normalized, expected);
166    }
167
168    #[test]
169    fn keeps_unary_negative_literals_intact_after_operators() {
170        let tokens = vec![(Token::Minus, span(0, 1)), (Token::Arcana(-2), span(1, 3))];
171
172        let normalized = normalize_negative_literals(tokens.clone());
173
174        assert_eq!(normalized, tokens);
175    }
176
177    #[test]
178    fn splits_negative_aether_after_closing_delimiters() {
179        let tokens = vec![
180            (Token::CloseParen, span(0, 1)),
181            (Token::Aether(OrderedFloat::from(-1.25)), span(1, 5)),
182        ];
183
184        let normalized = normalize_negative_literals(tokens);
185
186        let expected = vec![
187            (Token::CloseParen, span(0, 1)),
188            (Token::Minus, span(1, 2)),
189            (Token::Aether(OrderedFloat::from(1.25)), span(2, 5)),
190        ];
191
192        assert_eq!(normalized, expected);
193    }
194}