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}