1use serde::{Deserialize, Serialize};
2
3use crate::Diagnostic;
4
5type ParseResult<T> = Result<T, Box<Diagnostic>>;
6
7pub const MAX_NESTING_DEPTH: usize = 256;
9
10#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
12pub struct Span {
13 pub start: usize,
14 pub end: usize,
15 pub start_line: usize,
16 pub start_column: usize,
17 pub end_line: usize,
18 pub end_column: usize,
19}
20
21#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
22pub struct Document {
23 pub forms: Vec<Form>,
24}
25
26#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
27pub struct Form {
28 pub span: Span,
29 pub kind: FormKind,
30}
31
32#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
33pub enum FormKind {
34 Atom(Atom),
35 List(Vec<Form>),
36}
37
38#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
39pub enum Atom {
40 Symbol(String),
41 Integer(String),
42 Decimal(String),
43 String(String),
44}
45
46pub fn parse_document(input: &str) -> Result<Document, Vec<Diagnostic>> {
48 let mut parser = Parser::new(input);
49 let mut forms = Vec::new();
50 let mut diagnostics = Vec::new();
51
52 loop {
53 parser.skip_trivia();
54 if parser.is_eof() {
55 break;
56 }
57
58 match parser.parse_form(0) {
59 Ok(form) => forms.push(form),
60 Err(diagnostic) => {
61 diagnostics.push(*diagnostic);
62 parser.recover_top_level();
63 }
64 }
65 }
66
67 if diagnostics.is_empty() {
68 Ok(Document { forms })
69 } else {
70 Err(diagnostics)
71 }
72}
73
74#[derive(Clone, Copy)]
75struct Mark {
76 byte: usize,
77 line: usize,
78 column: usize,
79}
80
81struct Parser<'a> {
82 input: &'a str,
83 byte: usize,
84 line: usize,
85 column: usize,
86}
87
88impl<'a> Parser<'a> {
89 fn new(input: &'a str) -> Self {
90 Self {
91 input,
92 byte: 0,
93 line: 1,
94 column: 1,
95 }
96 }
97
98 fn is_eof(&self) -> bool {
99 self.byte == self.input.len()
100 }
101
102 fn mark(&self) -> Mark {
103 Mark {
104 byte: self.byte,
105 line: self.line,
106 column: self.column,
107 }
108 }
109
110 fn span_from(&self, start: Mark) -> Span {
111 Span {
112 start: start.byte,
113 end: self.byte,
114 start_line: start.line,
115 start_column: start.column,
116 end_line: self.line,
117 end_column: self.column,
118 }
119 }
120
121 fn empty_span(&self) -> Span {
122 self.span_from(self.mark())
123 }
124
125 fn peek(&self) -> Option<char> {
126 self.input[self.byte..].chars().next()
127 }
128
129 fn bump(&mut self) -> Option<char> {
130 let character = self.peek()?;
131 self.byte += character.len_utf8();
132 if character == '\n' {
133 self.line += 1;
134 self.column = 1;
135 } else {
136 self.column += 1;
137 }
138 Some(character)
139 }
140
141 fn skip_trivia(&mut self) {
142 loop {
143 while matches!(self.peek(), Some(' ' | '\t' | '\r' | '\n')) {
144 self.bump();
145 }
146 if self.peek() != Some(';') {
147 return;
148 }
149 while let Some(character) = self.bump() {
150 if character == '\n' {
151 break;
152 }
153 }
154 }
155 }
156
157 fn parse_form(&mut self, depth: usize) -> ParseResult<Form> {
158 self.skip_trivia();
159 let start = self.mark();
160 match self.peek() {
161 Some('(') if depth == MAX_NESTING_DEPTH => {
162 self.bump();
163 let mut diagnostic = Diagnostic::error(
164 "nesting-depth-exceeded",
165 self.span_from(start),
166 format!(
167 "S-expression nesting exceeds the Chappi v1 limit of {MAX_NESTING_DEPTH} lists"
168 ),
169 );
170 diagnostic.hint = Some(format!(
171 "Reduce list nesting to at most {MAX_NESTING_DEPTH} levels."
172 ));
173 Err(diagnostic.into())
174 }
175 Some('(') => self.parse_list(start, depth + 1),
176 Some(')') => {
177 self.bump();
178 Err(Diagnostic::error(
179 "unexpected-close-paren",
180 self.span_from(start),
181 "unexpected `)` at top level",
182 )
183 .into())
184 }
185 Some('"') => self.parse_string(start),
186 Some(_) => self.parse_bare_atom(start),
187 None => Err(Diagnostic::error(
188 "unexpected-eof",
189 self.empty_span(),
190 "expected an S-expression",
191 )
192 .into()),
193 }
194 }
195
196 fn parse_list(&mut self, start: Mark, depth: usize) -> ParseResult<Form> {
197 self.bump();
198 let mut items = Vec::new();
199 loop {
200 self.skip_trivia();
201 match self.peek() {
202 Some(')') => {
203 self.bump();
204 return Ok(Form {
205 span: self.span_from(start),
206 kind: FormKind::List(items),
207 });
208 }
209 Some(_) => items.push(self.parse_form(depth)?),
210 None => {
211 return Err(Diagnostic::error(
212 "unclosed-list",
213 self.span_from(start),
214 "list opened here is missing a closing `)`",
215 )
216 .into());
217 }
218 }
219 }
220 }
221
222 fn parse_string(&mut self, start: Mark) -> ParseResult<Form> {
223 self.bump();
224 let mut value = String::new();
225 loop {
226 let Some(character) = self.peek() else {
227 return Err(Diagnostic::error(
228 "unclosed-string",
229 self.span_from(start),
230 "string literal is missing its closing quote",
231 )
232 .into());
233 };
234 if matches!(character, '\n' | '\r') {
235 return Err(Diagnostic::error(
236 "unclosed-string",
237 self.span_from(start),
238 "string literals cannot contain a raw line break",
239 )
240 .into());
241 }
242 self.bump();
243 match character {
244 '"' => {
245 return Ok(Form {
246 span: self.span_from(start),
247 kind: FormKind::Atom(Atom::String(value)),
248 });
249 }
250 '\\' => {
251 let escape_start = self.mark();
252 let Some(escaped) = self.peek() else {
253 return Err(Diagnostic::error(
254 "invalid-escape",
255 self.span_from(start),
256 "string literal ends after an escape character",
257 )
258 .into());
259 };
260 if matches!(escaped, '\n' | '\r') {
261 return Err(Diagnostic::error(
262 "unclosed-string",
263 self.span_from(start),
264 "string literals cannot contain a raw line break",
265 )
266 .into());
267 }
268 self.bump();
269 let decoded = match escaped {
270 '"' => '"',
271 '\\' => '\\',
272 'n' => '\n',
273 'r' => '\r',
274 't' => '\t',
275 'u' => self.parse_unicode_escape(escape_start)?,
276 _ => {
277 return Err(Diagnostic::error(
278 "invalid-escape",
279 self.span_from(escape_start),
280 "supported string escapes are `\\\"`, `\\\\`, `\\n`, `\\r`, `\\t`, and `\\u{...}`",
281 )
282 .into());
283 }
284 };
285 value.push(decoded);
286 }
287 _ => value.push(character),
288 }
289 }
290 }
291
292 fn parse_bare_atom(&mut self, start: Mark) -> ParseResult<Form> {
293 while matches!(self.peek(), Some(character) if !is_delimiter(character)) {
294 self.bump();
295 }
296 let value = &self.input[start.byte..self.byte];
297 if value.is_empty() {
298 self.bump();
299 return Err(Diagnostic::error(
300 "invalid-character",
301 self.span_from(start),
302 "expected an atom, list, or string",
303 )
304 .into());
305 }
306 let atom = if is_integer(value) {
307 Atom::Integer(value.to_owned())
308 } else if is_decimal(value) {
309 Atom::Decimal(value.to_owned())
310 } else {
311 Atom::Symbol(value.to_owned())
312 };
313 Ok(Form {
314 span: self.span_from(start),
315 kind: FormKind::Atom(atom),
316 })
317 }
318
319 fn parse_unicode_escape(&mut self, escape_start: Mark) -> ParseResult<char> {
320 if self.peek() != Some('{') {
321 return Err(Diagnostic::error(
322 "invalid-unicode-escape",
323 self.span_from(escape_start),
324 "a Unicode escape must start with `\\u{`",
325 )
326 .into());
327 }
328 self.bump();
329 let digits_start = self.byte;
330 while matches!(self.peek(), Some(character) if character.is_ascii_hexdigit()) {
331 self.bump();
332 }
333 let digits = &self.input[digits_start..self.byte];
334 if digits.is_empty() || digits.len() > 6 || self.peek() != Some('}') {
335 return Err(Diagnostic::error(
336 "invalid-unicode-escape",
337 self.span_from(escape_start),
338 "Unicode escapes must contain one to six hexadecimal digits and end in `}`",
339 )
340 .into());
341 }
342 self.bump();
343 let value = u32::from_str_radix(digits, 16).expect("ASCII hexadecimal digits parse");
344 char::from_u32(value).ok_or_else(|| {
345 Diagnostic::error(
346 "invalid-unicode-escape",
347 self.span_from(escape_start),
348 "Unicode escape is not a valid scalar value",
349 )
350 .into()
351 })
352 }
353
354 fn recover_top_level(&mut self) {
355 while let Some(character) = self.bump() {
356 if character == '\n' {
357 break;
358 }
359 }
360 }
361}
362
363fn is_delimiter(character: char) -> bool {
364 matches!(character, ' ' | '\t' | '\n' | '\r' | '(' | ')' | '"' | ';')
365}
366
367fn is_integer(value: &str) -> bool {
368 let digits = value.strip_prefix('-').unwrap_or(value);
369 !digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit())
370}
371
372fn is_decimal(value: &str) -> bool {
373 let unsigned = value.strip_prefix('-').unwrap_or(value);
374 let Some((whole, fraction)) = unsigned.split_once('.') else {
375 return false;
376 };
377 !whole.is_empty()
378 && !fraction.is_empty()
379 && !fraction.contains('.')
380 && whole.bytes().all(|byte| byte.is_ascii_digit())
381 && fraction.bytes().all(|byte| byte.is_ascii_digit())
382}