use serde::{Deserialize, Serialize};
use crate::Diagnostic;
type ParseResult<T> = Result<T, Box<Diagnostic>>;
pub const MAX_NESTING_DEPTH: usize = 256;
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Span {
pub start: usize,
pub end: usize,
pub start_line: usize,
pub start_column: usize,
pub end_line: usize,
pub end_column: usize,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Document {
pub forms: Vec<Form>,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Form {
pub span: Span,
pub kind: FormKind,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum FormKind {
Atom(Atom),
List(Vec<Form>),
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum Atom {
Symbol(String),
Integer(String),
Decimal(String),
String(String),
}
pub fn parse_document(input: &str) -> Result<Document, Vec<Diagnostic>> {
let mut parser = Parser::new(input);
let mut forms = Vec::new();
let mut diagnostics = Vec::new();
loop {
parser.skip_trivia();
if parser.is_eof() {
break;
}
match parser.parse_form(0) {
Ok(form) => forms.push(form),
Err(diagnostic) => {
diagnostics.push(*diagnostic);
parser.recover_top_level();
}
}
}
if diagnostics.is_empty() {
Ok(Document { forms })
} else {
Err(diagnostics)
}
}
#[derive(Clone, Copy)]
struct Mark {
byte: usize,
line: usize,
column: usize,
}
struct Parser<'a> {
input: &'a str,
byte: usize,
line: usize,
column: usize,
}
impl<'a> Parser<'a> {
fn new(input: &'a str) -> Self {
Self {
input,
byte: 0,
line: 1,
column: 1,
}
}
fn is_eof(&self) -> bool {
self.byte == self.input.len()
}
fn mark(&self) -> Mark {
Mark {
byte: self.byte,
line: self.line,
column: self.column,
}
}
fn span_from(&self, start: Mark) -> Span {
Span {
start: start.byte,
end: self.byte,
start_line: start.line,
start_column: start.column,
end_line: self.line,
end_column: self.column,
}
}
fn empty_span(&self) -> Span {
self.span_from(self.mark())
}
fn peek(&self) -> Option<char> {
self.input[self.byte..].chars().next()
}
fn bump(&mut self) -> Option<char> {
let character = self.peek()?;
self.byte += character.len_utf8();
if character == '\n' {
self.line += 1;
self.column = 1;
} else {
self.column += 1;
}
Some(character)
}
fn skip_trivia(&mut self) {
loop {
while matches!(self.peek(), Some(' ' | '\t' | '\r' | '\n')) {
self.bump();
}
if self.peek() != Some(';') {
return;
}
while let Some(character) = self.bump() {
if character == '\n' {
break;
}
}
}
}
fn parse_form(&mut self, depth: usize) -> ParseResult<Form> {
self.skip_trivia();
let start = self.mark();
match self.peek() {
Some('(') if depth == MAX_NESTING_DEPTH => {
self.bump();
let mut diagnostic = Diagnostic::error(
"nesting-depth-exceeded",
self.span_from(start),
format!(
"S-expression nesting exceeds the Chappi v1 limit of {MAX_NESTING_DEPTH} lists"
),
);
diagnostic.hint = Some(format!(
"Reduce list nesting to at most {MAX_NESTING_DEPTH} levels."
));
Err(diagnostic.into())
}
Some('(') => self.parse_list(start, depth + 1),
Some(')') => {
self.bump();
Err(Diagnostic::error(
"unexpected-close-paren",
self.span_from(start),
"unexpected `)` at top level",
)
.into())
}
Some('"') => self.parse_string(start),
Some(_) => self.parse_bare_atom(start),
None => Err(Diagnostic::error(
"unexpected-eof",
self.empty_span(),
"expected an S-expression",
)
.into()),
}
}
fn parse_list(&mut self, start: Mark, depth: usize) -> ParseResult<Form> {
self.bump();
let mut items = Vec::new();
loop {
self.skip_trivia();
match self.peek() {
Some(')') => {
self.bump();
return Ok(Form {
span: self.span_from(start),
kind: FormKind::List(items),
});
}
Some(_) => items.push(self.parse_form(depth)?),
None => {
return Err(Diagnostic::error(
"unclosed-list",
self.span_from(start),
"list opened here is missing a closing `)`",
)
.into());
}
}
}
}
fn parse_string(&mut self, start: Mark) -> ParseResult<Form> {
self.bump();
let mut value = String::new();
loop {
let Some(character) = self.peek() else {
return Err(Diagnostic::error(
"unclosed-string",
self.span_from(start),
"string literal is missing its closing quote",
)
.into());
};
if matches!(character, '\n' | '\r') {
return Err(Diagnostic::error(
"unclosed-string",
self.span_from(start),
"string literals cannot contain a raw line break",
)
.into());
}
self.bump();
match character {
'"' => {
return Ok(Form {
span: self.span_from(start),
kind: FormKind::Atom(Atom::String(value)),
});
}
'\\' => {
let escape_start = self.mark();
let Some(escaped) = self.peek() else {
return Err(Diagnostic::error(
"invalid-escape",
self.span_from(start),
"string literal ends after an escape character",
)
.into());
};
if matches!(escaped, '\n' | '\r') {
return Err(Diagnostic::error(
"unclosed-string",
self.span_from(start),
"string literals cannot contain a raw line break",
)
.into());
}
self.bump();
let decoded = match escaped {
'"' => '"',
'\\' => '\\',
'n' => '\n',
'r' => '\r',
't' => '\t',
'u' => self.parse_unicode_escape(escape_start)?,
_ => {
return Err(Diagnostic::error(
"invalid-escape",
self.span_from(escape_start),
"supported string escapes are `\\\"`, `\\\\`, `\\n`, `\\r`, `\\t`, and `\\u{...}`",
)
.into());
}
};
value.push(decoded);
}
_ => value.push(character),
}
}
}
fn parse_bare_atom(&mut self, start: Mark) -> ParseResult<Form> {
while matches!(self.peek(), Some(character) if !is_delimiter(character)) {
self.bump();
}
let value = &self.input[start.byte..self.byte];
if value.is_empty() {
self.bump();
return Err(Diagnostic::error(
"invalid-character",
self.span_from(start),
"expected an atom, list, or string",
)
.into());
}
let atom = if is_integer(value) {
Atom::Integer(value.to_owned())
} else if is_decimal(value) {
Atom::Decimal(value.to_owned())
} else {
Atom::Symbol(value.to_owned())
};
Ok(Form {
span: self.span_from(start),
kind: FormKind::Atom(atom),
})
}
fn parse_unicode_escape(&mut self, escape_start: Mark) -> ParseResult<char> {
if self.peek() != Some('{') {
return Err(Diagnostic::error(
"invalid-unicode-escape",
self.span_from(escape_start),
"a Unicode escape must start with `\\u{`",
)
.into());
}
self.bump();
let digits_start = self.byte;
while matches!(self.peek(), Some(character) if character.is_ascii_hexdigit()) {
self.bump();
}
let digits = &self.input[digits_start..self.byte];
if digits.is_empty() || digits.len() > 6 || self.peek() != Some('}') {
return Err(Diagnostic::error(
"invalid-unicode-escape",
self.span_from(escape_start),
"Unicode escapes must contain one to six hexadecimal digits and end in `}`",
)
.into());
}
self.bump();
let value = u32::from_str_radix(digits, 16).expect("ASCII hexadecimal digits parse");
char::from_u32(value).ok_or_else(|| {
Diagnostic::error(
"invalid-unicode-escape",
self.span_from(escape_start),
"Unicode escape is not a valid scalar value",
)
.into()
})
}
fn recover_top_level(&mut self) {
while let Some(character) = self.bump() {
if character == '\n' {
break;
}
}
}
}
fn is_delimiter(character: char) -> bool {
matches!(character, ' ' | '\t' | '\n' | '\r' | '(' | ')' | '"' | ';')
}
fn is_integer(value: &str) -> bool {
let digits = value.strip_prefix('-').unwrap_or(value);
!digits.is_empty() && digits.bytes().all(|byte| byte.is_ascii_digit())
}
fn is_decimal(value: &str) -> bool {
let unsigned = value.strip_prefix('-').unwrap_or(value);
let Some((whole, fraction)) = unsigned.split_once('.') else {
return false;
};
!whole.is_empty()
&& !fraction.is_empty()
&& !fraction.contains('.')
&& whole.bytes().all(|byte| byte.is_ascii_digit())
&& fraction.bytes().all(|byte| byte.is_ascii_digit())
}