Lines
100 %
Functions
60 %
Branches
//! Reader macros for quote / quasiquote / unquote / unquote-splicing
//! plus the parenthesised list parser (including the dotted-pair tail
//! `(a . b)` form).
use winnow::combinator::cut_err;
use winnow::error::{AddContext, ContextError, ErrMode, ModalResult, StrContext, StrContextValue};
use winnow::prelude::*;
use crate::ast::Expr;
use super::parse_expr;
use super::whitespace::skip_ws_and_comments_no_annotations;
pub(super) fn parse_quote(input: &mut &str) -> ModalResult<Expr> {
let _ = '\''.parse_next(input)?;
let expr = parse_expr.parse_next(input)?;
Ok(Expr::Quote(Box::new(expr)))
}
pub(super) fn parse_quasiquote(input: &mut &str) -> ModalResult<Expr> {
let _ = '`'.parse_next(input)?;
Ok(Expr::Quasiquote(Box::new(expr)))
pub(super) fn parse_unquote(input: &mut &str) -> ModalResult<Expr> {
let _ = ','.parse_next(input)?;
if input.starts_with('@') {
let _ = '@'.parse_next(input)?;
return Ok(Expr::UnquoteSplicing(Box::new(expr)));
Ok(Expr::Unquote(Box::new(expr)))
pub(super) fn parse_list(input: &mut &str) -> ModalResult<Expr> {
let _ = '('.parse_next(input)?;
skip_ws_and_comments_no_annotations(input)?;
let mut items = Vec::new();
let mut dotted_cdr: Option<Expr> = None;
loop {
if input.starts_with(')') {
break;
if input.is_empty() {
return Err(ErrMode::Cut(
ContextError::new()
.add_context(input, &input.checkpoint(), StrContext::Label("list"))
.add_context(
input,
&input.checkpoint(),
StrContext::Expected(StrContextValue::Description("closing paren")),
),
));
if input.starts_with('.') && input.chars().nth(1).is_some_and(char::is_whitespace) {
let _ = '.'.parse_next(input)?;
dotted_cdr = Some(
cut_err(parse_expr)
.context(StrContext::Label("cdr expression"))
.parse_next(input)?,
);
cut_err(')')
.context(StrContext::Label("closing paren"))
.context(StrContext::Expected(StrContextValue::Description(
"only one expression after dot in dotted pair",
)))
.parse_next(input)?;
let expr = parse_expr(input)?;
items.push(expr);
if dotted_cdr.is_none() {
.context(StrContext::Label("list"))
"closing paren",
if let Some(cdr) = dotted_cdr {
let mut result = cdr;
for item in items.into_iter().rev() {
result = Expr::cons(item, result);
Ok(result)
} else {
Ok(Expr::List(items))