1
//! Parsing helpers for binding forms.
2
//!
3
//! - `parse_bindings` — the `((name init) (name init) ...)` list LET
4
//!   / LET* / DO take.
5
//! - `parse_param_list` — a simple required-param list for places
6
//!   that don't accept `&optional` / `&rest` / `&key` / `&aux`.
7
//! - `parse_lambda_params` — the full lambda-list parser plus its
8
//!   `ParseState` state machine.
9

            
10
use crate::ast::{Expr, LambdaParams};
11
use crate::error::{Error, Result};
12

            
13
39176
pub(in crate::compiler) fn parse_bindings(
14
39176
    context: &str,
15
39176
    expr: &Expr,
16
39176
) -> Result<Vec<(String, Option<Expr>)>> {
17
39176
    let list = expr.as_list().ok_or_else(|| {
18
        Error::Compile(format!("{context}: expected bindings list, got {expr:?}"))
19
    })?;
20
39176
    list.iter()
21
44737
        .map(|binding| match binding {
22
            Expr::Symbol(name) => Ok((name.clone(), None)),
23
44820
            Expr::List(elems) if elems.len() == 1 => {
24
83
                let name = elems[0].as_symbol().ok_or_else(|| {
25
                    Error::Compile(format!(
26
                        "{context}: binding name must be a symbol, got {:?}",
27
                        elems[0]
28
                    ))
29
                })?;
30
83
                Ok((name.to_string(), None))
31
            }
32
44737
            Expr::List(elems) if elems.len() == 2 => {
33
44737
                let name = elems[0].as_symbol().ok_or_else(|| {
34
83
                    Error::Compile(format!(
35
83
                        "{context}: binding name must be a symbol, got {:?}",
36
83
                        elems[0]
37
83
                    ))
38
83
                })?;
39
44654
                Ok((name.to_string(), Some(elems[1].clone())))
40
            }
41
            _ => Err(Error::Compile(format!(
42
                "{context}: malformed binding: {binding:?}"
43
            ))),
44
44820
        })
45
39176
        .collect()
46
39176
}
47

            
48
1245
pub(in crate::compiler) fn parse_param_list(context: &str, expr: &Expr) -> Result<Vec<String>> {
49
1245
    let params = parse_lambda_params(context, expr)?;
50
1245
    if !params.optional.is_empty()
51
1245
        || params.rest.is_some()
52
1245
        || !params.key.is_empty()
53
1245
        || !params.aux.is_empty()
54
    {
55
        return Err(Error::Compile(format!(
56
            "{context}: lambda list keywords not supported in this context"
57
        )));
58
1245
    }
59
1245
    Ok(params.required)
60
1245
}
61

            
62
582582
pub(in crate::compiler) fn parse_lambda_params(context: &str, expr: &Expr) -> Result<LambdaParams> {
63
582582
    match expr.as_list() {
64
582582
        Some(ps) => {
65
582582
            let mut params = LambdaParams::simple(Vec::new());
66
582582
            let mut state = ParseState::Required;
67

            
68
594371
            for param in ps {
69
594371
                if let Some(sym) = param.as_symbol() {
70
593624
                    if sym.eq_ignore_ascii_case("&optional") {
71
415
                        if matches!(
72
415
                            state,
73
                            ParseState::Optional
74
                                | ParseState::Rest
75
                                | ParseState::Key
76
                                | ParseState::Aux
77
                        ) {
78
                            return Err(Error::Compile(format!(
79
                                "{context}: &optional after {}",
80
                                state.name()
81
                            )));
82
415
                        }
83
415
                        state = ParseState::Optional;
84
415
                        continue;
85
593209
                    }
86
593209
                    if sym.eq_ignore_ascii_case("&rest") {
87
581
                        if matches!(state, ParseState::Rest | ParseState::Key | ParseState::Aux) {
88
                            return Err(Error::Compile(format!(
89
                                "{context}: &rest after {}",
90
                                state.name()
91
                            )));
92
581
                        }
93
581
                        state = ParseState::Rest;
94
581
                        continue;
95
592628
                    }
96
592628
                    if sym.eq_ignore_ascii_case("&key") {
97
415
                        if matches!(state, ParseState::Key | ParseState::Aux) {
98
                            return Err(Error::Compile(format!(
99
                                "{context}: &key after {}",
100
                                state.name()
101
                            )));
102
415
                        }
103
415
                        state = ParseState::Key;
104
415
                        continue;
105
592213
                    }
106
592213
                    if sym.eq_ignore_ascii_case("&aux") {
107
249
                        if matches!(state, ParseState::Aux) {
108
                            return Err(Error::Compile(format!("{context}: &aux after aux")));
109
249
                        }
110
249
                        state = ParseState::Aux;
111
249
                        continue;
112
591964
                    }
113
747
                }
114

            
115
592711
                match state {
116
                    ParseState::Required => {
117
591051
                        let name = param
118
591051
                            .as_symbol()
119
591051
                            .ok_or_else(|| {
120
                                Error::Compile(format!(
121
                                    "{context}: required parameter must be a symbol, got {param:?}"
122
                                ))
123
                            })?
124
591051
                            .to_string();
125
591051
                        params.required.push(name);
126
                    }
127
                    ParseState::Optional => {
128
415
                        let (name, default) = parse_optional_param(context, param)?;
129
415
                        params.optional.push((name, default));
130
                    }
131
                    ParseState::Rest => {
132
581
                        let name = param
133
581
                            .as_symbol()
134
581
                            .ok_or_else(|| {
135
                                Error::Compile(format!(
136
                                    "{context}: &rest parameter must be a symbol, got {param:?}"
137
                                ))
138
                            })?
139
581
                            .to_string();
140
581
                        if params.rest.is_some() {
141
                            return Err(Error::Compile(format!(
142
                                "{context}: multiple &rest parameters"
143
                            )));
144
581
                        }
145
581
                        params.rest = Some(name);
146
581
                        state = ParseState::PostRest;
147
                    }
148
                    ParseState::PostRest => {
149
                        return Err(Error::Compile(format!(
150
                            "{context}: parameters after &rest must use &key or &aux"
151
                        )));
152
                    }
153
                    ParseState::Key => {
154
415
                        let (name, default) = parse_optional_param(context, param)?;
155
415
                        params.key.push((name, default));
156
                    }
157
                    ParseState::Aux => {
158
249
                        let (name, default) = parse_optional_param(context, param)?;
159
249
                        params.aux.push((name, default));
160
                    }
161
                }
162
            }
163

            
164
582582
            Ok(params)
165
        }
166
        None => Err(Error::Compile(format!(
167
            "{context}: expected parameter list, got {expr:?}"
168
        ))),
169
    }
170
582582
}
171

            
172
#[derive(Debug, Clone)]
173
enum ParseState {
174
    Required,
175
    Optional,
176
    Rest,
177
    PostRest,
178
    Key,
179
    Aux,
180
}
181

            
182
impl ParseState {
183
    fn name(&self) -> &'static str {
184
        match self {
185
            ParseState::Required => "required",
186
            ParseState::Optional => "&optional",
187
            ParseState::Rest => "&rest",
188
            ParseState::PostRest => "post-&rest",
189
            ParseState::Key => "&key",
190
            ParseState::Aux => "&aux",
191
        }
192
    }
193
}
194

            
195
1079
fn parse_optional_param(context: &str, param: &Expr) -> Result<(String, Option<Expr>)> {
196
747
    match param {
197
332
        Expr::Symbol(name) => Ok((name.clone(), None)),
198
747
        Expr::List(elems) if elems.len() == 2 => {
199
747
            let name = elems[0]
200
747
                .as_symbol()
201
747
                .ok_or_else(|| {
202
                    Error::Compile(format!(
203
                        "{context}: parameter name must be a symbol, got {:?}",
204
                        elems[0]
205
                    ))
206
                })?
207
747
                .to_string();
208
747
            Ok((name, Some(elems[1].clone())))
209
        }
210
        _ => Err(Error::Compile(format!(
211
            "{context}: optional parameter must be a symbol or (symbol default), got {param:?}"
212
        ))),
213
    }
214
1079
}