• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TyRoXx / NonlocalityOS / 15096205959

18 May 2025 01:01PM UTC coverage: 72.333% (+0.3%) from 72.054%
15096205959

Pull #254

github

web-flow
Merge fc879e53c into 075058b9d
Pull Request #254: GH-253: Begin lambda captures

169 of 192 new or added lines in 7 files covered. (88.02%)

1 existing line in 1 file now uncovered.

3200 of 4424 relevant lines covered (72.33%)

2243.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.66
/lambda_compiler/src/parsing.rs
1
use crate::{
2
    ast,
3
    compilation::{CompilerError, SourceLocation},
4
    tokenization::{Token, TokenContent},
5
};
6
use lambda::name::{Name, NamespaceId};
7

8
#[derive(Debug)]
9
pub struct ParserError {
10
    pub message: String,
11
    pub location: SourceLocation,
12
}
13

14
impl ParserError {
15
    pub fn new(message: String, location: SourceLocation) -> Self {
3✔
16
        Self { message, location }
17
    }
18
}
19

20
impl std::fmt::Display for ParserError {
21
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3✔
22
        write!(f, "{}", &self.message)
3✔
23
    }
24
}
25

26
pub type ParserResult<T> = std::result::Result<T, ParserError>;
27

28
pub fn pop_next_non_whitespace_token<'t>(
245✔
29
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
30
) -> Option<&'t Token> {
31
    let token = peek_next_non_whitespace_token(tokens);
245✔
32
    if token.is_some() {
490✔
33
        tokens.next();
245✔
34
    }
35

36
    token
245✔
37
}
38

39
pub fn peek_next_non_whitespace_token<'t>(
699✔
40
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
41
) -> Option<&'t Token> {
42
    loop {
×
43
        let next = tokens.peek();
817✔
44
        match next {
817✔
45
            Some(token) => match token.content {
817✔
46
                TokenContent::Whitespace => {
×
47
                    tokens.next();
118✔
48
                    continue;
118✔
49
                }
50
                TokenContent::Identifier(_)
×
51
                | TokenContent::Assign
×
52
                | TokenContent::LeftParenthesis
×
53
                | TokenContent::RightParenthesis
×
54
                | TokenContent::LeftBracket
×
55
                | TokenContent::RightBracket
×
56
                | TokenContent::LeftBrace
×
57
                | TokenContent::RightBrace
×
58
                | TokenContent::Dot
×
59
                | TokenContent::Quotes(_)
×
60
                | TokenContent::FatArrow
×
61
                | TokenContent::Comma
×
62
                | TokenContent::EndOfFile => return Some(token),
699✔
63
            },
64
            None => return None,
×
65
        }
66
    }
67
}
68

69
fn expect_right_brace(
7✔
70
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
71
) -> ParserResult<()> {
72
    match peek_next_non_whitespace_token(tokens) {
7✔
73
        Some(non_whitespace) => match &non_whitespace.content {
7✔
74
            TokenContent::Whitespace => todo!(),
75
            TokenContent::Identifier(_) => todo!(),
76
            TokenContent::Assign => todo!(),
77
            TokenContent::LeftParenthesis => todo!(),
78
            TokenContent::RightParenthesis => todo!(),
79
            TokenContent::LeftBracket => todo!(),
80
            TokenContent::RightBracket => todo!(),
81
            TokenContent::LeftBrace => todo!(),
82
            TokenContent::RightBrace => {
83
                pop_next_non_whitespace_token(tokens);
7✔
84
                Ok(())
7✔
85
            }
86
            TokenContent::Dot => todo!(),
87
            TokenContent::Quotes(_) => todo!(),
88
            TokenContent::FatArrow => todo!(),
89
            TokenContent::Comma => todo!(),
90
            TokenContent::EndOfFile => todo!(),
91
        },
92
        None => todo!(),
93
    }
94
}
95

96
fn try_skip_right_parenthesis(
39✔
97
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
98
) -> bool {
99
    match peek_next_non_whitespace_token(tokens) {
39✔
100
        Some(non_whitespace) => match &non_whitespace.content {
39✔
101
            TokenContent::Whitespace => todo!(),
102
            TokenContent::Identifier(_) => false,
5✔
103
            TokenContent::Assign => todo!(),
104
            TokenContent::LeftParenthesis => todo!(),
105
            TokenContent::RightParenthesis => {
106
                pop_next_non_whitespace_token(tokens);
28✔
107
                true
28✔
108
            }
109
            TokenContent::LeftBracket => false,
2✔
110
            TokenContent::RightBracket => todo!(),
NEW
111
            TokenContent::LeftBrace => false,
×
112
            TokenContent::RightBrace => todo!(),
113
            TokenContent::Dot => todo!(),
114
            TokenContent::Quotes(_) => false,
2✔
115
            TokenContent::FatArrow => todo!(),
116
            TokenContent::Comma => false,
2✔
117
            TokenContent::EndOfFile => todo!(),
118
        },
119
        None => todo!(),
120
    }
121
}
122

123
fn expect_fat_arrow(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) {
24✔
124
    match pop_next_non_whitespace_token(tokens) {
24✔
125
        Some(non_whitespace) => match &non_whitespace.content {
24✔
126
            TokenContent::Whitespace => todo!(),
127
            TokenContent::Identifier(_identifier) => todo!(),
128
            TokenContent::Assign => todo!(),
129
            TokenContent::LeftParenthesis => todo!(),
130
            TokenContent::RightParenthesis => todo!(),
131
            TokenContent::LeftBracket => todo!(),
132
            TokenContent::RightBracket => todo!(),
133
            TokenContent::LeftBrace => todo!(),
134
            TokenContent::RightBrace => todo!(),
135
            TokenContent::Dot => todo!(),
136
            TokenContent::Quotes(_) => todo!(),
137
            TokenContent::FatArrow => {}
24✔
138
            TokenContent::Comma => todo!(),
139
            TokenContent::EndOfFile => todo!(),
140
        },
141
        None => todo!(),
142
    }
143
}
144

145
fn expect_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) {
22✔
146
    match pop_next_non_whitespace_token(tokens) {
22✔
147
        Some(non_whitespace) => match &non_whitespace.content {
22✔
148
            TokenContent::Whitespace => todo!(),
149
            TokenContent::Identifier(_) => todo!(),
150
            TokenContent::Assign => todo!(),
151
            TokenContent::LeftParenthesis => todo!(),
152
            TokenContent::RightParenthesis => todo!(),
153
            TokenContent::LeftBracket => todo!(),
154
            TokenContent::RightBracket => todo!(),
155
            TokenContent::LeftBrace => todo!(),
156
            TokenContent::RightBrace => todo!(),
157
            TokenContent::Dot => todo!(),
158
            TokenContent::Quotes(_) => todo!(),
159
            TokenContent::FatArrow => todo!(),
160
            TokenContent::Comma => {}
22✔
161
            TokenContent::EndOfFile => todo!(),
162
        },
163
        None => todo!(),
164
    }
165
}
166

167
fn skip_right_bracket(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
110✔
168
    let maybe_right_bracket = peek_next_non_whitespace_token(tokens);
110✔
169
    match maybe_right_bracket {
110✔
170
        Some(token) => match &token.content {
110✔
171
            TokenContent::Whitespace => unreachable!(),
172
            TokenContent::Identifier(_) => false,
43✔
173
            TokenContent::Assign => false,
×
174
            TokenContent::LeftParenthesis => false,
×
175
            TokenContent::RightParenthesis => false,
×
176
            TokenContent::LeftBracket => false,
2✔
177
            TokenContent::RightBracket => {
178
                tokens.next();
31✔
179
                true
31✔
180
            }
181
            TokenContent::LeftBrace => todo!(),
182
            TokenContent::RightBrace => todo!(),
183
            TokenContent::Dot => false,
×
184
            TokenContent::Quotes(_) => false,
12✔
185
            TokenContent::FatArrow => false,
×
186
            TokenContent::Comma => false,
22✔
187
            TokenContent::EndOfFile => todo!(),
188
        },
189
        None => false,
×
190
    }
191
}
192

193
fn parse_tree_construction(
31✔
194
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
195
    local_namespace: &NamespaceId,
196
) -> ParserResult<ast::Expression> {
197
    let mut elements = Vec::new();
31✔
198
    loop {
199
        if skip_right_bracket(tokens) {
65✔
200
            break;
20✔
201
        }
202
        if !elements.is_empty() {
67✔
203
            expect_comma(tokens);
22✔
204
        }
205
        if skip_right_bracket(tokens) {
206
            break;
11✔
207
        }
208
        let element = parse_expression(tokens, local_namespace)?;
34✔
209
        elements.push(element);
210
    }
211
    Ok(ast::Expression::ConstructTree(elements))
31✔
212
}
213

214
fn parse_braces(
7✔
215
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
216
    local_namespace: &NamespaceId,
217
) -> ParserResult<ast::Expression> {
218
    let content = parse_expression(tokens, local_namespace)?;
14✔
219
    expect_right_brace(tokens)?;
×
220
    Ok(ast::Expression::Braces(Box::new(content)))
7✔
221
}
222

223
fn parse_expression_start<'t>(
122✔
224
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
225
    local_namespace: &NamespaceId,
226
) -> ParserResult<ast::Expression> {
227
    match peek_next_non_whitespace_token(tokens) {
122✔
228
        Some(non_whitespace) => match &non_whitespace.content {
122✔
229
            TokenContent::Whitespace => todo!(),
230
            TokenContent::Identifier(identifier) => {
49✔
231
                pop_next_non_whitespace_token(tokens);
49✔
232
                Ok(ast::Expression::Identifier(
49✔
233
                    Name::new(*local_namespace, identifier.clone()),
49✔
234
                    non_whitespace.location,
49✔
235
                ))
236
            }
237
            TokenContent::Assign => todo!(),
238
            TokenContent::LeftParenthesis => {
×
239
                pop_next_non_whitespace_token(tokens);
25✔
240
                parse_lambda(tokens, local_namespace)
25✔
241
            }
242
            TokenContent::RightParenthesis => Err(ParserError::new(
×
243
                "Expected expression, found right parenthesis.".to_string(),
×
244
                non_whitespace.location,
×
245
            )),
246
            TokenContent::LeftBracket => {
×
247
                pop_next_non_whitespace_token(tokens);
31✔
248
                parse_tree_construction(tokens, local_namespace)
31✔
249
            }
250
            TokenContent::RightBracket => Err(ParserError::new(
×
251
                "Expected expression, found right bracket.".to_string(),
×
252
                non_whitespace.location,
×
253
            )),
254
            TokenContent::LeftBrace => {
×
255
                pop_next_non_whitespace_token(tokens);
7✔
256
                parse_braces(tokens, local_namespace)
7✔
257
            }
258
            TokenContent::RightBrace => todo!(),
259
            TokenContent::Dot => todo!(),
260
            TokenContent::Quotes(content) => {
8✔
261
                pop_next_non_whitespace_token(tokens);
8✔
262
                Ok(ast::Expression::StringLiteral(content.clone()))
8✔
263
            }
264
            TokenContent::FatArrow => todo!(),
265
            TokenContent::Comma => Err(ParserError::new(
1✔
266
                "Expected expression, found comma.".to_string(),
1✔
267
                non_whitespace.location,
1✔
268
            )),
269
            TokenContent::EndOfFile => Err(ParserError::new(
1✔
270
                "Expected expression, got end of file.".to_string(),
1✔
271
                non_whitespace.location,
1✔
272
            )),
273
        },
274
        None => todo!(),
275
    }
276
}
277

278
fn parse_apply(
5✔
279
    callee: ast::Expression,
280
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
281
    local_namespace: &NamespaceId,
282
) -> ParserResult<ast::Expression> {
283
    let mut arguments = Vec::new();
5✔
284
    loop {
285
        if try_skip_right_parenthesis(tokens) {
9✔
286
            break;
4✔
287
        }
288
        if !arguments.is_empty() {
5✔
289
            expect_comma(tokens);
×
290
        }
291
        if try_skip_right_parenthesis(tokens) {
292
            break;
×
293
        }
294
        let argument = parse_expression(tokens, local_namespace)?;
5✔
295
        arguments.push(argument);
296
    }
297
    Ok(ast::Expression::Apply {
4✔
298
        callee: Box::new(callee),
4✔
299
        arguments,
4✔
300
    })
301
}
302

303
pub fn parse_expression<'t>(
122✔
304
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
305
    local_namespace: &NamespaceId,
306
) -> ParserResult<ast::Expression> {
307
    let start = parse_expression_start(tokens, local_namespace)?;
244✔
308
    match peek_next_non_whitespace_token(tokens) {
×
309
        Some(more) => match &more.content {
118✔
310
            TokenContent::Whitespace => unreachable!(),
311
            TokenContent::Identifier(_) => Ok(start),
×
312
            TokenContent::Assign => Ok(start),
×
313
            TokenContent::LeftParenthesis => {
×
314
                tokens.next();
5✔
315
                parse_apply(start, tokens, local_namespace)
5✔
316
            }
317
            TokenContent::RightParenthesis => Ok(start),
6✔
318
            TokenContent::LeftBracket => todo!(),
319
            TokenContent::RightBracket => Ok(start),
12✔
320
            TokenContent::LeftBrace => todo!(),
321
            TokenContent::RightBrace => Ok(start),
8✔
322
            TokenContent::Dot => todo!(),
323
            TokenContent::Quotes(_) => todo!(),
324
            TokenContent::FatArrow => todo!(),
325
            TokenContent::Comma => Ok(start),
22✔
326
            TokenContent::EndOfFile => Ok(start),
65✔
327
        },
328
        None => todo!(),
329
    }
330
}
331

332
fn try_pop_identifier(
34✔
333
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
334
) -> Option<String> {
335
    match peek_next_non_whitespace_token(tokens) {
34✔
336
        Some(non_whitespace) => match &non_whitespace.content {
34✔
337
            TokenContent::Whitespace => todo!(),
338
            TokenContent::Identifier(identifier) => {
23✔
339
                pop_next_non_whitespace_token(tokens);
23✔
340
                Some(identifier.clone())
23✔
341
            }
342
            TokenContent::Assign => todo!(),
343
            TokenContent::LeftParenthesis => todo!(),
344
            TokenContent::RightParenthesis => None,
11✔
345
            TokenContent::LeftBracket => todo!(),
346
            TokenContent::RightBracket => todo!(),
347
            TokenContent::LeftBrace => todo!(),
348
            TokenContent::RightBrace => todo!(),
349
            TokenContent::Dot => todo!(),
350
            TokenContent::Quotes(_) => todo!(),
351
            TokenContent::FatArrow => todo!(),
352
            TokenContent::Comma => None,
×
353
            TokenContent::EndOfFile => None,
×
354
        },
355
        None => None,
×
356
    }
357
}
358

359
fn try_skip_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
23✔
360
    match peek_next_non_whitespace_token(tokens) {
23✔
361
        Some(non_whitespace) => match &non_whitespace.content {
23✔
362
            TokenContent::Whitespace => todo!(),
363
            TokenContent::Identifier(_identifier) => false,
1✔
364
            TokenContent::Assign => todo!(),
365
            TokenContent::LeftParenthesis => todo!(),
366
            TokenContent::RightParenthesis => false,
13✔
367
            TokenContent::LeftBracket => todo!(),
368
            TokenContent::RightBracket => todo!(),
369
            TokenContent::LeftBrace => todo!(),
370
            TokenContent::RightBrace => todo!(),
371
            TokenContent::Dot => todo!(),
372
            TokenContent::Quotes(_) => todo!(),
373
            TokenContent::FatArrow => todo!(),
374
            TokenContent::Comma => {
375
                pop_next_non_whitespace_token(tokens);
9✔
376
                true
9✔
377
            }
378
            TokenContent::EndOfFile => false,
×
379
        },
380
        None => false,
×
381
    }
382
}
383

384
fn parse_lambda<'t>(
25✔
385
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
386
    local_namespace: &NamespaceId,
387
) -> ParserResult<ast::Expression> {
388
    let mut parameter_names = Vec::new();
25✔
389
    while let Some(name) = try_pop_identifier(tokens) {
57✔
390
        let parameter_name: Name = Name::new(*local_namespace, name);
×
391
        parameter_names.push(parameter_name);
×
392
        if !try_skip_comma(tokens) {
×
393
            break;
14✔
394
        }
395
    }
396
    if !try_skip_right_parenthesis(tokens) {
25✔
397
        let next_token = peek_next_non_whitespace_token(tokens).unwrap();
1✔
398
        return Err(ParserError::new(
1✔
399
            "Expected comma or right parenthesis in lambda parameter list.".to_string(),
1✔
400
            next_token.location,
1✔
401
        ));
402
    }
403
    expect_fat_arrow(tokens);
24✔
404
    let body = parse_expression(tokens, local_namespace)?;
48✔
405
    Ok(ast::Expression::Lambda {
×
406
        parameter_names,
×
407
        body: Box::new(body),
×
408
    })
409
}
410

411
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
412
pub struct ParserOutput {
413
    pub entry_point: Option<ast::Expression>,
414
    pub errors: Vec<CompilerError>,
415
}
416

417
impl ParserOutput {
418
    pub fn new(entry_point: Option<ast::Expression>, errors: Vec<CompilerError>) -> ParserOutput {
16✔
419
        ParserOutput {
420
            entry_point,
421
            errors,
422
        }
423
    }
424
}
425

426
pub fn parse_expression_tolerantly<'t>(
14✔
427
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
428
    local_namespace: &NamespaceId,
429
) -> ParserOutput {
430
    let mut errors = Vec::new();
14✔
431
    let entry_point_result = parse_expression(tokens, local_namespace);
14✔
432
    match entry_point_result {
14✔
433
        Ok(entry_point) => ParserOutput::new(Some(entry_point), errors),
11✔
434
        Err(error) => {
3✔
435
            errors.push(CompilerError::new(
3✔
436
                format!("Parser error: {}", &error),
3✔
437
                error.location,
3✔
438
            ));
439
            ParserOutput::new(None, errors)
3✔
440
        }
441
    }
442
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc