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

TyRoXx / NonlocalityOS / 15320443667

29 May 2025 09:10AM UTC coverage: 72.844% (+0.09%) from 72.751%
15320443667

Pull #262

github

web-flow
Merge 640ef9b92 into b29009a9e
Pull Request #262: GH-257: support local variables

54 of 73 new or added lines in 4 files covered. (73.97%)

16 existing lines in 1 file now uncovered.

3345 of 4592 relevant lines covered (72.84%)

2219.36 hits per line

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

74.45
/lambda_compiler/src/parsing.rs
1
use crate::{
2
    ast::{self, LambdaParameter},
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 {
4✔
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 {
4✔
22
        write!(f, "{}", &self.message)
4✔
23
    }
24
}
25

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

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

36
    token
341✔
37
}
38

39
pub fn peek_next_non_whitespace_token<'t>(
965✔
40
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
41
) -> Option<&'t Token> {
42
    loop {
×
43
        let next = tokens.peek();
1,140✔
44
        match next {
1,140✔
45
            Some(token) => match token.content {
1,140✔
46
                TokenContent::Whitespace => {
×
47
                    tokens.next();
175✔
48
                    continue;
175✔
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::Colon
×
60
                | TokenContent::Quotes(_)
×
61
                | TokenContent::FatArrow
×
62
                | TokenContent::Comma
×
63
                | TokenContent::EndOfFile => return Some(token),
965✔
64
            },
65
            None => return None,
×
66
        }
67
    }
68
}
69

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

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

126
fn try_skip_assign(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
3✔
127
    match peek_next_non_whitespace_token(tokens) {
3✔
128
        Some(non_whitespace) => match &non_whitespace.content {
3✔
129
            TokenContent::Whitespace => todo!(),
NEW
130
            TokenContent::Identifier(_) => false,
×
131
            TokenContent::Assign => {
132
                pop_next_non_whitespace_token(tokens);
3✔
133
                true
3✔
134
            }
135
            TokenContent::LeftParenthesis => todo!(),
136
            TokenContent::RightParenthesis => todo!(),
137
            TokenContent::LeftBracket => todo!(),
138
            TokenContent::RightBracket => todo!(),
NEW
139
            TokenContent::LeftBrace => false,
×
140
            TokenContent::RightBrace => todo!(),
141
            TokenContent::Dot => todo!(),
142
            TokenContent::Colon => todo!(),
NEW
143
            TokenContent::Quotes(_) => false,
×
144
            TokenContent::FatArrow => todo!(),
NEW
145
            TokenContent::Comma => false,
×
146
            TokenContent::EndOfFile => todo!(),
147
        },
148
        None => todo!(),
149
    }
150
}
151

152
fn expect_fat_arrow(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) {
34✔
153
    match pop_next_non_whitespace_token(tokens) {
34✔
154
        Some(non_whitespace) => match &non_whitespace.content {
34✔
155
            TokenContent::Whitespace => todo!(),
156
            TokenContent::Identifier(_identifier) => todo!(),
157
            TokenContent::Assign => todo!(),
158
            TokenContent::LeftParenthesis => todo!(),
159
            TokenContent::RightParenthesis => todo!(),
160
            TokenContent::LeftBracket => todo!(),
161
            TokenContent::RightBracket => todo!(),
162
            TokenContent::LeftBrace => todo!(),
163
            TokenContent::RightBrace => todo!(),
164
            TokenContent::Dot => todo!(),
165
            TokenContent::Colon => todo!(),
166
            TokenContent::Quotes(_) => todo!(),
167
            TokenContent::FatArrow => {}
34✔
168
            TokenContent::Comma => todo!(),
169
            TokenContent::EndOfFile => todo!(),
170
        },
171
        None => todo!(),
172
    }
173
}
174

175
fn expect_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) {
26✔
176
    match pop_next_non_whitespace_token(tokens) {
26✔
177
        Some(non_whitespace) => match &non_whitespace.content {
26✔
178
            TokenContent::Whitespace => todo!(),
179
            TokenContent::Identifier(_) => todo!(),
180
            TokenContent::Assign => todo!(),
181
            TokenContent::LeftParenthesis => todo!(),
182
            TokenContent::RightParenthesis => todo!(),
183
            TokenContent::LeftBracket => todo!(),
184
            TokenContent::RightBracket => todo!(),
185
            TokenContent::LeftBrace => todo!(),
186
            TokenContent::RightBrace => todo!(),
187
            TokenContent::Dot => todo!(),
188
            TokenContent::Colon => todo!(),
189
            TokenContent::Quotes(_) => todo!(),
190
            TokenContent::FatArrow => todo!(),
191
            TokenContent::Comma => {}
26✔
192
            TokenContent::EndOfFile => todo!(),
193
        },
194
        None => todo!(),
195
    }
196
}
197

198
fn skip_right_bracket(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
120✔
199
    let maybe_right_bracket = peek_next_non_whitespace_token(tokens);
120✔
200
    match maybe_right_bracket {
120✔
201
        Some(token) => match &token.content {
120✔
202
            TokenContent::Whitespace => unreachable!(),
203
            TokenContent::Identifier(_) => false,
52✔
204
            TokenContent::Assign => false,
×
205
            TokenContent::LeftParenthesis => false,
×
206
            TokenContent::RightParenthesis => false,
×
207
            TokenContent::LeftBracket => false,
2✔
208
            TokenContent::RightBracket => {
209
                tokens.next();
33✔
210
                true
33✔
211
            }
212
            TokenContent::LeftBrace => todo!(),
213
            TokenContent::RightBrace => todo!(),
214
            TokenContent::Dot => false,
×
215
            TokenContent::Colon => todo!(),
216
            TokenContent::Quotes(_) => false,
9✔
217
            TokenContent::FatArrow => false,
×
218
            TokenContent::Comma => false,
24✔
219
            TokenContent::EndOfFile => todo!(),
220
        },
221
        None => false,
×
222
    }
223
}
224

225
fn parse_tree_construction(
33✔
226
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
227
    local_namespace: &NamespaceId,
228
) -> ParserResult<ast::Expression> {
229
    let mut elements = Vec::new();
33✔
230
    loop {
231
        if skip_right_bracket(tokens) {
71✔
232
            break;
22✔
233
        }
234
        if !elements.is_empty() {
73✔
235
            expect_comma(tokens);
24✔
236
        }
237
        if skip_right_bracket(tokens) {
238
            break;
11✔
239
        }
240
        let element = parse_expression(tokens, local_namespace)?;
38✔
241
        elements.push(element);
242
    }
243
    Ok(ast::Expression::ConstructTree(elements))
33✔
244
}
245

246
fn parse_braces(
10✔
247
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
248
    local_namespace: &NamespaceId,
249
) -> ParserResult<ast::Expression> {
250
    let content = parse_expression(tokens, local_namespace)?;
20✔
251
    expect_right_brace(tokens)?;
×
252
    Ok(ast::Expression::Braces(Box::new(content)))
10✔
253
}
254

255
fn parse_let(
3✔
256
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
257
    local_namespace: &NamespaceId,
258
    let_location: &SourceLocation,
259
) -> ParserResult<ast::Expression> {
260
    let (name, location) = match try_pop_identifier(tokens) {
6✔
261
        Some((name, location)) => (name, location),
3✔
262
        None => {
NEW
263
            return Err(ParserError::new(
×
NEW
264
                "Expected identifier after 'let' keyword.".to_string(),
×
NEW
265
                *let_location,
×
266
            ))
267
        }
268
    };
269
    if !try_skip_assign(tokens) {
3✔
NEW
270
        return Err(ParserError::new(
×
NEW
271
            "Expected '=' after 'let' identifier.".to_string(),
×
NEW
272
            *let_location,
×
273
        ));
274
    }
275
    let value = parse_expression(tokens, local_namespace)?;
6✔
276
    let body = parse_expression(tokens, local_namespace)?;
3✔
277
    Ok(ast::Expression::Let {
278
        name: Name::new(*local_namespace, name),
279
        location,
280
        value: Box::new(value),
281
        body: Box::new(body),
282
    })
283
}
284

285
fn parse_expression_start<'t>(
162✔
286
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
287
    local_namespace: &NamespaceId,
288
) -> ParserResult<ast::Expression> {
289
    match peek_next_non_whitespace_token(tokens) {
162✔
290
        Some(non_whitespace) => match &non_whitespace.content {
162✔
291
            TokenContent::Whitespace => todo!(),
292
            TokenContent::Identifier(identifier) => {
67✔
293
                pop_next_non_whitespace_token(tokens);
67✔
294
                if identifier.as_str() == "let" {
67✔
295
                    parse_let(tokens, local_namespace, &non_whitespace.location)
3✔
296
                } else {
297
                    Ok(ast::Expression::Identifier(
64✔
298
                        Name::new(*local_namespace, identifier.clone()),
64✔
299
                        non_whitespace.location,
64✔
300
                    ))
301
                }
302
            }
303
            TokenContent::Assign => todo!(),
304
            TokenContent::LeftParenthesis => {
×
305
                pop_next_non_whitespace_token(tokens);
36✔
306
                parse_lambda(tokens, local_namespace)
36✔
307
            }
308
            TokenContent::RightParenthesis => Err(ParserError::new(
1✔
309
                "Expected expression, found right parenthesis.".to_string(),
1✔
310
                non_whitespace.location,
1✔
311
            )),
312
            TokenContent::LeftBracket => {
×
313
                pop_next_non_whitespace_token(tokens);
33✔
314
                parse_tree_construction(tokens, local_namespace)
33✔
315
            }
316
            TokenContent::RightBracket => Err(ParserError::new(
×
317
                "Expected expression, found right bracket.".to_string(),
×
318
                non_whitespace.location,
×
319
            )),
320
            TokenContent::LeftBrace => {
×
321
                pop_next_non_whitespace_token(tokens);
10✔
322
                parse_braces(tokens, local_namespace)
10✔
323
            }
324
            TokenContent::RightBrace => todo!(),
325
            TokenContent::Dot => todo!(),
326
            TokenContent::Colon => todo!(),
327
            TokenContent::Quotes(content) => {
13✔
328
                pop_next_non_whitespace_token(tokens);
13✔
329
                Ok(ast::Expression::StringLiteral(content.clone()))
13✔
330
            }
331
            TokenContent::FatArrow => todo!(),
332
            TokenContent::Comma => Err(ParserError::new(
1✔
333
                "Expected expression, found comma.".to_string(),
1✔
334
                non_whitespace.location,
1✔
335
            )),
336
            TokenContent::EndOfFile => Err(ParserError::new(
1✔
337
                "Expected expression, got end of file.".to_string(),
1✔
338
                non_whitespace.location,
1✔
339
            )),
340
        },
341
        None => todo!(),
342
    }
343
}
344

345
fn parse_apply(
7✔
346
    callee: ast::Expression,
347
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
348
    local_namespace: &NamespaceId,
349
) -> ParserResult<ast::Expression> {
350
    let mut arguments = Vec::new();
7✔
351
    loop {
352
        if try_skip_right_parenthesis(tokens) {
14✔
353
            break;
6✔
354
        }
355
        if !arguments.is_empty() {
10✔
356
            expect_comma(tokens);
2✔
357
        }
358
        if try_skip_right_parenthesis(tokens) {
UNCOV
359
            break;
×
360
        }
361
        let argument = parse_expression(tokens, local_namespace)?;
8✔
362
        arguments.push(argument);
363
    }
364
    Ok(ast::Expression::Apply {
6✔
365
        callee: Box::new(callee),
6✔
366
        arguments,
6✔
367
    })
368
}
369

370
pub fn parse_expression<'t>(
162✔
371
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
372
    local_namespace: &NamespaceId,
373
) -> ParserResult<ast::Expression> {
374
    let start = parse_expression_start(tokens, local_namespace)?;
324✔
UNCOV
375
    match peek_next_non_whitespace_token(tokens) {
×
376
        Some(more) => match &more.content {
156✔
377
            TokenContent::Whitespace => unreachable!(),
378
            TokenContent::Identifier(_) => Ok(start),
2✔
UNCOV
379
            TokenContent::Assign => Ok(start),
×
UNCOV
380
            TokenContent::LeftParenthesis => {
×
381
                tokens.next();
7✔
382
                parse_apply(start, tokens, local_namespace)
7✔
383
            }
384
            TokenContent::RightParenthesis => Ok(start),
9✔
385
            TokenContent::LeftBracket => Ok(start),
1✔
386
            TokenContent::RightBracket => Ok(start),
14✔
387
            TokenContent::LeftBrace => todo!(),
388
            TokenContent::RightBrace => Ok(start),
16✔
389
            TokenContent::Dot => todo!(),
390
            TokenContent::Colon => todo!(),
391
            TokenContent::Quotes(_) => todo!(),
392
            TokenContent::FatArrow => todo!(),
393
            TokenContent::Comma => Ok(start),
28✔
394
            TokenContent::EndOfFile => Ok(start),
79✔
395
        },
396
        None => todo!(),
397
    }
398
}
399

400
fn try_pop_identifier(
52✔
401
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
402
) -> Option<(String, SourceLocation)> {
403
    match peek_next_non_whitespace_token(tokens) {
52✔
404
        Some(non_whitespace) => match &non_whitespace.content {
52✔
405
            TokenContent::Whitespace => todo!(),
406
            TokenContent::Identifier(identifier) => {
35✔
407
                pop_next_non_whitespace_token(tokens);
35✔
408
                Some((identifier.clone(), non_whitespace.location))
35✔
409
            }
410
            TokenContent::Assign => todo!(),
411
            TokenContent::LeftParenthesis => todo!(),
412
            TokenContent::RightParenthesis => None,
17✔
413
            TokenContent::LeftBracket => todo!(),
414
            TokenContent::RightBracket => todo!(),
415
            TokenContent::LeftBrace => todo!(),
416
            TokenContent::RightBrace => todo!(),
417
            TokenContent::Dot => todo!(),
418
            TokenContent::Colon => todo!(),
419
            TokenContent::Quotes(_) => todo!(),
420
            TokenContent::FatArrow => todo!(),
UNCOV
421
            TokenContent::Comma => None,
×
UNCOV
422
            TokenContent::EndOfFile => None,
×
423
        },
424
        None => None,
×
425
    }
426
}
427

428
fn try_skip_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
31✔
429
    match peek_next_non_whitespace_token(tokens) {
31✔
430
        Some(non_whitespace) => match &non_whitespace.content {
31✔
431
            TokenContent::Whitespace => todo!(),
432
            TokenContent::Identifier(_identifier) => false,
1✔
433
            TokenContent::Assign => todo!(),
434
            TokenContent::LeftParenthesis => todo!(),
435
            TokenContent::RightParenthesis => false,
17✔
436
            TokenContent::LeftBracket => todo!(),
437
            TokenContent::RightBracket => todo!(),
438
            TokenContent::LeftBrace => todo!(),
439
            TokenContent::RightBrace => todo!(),
440
            TokenContent::Dot => todo!(),
441
            TokenContent::Colon => todo!(),
442
            TokenContent::Quotes(_) => todo!(),
443
            TokenContent::FatArrow => todo!(),
444
            TokenContent::Comma => {
445
                pop_next_non_whitespace_token(tokens);
13✔
446
                true
13✔
447
            }
UNCOV
448
            TokenContent::EndOfFile => false,
×
449
        },
UNCOV
450
        None => false,
×
451
    }
452
}
453

454
fn try_skip_colon(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
32✔
455
    match peek_next_non_whitespace_token(tokens) {
32✔
456
        Some(non_whitespace) => match &non_whitespace.content {
32✔
457
            TokenContent::Whitespace => todo!(),
458
            TokenContent::Identifier(_identifier) => false,
1✔
459
            TokenContent::Assign => todo!(),
460
            TokenContent::LeftParenthesis => todo!(),
461
            TokenContent::RightParenthesis => false,
15✔
462
            TokenContent::LeftBracket => todo!(),
463
            TokenContent::RightBracket => todo!(),
464
            TokenContent::LeftBrace => todo!(),
465
            TokenContent::RightBrace => todo!(),
466
            TokenContent::Dot => todo!(),
467
            TokenContent::Colon => {
468
                pop_next_non_whitespace_token(tokens);
5✔
469
                true
5✔
470
            }
471
            TokenContent::Quotes(_) => todo!(),
472
            TokenContent::FatArrow => todo!(),
473
            TokenContent::Comma => false,
11✔
UNCOV
474
            TokenContent::EndOfFile => false,
×
475
        },
UNCOV
476
        None => false,
×
477
    }
478
}
479

480
fn parse_lambda<'t>(
36✔
481
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
482
    local_namespace: &NamespaceId,
483
) -> ParserResult<ast::Expression> {
484
    let mut parameters = Vec::new();
36✔
485
    while let Some((parameter_name, parameter_location)) = try_pop_identifier(tokens) {
81✔
UNCOV
486
        let namespaced_name = Name::new(*local_namespace, parameter_name);
×
UNCOV
487
        let mut type_annotation = None;
×
UNCOV
488
        if try_skip_colon(tokens) {
×
489
            type_annotation = Some(parse_expression(tokens, local_namespace)?);
10✔
490
        }
491
        parameters.push(LambdaParameter::new(
31✔
492
            namespaced_name,
31✔
493
            parameter_location,
31✔
494
            type_annotation,
31✔
495
        ));
496
        if !try_skip_comma(tokens) {
31✔
497
            break;
18✔
498
        }
499
    }
500
    if !try_skip_right_parenthesis(tokens) {
35✔
501
        let next_token = peek_next_non_whitespace_token(tokens).unwrap();
1✔
502
        return Err(ParserError::new(
1✔
503
            "Expected comma or right parenthesis in lambda parameter list.".to_string(),
1✔
504
            next_token.location,
1✔
505
        ));
506
    }
507
    expect_fat_arrow(tokens);
34✔
508
    let body = parse_expression(tokens, local_namespace)?;
68✔
UNCOV
509
    Ok(ast::Expression::Lambda {
×
UNCOV
510
        parameters,
×
UNCOV
511
        body: Box::new(body),
×
512
    })
513
}
514

515
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
516
pub struct ParserOutput {
517
    pub entry_point: Option<ast::Expression>,
518
    pub errors: Vec<CompilerError>,
519
}
520

521
impl ParserOutput {
522
    pub fn new(entry_point: Option<ast::Expression>, errors: Vec<CompilerError>) -> ParserOutput {
22✔
523
        ParserOutput {
524
            entry_point,
525
            errors,
526
        }
527
    }
528
}
529

530
pub fn parse_expression_tolerantly<'t>(
19✔
531
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
532
    local_namespace: &NamespaceId,
533
) -> ParserOutput {
534
    let mut errors = Vec::new();
19✔
535
    let entry_point_result = parse_expression(tokens, local_namespace);
19✔
536
    match entry_point_result {
19✔
537
        Ok(entry_point) => ParserOutput::new(Some(entry_point), errors),
15✔
538
        Err(error) => {
4✔
539
            errors.push(CompilerError::new(
4✔
540
                format!("Parser error: {}", &error),
4✔
541
                error.location,
4✔
542
            ));
543
            ParserOutput::new(None, errors)
4✔
544
        }
545
    }
546
}
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