Lines
60.76 %
Functions
18.18 %
Branches
100 %
//! Parsing helpers for binding forms.
//!
//! - `parse_bindings` — the `((name init) (name init) ...)` list LET
//! / LET* / DO take.
//! - `parse_param_list` — a simple required-param list for places
//! that don't accept `&optional` / `&rest` / `&key` / `&aux`.
//! - `parse_lambda_params` — the full lambda-list parser plus its
//! `ParseState` state machine.
use crate::ast::{Expr, LambdaParams};
use crate::error::{Error, Result};
pub(in crate::compiler) fn parse_bindings(
context: &str,
expr: &Expr,
) -> Result<Vec<(String, Option<Expr>)>> {
let list = match expr {
Expr::List(elems) => elems,
_ => {
return Err(Error::Compile(format!(
"{context}: expected bindings list, got {expr:?}"
)));
}
};
list.iter()
.map(|binding| match binding {
Expr::Symbol(name) => Ok((name.clone(), None)),
Expr::List(elems) if elems.len() == 1 => {
let name = elems[0].as_symbol().ok_or_else(|| {
Error::Compile(format!(
"{context}: binding name must be a symbol, got {:?}",
elems[0]
))
})?;
Ok((name.to_string(), None))
Expr::List(elems) if elems.len() == 2 => {
Ok((name.to_string(), Some(elems[1].clone())))
_ => Err(Error::Compile(format!(
"{context}: malformed binding: {binding:?}"
))),
})
.collect()
pub(in crate::compiler) fn parse_param_list(context: &str, expr: &Expr) -> Result<Vec<String>> {
let params = parse_lambda_params(context, expr)?;
if !params.optional.is_empty()
|| params.rest.is_some()
|| !params.key.is_empty()
|| !params.aux.is_empty()
{
"{context}: lambda list keywords not supported in this context"
Ok(params.required)
pub(in crate::compiler) fn parse_lambda_params(context: &str, expr: &Expr) -> Result<LambdaParams> {
match expr {
Expr::List(ps) => {
let mut params = LambdaParams::simple(Vec::new());
let mut state = ParseState::Required;
for param in ps {
if let Some(sym) = param.as_symbol() {
if sym.eq_ignore_ascii_case("&optional") {
if matches!(
state,
ParseState::Optional
| ParseState::Rest
| ParseState::Key
| ParseState::Aux
) {
"{context}: &optional after {}",
state.name()
state = ParseState::Optional;
continue;
if sym.eq_ignore_ascii_case("&rest") {
if matches!(state, ParseState::Rest | ParseState::Key | ParseState::Aux) {
"{context}: &rest after {}",
state = ParseState::Rest;
if sym.eq_ignore_ascii_case("&key") {
if matches!(state, ParseState::Key | ParseState::Aux) {
"{context}: &key after {}",
state = ParseState::Key;
if sym.eq_ignore_ascii_case("&aux") {
if matches!(state, ParseState::Aux) {
return Err(Error::Compile(format!("{context}: &aux after aux")));
state = ParseState::Aux;
match state {
ParseState::Required => {
let name = param
.as_symbol()
.ok_or_else(|| {
"{context}: required parameter must be a symbol, got {param:?}"
})?
.to_string();
params.required.push(name);
ParseState::Optional => {
let (name, default) = parse_optional_param(context, param)?;
params.optional.push((name, default));
ParseState::Rest => {
"{context}: &rest parameter must be a symbol, got {param:?}"
if params.rest.is_some() {
"{context}: multiple &rest parameters"
params.rest = Some(name);
state = ParseState::PostRest;
ParseState::PostRest => {
"{context}: parameters after &rest must use &key or &aux"
ParseState::Key => {
params.key.push((name, default));
ParseState::Aux => {
params.aux.push((name, default));
Ok(params)
other => Err(Error::Compile(format!(
"{context}: expected parameter list, got {other:?}"
#[derive(Debug, Clone)]
enum ParseState {
Required,
Optional,
Rest,
PostRest,
Key,
Aux,
impl ParseState {
fn name(&self) -> &'static str {
match self {
ParseState::Required => "required",
ParseState::Optional => "&optional",
ParseState::Rest => "&rest",
ParseState::PostRest => "post-&rest",
ParseState::Key => "&key",
ParseState::Aux => "&aux",
fn parse_optional_param(context: &str, param: &Expr) -> Result<(String, Option<Expr>)> {
match param {
let name = elems[0]
"{context}: parameter name must be a symbol, got {:?}",
Ok((name, Some(elems[1].clone())))
"{context}: optional parameter must be a symbol or (symbol default), got {param:?}"