use crate::datum::{Datum, DatumKind, Delim, Notation, Prefix};
use crate::error::{ErrorKind, ParseError};
use crate::lexer::Lexer;
use crate::options::Options;
use crate::span::Span;
use crate::token::{Token, TokenKind};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Parsed<'a> {
pub lang_line: Option<&'a str>,
pub data: Vec<Datum<'a>>,
pub errors: Vec<ParseError>,
}
#[must_use]
pub fn parse<'a>(source: &'a str, options: &Options) -> Parsed<'a> {
let mut lang_line: Option<&'a str> = None;
let tokens = significant_tokens(source, options, Some(&mut lang_line));
let mut parser = Parser::new(source, tokens, options);
let data = parser.parse_top_level();
Parsed {
lang_line,
data,
errors: parser.errors,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FormAt<'a> {
pub form: Datum<'a>,
pub errors: Vec<ParseError>,
pub end: u32,
}
#[must_use]
pub fn parse_form_at<'a>(source: &'a str, start: u32, options: &Options) -> Option<FormAt<'a>> {
let tokens = significant_tokens(source, options, None);
let mut parser = Parser::new(source, tokens, options);
parser.pos = parser.tokens.partition_point(|t| t.span.start < start);
let form = loop {
let t = parser.peek()?;
if let TokenKind::Close(found) = t.kind {
parser.advance();
parser.error(t.span, ErrorKind::UnexpectedDelimiter { found });
continue;
}
break parser.parse_datum()?;
};
let end = form.span.end;
Some(FormAt {
form,
errors: parser.errors,
end,
})
}
fn significant_tokens<'a>(
source: &'a str,
options: &Options,
mut capture_lang: Option<&mut Option<&'a str>>,
) -> Vec<Token> {
Lexer::new(source, options)
.filter(|t| {
if t.kind == TokenKind::LangLine {
if let Some(slot) = capture_lang.as_deref_mut() {
if slot.is_none() {
*slot = Some(t.span.text(source).trim_start_matches("#lang").trim());
}
}
}
!matches!(
t.kind,
TokenKind::Whitespace
| TokenKind::LineComment
| TokenKind::BlockComment
| TokenKind::LangLine
)
})
.collect()
}
const MAX_DEPTH: usize = 200;
struct Parser<'a, 'o> {
source: &'a str,
tokens: Vec<Token>,
pos: usize,
line_starts: Vec<u32>,
errors: Vec<ParseError>,
depth: usize,
opts: &'o Options,
}
impl<'a, 'o> Parser<'a, 'o> {
fn new(source: &'a str, tokens: Vec<Token>, options: &'o Options) -> Self {
Parser {
source,
tokens,
pos: 0,
line_starts: line_starts(source),
errors: Vec::new(),
opts: options,
depth: 0,
}
}
fn peek(&self) -> Option<Token> {
self.tokens.get(self.pos).copied()
}
fn advance(&mut self) -> Option<Token> {
let t = self.tokens.get(self.pos).copied();
if t.is_some() {
self.pos += 1;
}
t
}
fn text(&self, span: Span) -> &'a str {
span.text(self.source)
}
fn line_of(&self, offset: u32) -> u32 {
self.line_starts.partition_point(|&s| s <= offset) as u32
}
fn error(&mut self, span: Span, kind: ErrorKind) {
let line = self.line_of(span.start);
self.errors.push(ParseError { span, line, kind });
}
fn parse_top_level(&mut self) -> Vec<Datum<'a>> {
let mut data = Vec::new();
while let Some(t) = self.peek() {
match t.kind {
TokenKind::Close(found) => {
self.advance();
self.error(t.span, ErrorKind::UnexpectedDelimiter { found });
}
_ => {
let before = self.pos;
if let Some(d) = self.parse_datum() {
data.push(d);
} else if self.pos < self.tokens.len() {
if self.pos == before {
self.advance();
}
} else {
break; }
}
}
}
data
}
fn parse_datum(&mut self) -> Option<Datum<'a>> {
loop {
let t = self.peek()?;
match t.kind {
TokenKind::Close(_) => return None,
TokenKind::Prefix(Prefix::Discard) => {
self.advance();
if self.parse_datum().is_none() {
self.error(
t.span,
ErrorKind::DanglingPrefix {
prefix: Prefix::Discard,
},
);
}
continue;
}
TokenKind::Unterminated(_) => {
self.advance();
self.error(
t.span,
ErrorKind::MalformedToken {
text: self.text(t.span).into(),
},
);
continue;
}
_ => break,
}
}
let t = self.advance()?;
let line = self.line_of(t.span.start);
let kind = match t.kind {
TokenKind::Open(delim) => return Some(self.finish_list(delim, t.span, true)),
TokenKind::HashOpen(delim) => return Some(self.finish_hash(delim, t.span)),
TokenKind::Str => DatumKind::Str(self.text(t.span)),
TokenKind::Char => DatumKind::Char(self.text(t.span)),
TokenKind::Bool(b) => DatumKind::Bool(b),
TokenKind::Atom => classify_atom(self.text(t.span), self.opts),
TokenKind::HashTag => {
let tag = &self.text(t.span)[1..]; let inner = match self.parse_datum() {
Some(d) => Some(Box::new(d)),
None => {
self.error(t.span, ErrorKind::DanglingTag);
None
}
};
let end = inner.as_ref().map(|d| d.span.end).unwrap_or(t.span.end);
return Some(Datum {
kind: DatumKind::HashLiteral { tag, inner },
span: Span::new(t.span.start, end),
line,
});
}
TokenKind::Prefix(prefix @ Prefix::FeatureConditional { .. })
if self.opts.feature_conditional =>
{
let feature = self.parse_datum().map(Box::new);
let inner = match self.parse_datum() {
Some(d) => d,
None => {
self.error(t.span, ErrorKind::DanglingPrefix { prefix });
return None;
}
};
let span = Span::new(t.span.start, inner.span.end);
return Some(Datum {
kind: DatumKind::Prefixed {
prefix,
notation: Notation::Shorthand,
inner: Box::new(inner),
arg: feature,
},
span,
line,
});
}
TokenKind::Prefix(Prefix::Meta) => {
let meta = self.parse_datum().map(Box::new);
let inner = match self.parse_datum() {
Some(d) => d,
None => {
self.error(
t.span,
ErrorKind::DanglingPrefix {
prefix: Prefix::Meta,
},
);
return None;
}
};
let span = Span::new(t.span.start, inner.span.end);
return Some(Datum {
kind: DatumKind::Prefixed {
prefix: Prefix::Meta,
notation: Notation::Shorthand,
inner: Box::new(inner),
arg: meta,
},
span,
line,
});
}
TokenKind::Prefix(prefix) => {
let inner = match self.parse_datum() {
Some(d) => d,
None => {
self.error(t.span, ErrorKind::DanglingPrefix { prefix });
return None;
}
};
let span = Span::new(t.span.start, inner.span.end);
return Some(Datum {
kind: DatumKind::Prefixed {
prefix,
notation: Notation::Shorthand,
inner: Box::new(inner),
arg: None,
},
span,
line,
});
}
TokenKind::Label => {
let id = label_id(self.text(t.span));
let inner = match self.parse_datum() {
Some(d) => d,
None => {
self.error(t.span, ErrorKind::DanglingLabel);
return None;
}
};
let span = Span::new(t.span.start, inner.span.end);
return Some(Datum {
kind: DatumKind::Label {
id,
inner: Box::new(inner),
},
span,
line,
});
}
TokenKind::LabelRef => DatumKind::LabelRef {
id: label_id(self.text(t.span)),
},
TokenKind::Whitespace
| TokenKind::LineComment
| TokenKind::BlockComment
| TokenKind::LangLine
| TokenKind::Close(_)
| TokenKind::Unterminated(_) => return None,
};
Some(Datum {
kind,
span: t.span,
line,
})
}
fn finish_list(&mut self, delim: Delim, open: Span, fold: bool) -> Datum<'a> {
let line = self.line_of(open.start);
self.depth += 1;
if self.depth > MAX_DEPTH {
if self.depth == MAX_DEPTH + 1 {
self.error(open, ErrorKind::DepthLimitExceeded);
}
let end = self.skip_balanced(delim);
self.depth -= 1;
return Datum {
kind: DatumKind::List {
delim,
items: Vec::new(),
tail: None,
dot: None,
},
span: Span::new(open.start, end),
line,
};
}
let mut items: Vec<Datum<'a>> = Vec::new();
let mut tail: Option<Box<Datum<'a>>> = None;
let mut dot: Option<Span> = None;
let end;
loop {
let Some(t) = self.peek() else {
self.error(open, ErrorKind::UnclosedList { open: delim });
end = items.last().map(|d| d.span.end).unwrap_or(open.end);
break;
};
match t.kind {
TokenKind::Close(close_delim) => {
self.advance();
if !close_matches(delim, close_delim) {
self.error(
t.span,
ErrorKind::MismatchedDelimiter {
expected: delim,
found: close_delim,
},
);
}
end = t.span.end;
break;
}
TokenKind::Atom
if self.opts.dotted_pairs && !items.is_empty() && self.text(t.span) == "." =>
{
self.advance(); if let Some(prev_tail) = tail.take() {
if !self.opts.dotted_pairs_infix {
self.error(t.span, ErrorKind::ItemAfterDottedTail);
}
items.push(*prev_tail);
}
match self.parse_datum() {
Some(d) => {
tail = Some(Box::new(d));
dot = Some(t.span); }
None => self.error(t.span, ErrorKind::DanglingDot),
}
}
_ => match self.parse_datum() {
Some(d) => {
if let Some(prev_tail) = tail.take() {
if !self.opts.dotted_pairs_infix {
self.error(d.span, ErrorKind::ItemAfterDottedTail);
}
items.push(*prev_tail);
dot = None; }
items.push(d);
}
None => {
if !matches!(self.peek().map(|t| t.kind), Some(TokenKind::Close(_))) {
self.error(open, ErrorKind::UnclosedList { open: delim });
end = items.last().map(|d| d.span.end).unwrap_or(open.end);
break;
}
}
},
}
}
self.depth -= 1;
let datum = Datum {
kind: DatumKind::List {
delim,
items,
tail,
dot,
},
span: Span::new(open.start, end),
line,
};
if fold && self.opts.fold_longhand {
fold_longhand(datum, self.opts)
} else {
datum
}
}
fn skip_balanced(&mut self, delim: Delim) -> u32 {
let mut nesting = 1usize;
let mut end = self.tokens.get(self.pos).map(|t| t.span.start).unwrap_or(0);
while let Some(t) = self.advance() {
end = t.span.end;
match t.kind {
TokenKind::Open(_) | TokenKind::HashOpen(_) => nesting += 1,
TokenKind::Close(close_delim) => {
nesting -= 1;
if nesting == 0 {
if !close_matches(delim, close_delim) {
self.error(
t.span,
ErrorKind::MismatchedDelimiter {
expected: delim,
found: close_delim,
},
);
}
break;
}
}
_ => {}
}
}
end
}
fn finish_hash(&mut self, delim: Delim, open: Span) -> Datum<'a> {
let line = self.line_of(open.start);
let tag = &self.source[open.start as usize + 1..open.end as usize - 1];
let inner_open = Span::new(open.end - 1, open.end); let inner = self.finish_list(delim, inner_open, false);
let span = Span::new(open.start, inner.span.end);
Datum {
kind: DatumKind::HashLiteral {
tag,
inner: Some(Box::new(inner)),
},
span,
line,
}
}
}
fn fold_longhand<'a>(datum: Datum<'a>, opts: &Options) -> Datum<'a> {
match datum.kind {
DatumKind::List {
delim: Delim::Round,
mut items,
tail: None,
dot: _, } if items.len() == 2 => {
if let DatumKind::Symbol(s) = items[0].kind {
if let Some(prefix) = quote_symbol(s, opts) {
let inner = items.pop().unwrap(); return Datum {
kind: DatumKind::Prefixed {
prefix,
notation: Notation::Longhand,
inner: Box::new(inner),
arg: None,
},
span: datum.span,
line: datum.line,
};
}
}
Datum {
kind: DatumKind::List {
delim: Delim::Round,
items,
tail: None,
dot: None,
},
span: datum.span,
line: datum.line,
}
}
other => Datum {
kind: other,
span: datum.span,
line: datum.line,
},
}
}
fn quote_symbol(s: &str, opts: &Options) -> Option<Prefix> {
let eq = |name: &str| {
if opts.fold_case_insensitive {
s.eq_ignore_ascii_case(name)
} else {
s == name
}
};
if opts.roles.quote.is_some() && eq("quote") {
return Some(Prefix::Quote);
}
if opts.roles.quasiquote.is_some() && eq("quasiquote") {
return Some(Prefix::Quasiquote);
}
if opts.roles.unquote.is_some() {
if eq("unquote") {
return Some(Prefix::Unquote);
}
if eq("unquote-splicing") {
return Some(Prefix::UnquoteSplicing);
}
}
None
}
fn label_id(text: &str) -> &str {
&text[1..text.len() - 1]
}
fn close_matches(open: Delim, close: Delim) -> bool {
match open {
Delim::Set => close == Delim::Curly,
other => close == other,
}
}
fn classify_atom<'a>(text: &'a str, opts: &Options) -> DatumKind<'a> {
if opts.hash_curly_symbol && text.starts_with("#{") {
return DatumKind::Symbol(text);
}
if opts.hash_keyword && text.starts_with("#:") {
return DatumKind::Keyword(text);
}
if opts.keyword_colon && text.starts_with(':') {
return DatumKind::Keyword(text);
}
if looks_like_number(text) {
return DatumKind::Number(text);
}
if opts.keyword_trailing_colon && text.len() > 1 && text.ends_with(':') {
return DatumKind::Keyword(text);
}
DatumKind::Symbol(text)
}
fn looks_like_number(s: &str) -> bool {
let b = s.as_bytes();
if b.is_empty() {
return false;
}
if s.starts_with("##") {
return true;
}
if b[0] == b'#' {
if let Some(rest) = s[1..].strip_prefix(|c: char| c.is_ascii_digit()) {
let after_digits = rest.trim_start_matches(|c: char| c.is_ascii_digit());
if let Some(body) = after_digits.strip_prefix('r') {
return !body.is_empty() && body.bytes().all(|c| c.is_ascii_alphanumeric());
}
}
return b.len() >= 2
&& matches!(
b[1].to_ascii_lowercase(),
b'e' | b'i' | b'b' | b'o' | b'd' | b'x'
);
}
let mut i = 0;
if b[0] == b'+' || b[0] == b'-' {
i = 1;
}
if i >= b.len() {
return false; }
let starts_numeric =
b[i].is_ascii_digit() || (b[i] == b'.' && i + 1 < b.len() && b[i + 1].is_ascii_digit());
if !starts_numeric {
return false;
}
let mut prev_exp_marker = false;
for (k, &c) in b[i..].iter().enumerate() {
let is_exp_marker = matches!(c.to_ascii_lowercase(), b'e' | b's' | b'f' | b'd' | b'l');
let ok = c.is_ascii_digit()
|| c == b'.'
|| c == b'/'
|| is_exp_marker
|| ((c == b'+' || c == b'-') && prev_exp_marker)
|| (c.eq_ignore_ascii_case(&b'i') && k > 0 && b[i + k - 1].is_ascii_digit());
if !ok {
return false;
}
prev_exp_marker = is_exp_marker;
}
true
}
fn line_starts(source: &str) -> Vec<u32> {
let mut starts = vec![0u32];
for (i, b) in source.bytes().enumerate() {
if b == b'\n' {
starts.push(i as u32 + 1);
}
}
starts
}