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

TyRoXx / NonlocalityOS / 17581344357

09 Sep 2025 11:36AM UTC coverage: 74.9% (-1.7%) from 76.581%
17581344357

push

github

TyRoXx
GH-311: Format

4 of 21 new or added lines in 1 file covered. (19.05%)

158 existing lines in 3 files now uncovered.

3921 of 5235 relevant lines covered (74.9%)

4105.01 hits per line

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

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

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

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

36
    token
848✔
37
}
38

39
pub fn peek_next_non_whitespace_token<'t>(
2,295✔
40
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
41
) -> Option<&'t Token> {
42
    loop {
×
43
        let next = tokens.peek();
8,970✔
44
        match next {
2,990✔
45
            Some(token) => match token.content {
5,980✔
46
                TokenContent::Whitespace => {
×
47
                    tokens.next();
695✔
48
                    continue;
×
49
                }
50

51
                TokenContent::Identifier(_)
×
52
                | TokenContent::Assign
×
53
                | TokenContent::LeftParenthesis
×
54
                | TokenContent::RightParenthesis
×
55
                | TokenContent::LeftBracket
×
56
                | TokenContent::RightBracket
×
57
                | TokenContent::LeftBrace
×
58
                | TokenContent::RightBrace
×
59
                | TokenContent::Dot
×
60
                | TokenContent::Colon
×
61
                | TokenContent::Quotes(_)
×
62
                | TokenContent::FatArrow
×
63
                | TokenContent::Comma
×
64
                | TokenContent::Comment(_)
×
65
                | TokenContent::Integer(_, _)
×
66
                | TokenContent::EndOfFile => return Some(token),
2,295✔
67
            },
68
            None => return None,
×
69
        }
70
    }
71
}
72

73
fn expect_right_brace(
25✔
74
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
75
) -> ParserResult<()> {
76
    match peek_next_non_whitespace_token(tokens) {
25✔
77
        Some(non_whitespace) => {
25✔
78
            match &non_whitespace.content {
25✔
79
                TokenContent::Comment(_) => {
UNCOV
80
                    return Err(ParserError::new(
×
UNCOV
81
                        "Comments are currently not supported where a right brace is expected."
×
UNCOV
82
                            .to_string(),
×
UNCOV
83
                        non_whitespace.location,
×
84
                    ))
85
                }
86
                TokenContent::Whitespace => unreachable!(),
UNCOV
87
                TokenContent::Identifier(_) => {}
×
UNCOV
88
                TokenContent::Assign => {}
×
UNCOV
89
                TokenContent::LeftParenthesis => {}
×
UNCOV
90
                TokenContent::RightParenthesis => {}
×
UNCOV
91
                TokenContent::LeftBracket => {}
×
UNCOV
92
                TokenContent::RightBracket => {}
×
UNCOV
93
                TokenContent::LeftBrace => {}
×
94
                TokenContent::RightBrace => {
95
                    pop_next_non_whitespace_token(tokens);
46✔
96
                    return Ok(());
23✔
97
                }
UNCOV
98
                TokenContent::Dot => {}
×
UNCOV
99
                TokenContent::Colon => {}
×
UNCOV
100
                TokenContent::Quotes(_) => {}
×
UNCOV
101
                TokenContent::FatArrow => {}
×
UNCOV
102
                TokenContent::Comma => {}
×
UNCOV
103
                TokenContent::Integer(_, _) => {}
×
104
                TokenContent::EndOfFile => {}
2✔
105
            }
106
            Err(ParserError::new(
2✔
107
                "Expected right brace.".to_string(),
2✔
108
                non_whitespace.location,
2✔
109
            ))
110
        }
111
        None => todo!(),
112
    }
113
}
114

115
fn try_skip_left_parenthesis(
14✔
116
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
117
) -> ParserResult<bool> {
118
    match peek_next_non_whitespace_token(tokens) {
14✔
119
        Some(non_whitespace) => match &non_whitespace.content {
28✔
NEW
120
            TokenContent::Comment(_) => Err(ParserError::new(
×
NEW
121
                "Comments are currently not supported where a left parenthesis could appear."
×
NEW
122
                    .to_string(),
×
NEW
123
                non_whitespace.location,
×
124
            )),
125
            TokenContent::Whitespace => unreachable!(),
126
            TokenContent::Identifier(_) => Ok(false),
1✔
NEW
127
            TokenContent::Assign => Ok(false),
×
128
            TokenContent::LeftParenthesis => {
129
                pop_next_non_whitespace_token(tokens);
26✔
130
                Ok(true)
13✔
131
            }
NEW
132
            TokenContent::RightParenthesis => Ok(false),
×
NEW
133
            TokenContent::LeftBracket => Ok(false),
×
NEW
134
            TokenContent::RightBracket => Ok(false),
×
NEW
135
            TokenContent::LeftBrace => Ok(false),
×
NEW
136
            TokenContent::RightBrace => Ok(false),
×
NEW
137
            TokenContent::Dot => Ok(false),
×
NEW
138
            TokenContent::Colon => Ok(false),
×
NEW
139
            TokenContent::Quotes(_) => Ok(false),
×
NEW
140
            TokenContent::FatArrow => Ok(false),
×
NEW
141
            TokenContent::Comma => Ok(false),
×
NEW
142
            TokenContent::Integer(_, _) => Ok(false),
×
NEW
143
            TokenContent::EndOfFile => Ok(false),
×
144
        },
145
        None => todo!(),
146
    }
147
}
148

149
fn try_skip_right_parenthesis(
162✔
150
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
151
) -> bool {
152
    match peek_next_non_whitespace_token(tokens) {
162✔
153
        Some(non_whitespace) => match &non_whitespace.content {
324✔
UNCOV
154
            TokenContent::Comment(_) => false,
×
155
            TokenContent::Whitespace => unreachable!(),
156
            TokenContent::Identifier(_) => false,
29✔
UNCOV
157
            TokenContent::Assign => false,
×
UNCOV
158
            TokenContent::LeftParenthesis => false,
×
159
            TokenContent::RightParenthesis => {
160
                pop_next_non_whitespace_token(tokens);
190✔
161
                true
95✔
162
            }
UNCOV
163
            TokenContent::LeftBracket => false,
×
UNCOV
164
            TokenContent::RightBracket => false,
×
UNCOV
165
            TokenContent::LeftBrace => false,
×
166
            TokenContent::RightBrace => false,
1✔
UNCOV
167
            TokenContent::Dot => false,
×
UNCOV
168
            TokenContent::Colon => false,
×
169
            TokenContent::Quotes(_) => false,
18✔
UNCOV
170
            TokenContent::FatArrow => false,
×
171
            TokenContent::Comma => false,
14✔
172
            TokenContent::Integer(_, _) => false,
4✔
173
            TokenContent::EndOfFile => false,
1✔
174
        },
175
        None => todo!(),
176
    }
177
}
178

179
fn try_skip_assign(
35✔
180
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
181
) -> ParserResult<bool> {
182
    match peek_next_non_whitespace_token(tokens) {
35✔
183
        Some(non_whitespace) => match &non_whitespace.content {
70✔
UNCOV
184
            TokenContent::Comment(_) => Err(ParserError::new(
×
UNCOV
185
                "Comments are currently not supported where an assignment operator could appear."
×
UNCOV
186
                    .to_string(),
×
UNCOV
187
                non_whitespace.location,
×
188
            )),
189
            TokenContent::Whitespace => unreachable!(),
UNCOV
190
            TokenContent::Identifier(_) => Ok(false),
×
191
            TokenContent::Assign => {
192
                pop_next_non_whitespace_token(tokens);
70✔
193
                Ok(true)
35✔
194
            }
UNCOV
195
            TokenContent::LeftParenthesis => Ok(false),
×
UNCOV
196
            TokenContent::RightParenthesis => Ok(false),
×
UNCOV
197
            TokenContent::LeftBracket => Ok(false),
×
UNCOV
198
            TokenContent::RightBracket => Ok(false),
×
UNCOV
199
            TokenContent::LeftBrace => Ok(false),
×
UNCOV
200
            TokenContent::RightBrace => Ok(false),
×
UNCOV
201
            TokenContent::Dot => Ok(false),
×
UNCOV
202
            TokenContent::Colon => Ok(false),
×
UNCOV
203
            TokenContent::Quotes(_) => Ok(false),
×
UNCOV
204
            TokenContent::FatArrow => Ok(false),
×
UNCOV
205
            TokenContent::Comma => Ok(false),
×
UNCOV
206
            TokenContent::Integer(_, _) => Ok(false),
×
UNCOV
207
            TokenContent::EndOfFile => Ok(false),
×
208
        },
209
        None => todo!(),
210
    }
211
}
212

213
fn expect_fat_arrow(
63✔
214
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
215
) -> ParserResult<()> {
216
    match pop_next_non_whitespace_token(tokens) {
63✔
217
        Some(non_whitespace) => {
63✔
218
            match &non_whitespace.content {
63✔
219
                TokenContent::Comment(_) => {
220
                    return Err(ParserError::new(
2✔
221
                        "Comments are currently not supported where a fat arrow could appear."
2✔
222
                            .to_string(),
1✔
223
                        non_whitespace.location,
1✔
224
                    ))
225
                }
226
                TokenContent::Whitespace => unreachable!(),
227
                TokenContent::Identifier(_identifier) => {}
2✔
UNCOV
228
                TokenContent::Assign => {}
×
UNCOV
229
                TokenContent::LeftParenthesis => {}
×
UNCOV
230
                TokenContent::RightParenthesis => {}
×
UNCOV
231
                TokenContent::LeftBracket => {}
×
UNCOV
232
                TokenContent::RightBracket => {}
×
UNCOV
233
                TokenContent::LeftBrace => {}
×
UNCOV
234
                TokenContent::RightBrace => {}
×
UNCOV
235
                TokenContent::Dot => {}
×
UNCOV
236
                TokenContent::Colon => {}
×
UNCOV
237
                TokenContent::Quotes(_) => {}
×
238
                TokenContent::FatArrow => {
239
                    return Ok(());
60✔
240
                }
UNCOV
241
                TokenContent::Comma => {}
×
UNCOV
242
                TokenContent::Integer(_, _) => {}
×
243
                TokenContent::EndOfFile => {}
1✔
244
            }
245
            Err(ParserError::new(
2✔
246
                "Expected fat arrow (=>).".to_string(),
2✔
247
                non_whitespace.location,
2✔
248
            ))
249
        }
250
        None => todo!(),
251
    }
252
}
253

254
fn expect_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> ParserResult<()> {
55✔
255
    match pop_next_non_whitespace_token(tokens) {
55✔
256
        Some(non_whitespace) => {
55✔
257
            match &non_whitespace.content {
55✔
258
                TokenContent::Comment(_) => {
259
                    return Err(ParserError::new(
×
UNCOV
260
                        "Comments are currently not supported where a comma could appear."
×
UNCOV
261
                            .to_string(),
×
UNCOV
262
                        non_whitespace.location,
×
263
                    ))
264
                }
265
                TokenContent::Whitespace => unreachable!(),
266
                TokenContent::Identifier(_) => {}
1✔
UNCOV
267
                TokenContent::Assign => {}
×
UNCOV
268
                TokenContent::LeftParenthesis => {}
×
UNCOV
269
                TokenContent::RightParenthesis => {}
×
UNCOV
270
                TokenContent::LeftBracket => {}
×
UNCOV
271
                TokenContent::RightBracket => {}
×
UNCOV
272
                TokenContent::LeftBrace => {}
×
UNCOV
273
                TokenContent::RightBrace => {}
×
UNCOV
274
                TokenContent::Dot => {}
×
UNCOV
275
                TokenContent::Colon => {}
×
UNCOV
276
                TokenContent::Quotes(_) => {}
×
UNCOV
277
                TokenContent::FatArrow => {}
×
278
                TokenContent::Comma => {
279
                    return Ok(());
53✔
280
                }
UNCOV
281
                TokenContent::Integer(_, _) => {}
×
282
                TokenContent::EndOfFile => {}
1✔
283
            }
284
            Err(ParserError::new(
2✔
285
                "Expected comma.".to_string(),
2✔
286
                non_whitespace.location,
2✔
287
            ))
288
        }
289
        None => todo!(),
290
    }
291
}
292

293
fn skip_right_bracket(
191✔
294
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
295
) -> ParserResult<bool> {
296
    let maybe_right_bracket = peek_next_non_whitespace_token(tokens);
573✔
297
    match maybe_right_bracket {
191✔
298
        Some(token) => match &token.content {
382✔
299
            TokenContent::Comment(_) => Err(ParserError::new(
4✔
300
                "Comments are currently not supported where a right bracket could appear."
4✔
301
                    .to_string(),
2✔
302
                token.location,
2✔
303
            )),
304
            TokenContent::Whitespace => unreachable!(),
305
            TokenContent::Identifier(_) => Ok(false),
87✔
UNCOV
306
            TokenContent::Assign => Ok(false),
×
307
            TokenContent::LeftParenthesis => Ok(false),
×
308
            TokenContent::RightParenthesis => Ok(false),
×
309
            TokenContent::LeftBracket => Ok(false),
2✔
310
            TokenContent::RightBracket => {
311
                tokens.next();
86✔
312
                Ok(true)
43✔
313
            }
314
            TokenContent::LeftBrace => Ok(false),
×
315
            TokenContent::RightBrace => Ok(false),
×
316
            TokenContent::Dot => Ok(false),
×
UNCOV
317
            TokenContent::Colon => Ok(false),
×
318
            TokenContent::Quotes(_) => Ok(false),
13✔
UNCOV
319
            TokenContent::FatArrow => Ok(false),
×
320
            TokenContent::Comma => Ok(false),
41✔
UNCOV
321
            TokenContent::Integer(_, _) => Ok(false),
×
322
            TokenContent::EndOfFile => Ok(false),
3✔
323
        },
UNCOV
324
        None => Ok(false),
×
325
    }
326
}
327

328
fn parse_tree_construction(
47✔
329
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
330
    local_namespace: &NamespaceId,
331
    location: &SourceLocation,
332
) -> ParserResult<ast::Expression> {
333
    let mut elements = Vec::new();
94✔
334
    loop {
335
        if skip_right_bracket(tokens)? {
228✔
336
            break;
32✔
337
        }
338
        if !elements.is_empty() {
339
            expect_comma(tokens)?;
43✔
340
        }
341
        if skip_right_bracket(tokens)? {
78✔
342
            break;
11✔
343
        }
344
        let element = parse_expression(tokens, local_namespace)?;
67✔
345
        elements.push(element);
346
    }
347
    Ok(ast::Expression::ConstructTree(elements, *location))
43✔
348
}
349

350
fn parse_braces(
25✔
351
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
352
    local_namespace: &NamespaceId,
353
) -> ParserResult<ast::Expression> {
354
    let content = parse_expression(tokens, local_namespace)?;
100✔
355
    expect_right_brace(tokens)?;
2✔
356
    Ok(ast::Expression::Braces(Box::new(content)))
23✔
357
}
358

359
fn parse_let(
35✔
360
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
361
    local_namespace: &NamespaceId,
362
    let_location: &SourceLocation,
363
) -> ParserResult<ast::Expression> {
364
    let (name, location) = match try_pop_identifier(tokens)? {
140✔
365
        Some((name, location)) => (name, location),
105✔
366
        None => {
UNCOV
367
            return Err(ParserError::new(
×
UNCOV
368
                "Expected identifier after 'let' keyword.".to_string(),
×
UNCOV
369
                *let_location,
×
370
            ))
371
        }
372
    };
373
    if !try_skip_assign(tokens)? {
70✔
UNCOV
374
        return Err(ParserError::new(
×
UNCOV
375
            "Expected '=' after 'let' identifier.".to_string(),
×
UNCOV
376
            *let_location,
×
377
        ));
378
    }
379
    let value = parse_expression(tokens, local_namespace)?;
140✔
380
    let body = parse_expression(tokens, local_namespace)?;
35✔
381
    Ok(ast::Expression::Let {
382
        name: Name::new(*local_namespace, name),
383
        location,
384
        value: Box::new(value),
385
        body: Box::new(body),
386
    })
387
}
388

389
fn parse_type_of(
14✔
390
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
391
    local_namespace: &NamespaceId,
392
    type_of_location: &SourceLocation,
393
) -> ParserResult<ast::Expression> {
394
    if !try_skip_left_parenthesis(tokens)? {
28✔
395
        return Err(ParserError::new(
1✔
396
            "Expected '(' after 'type_of' keyword.".to_string(),
1✔
397
            *type_of_location,
1✔
398
        ));
399
    }
400
    let expression = parse_expression(tokens, local_namespace)?;
52✔
401
    if !try_skip_right_parenthesis(tokens) {
402
        return Err(ParserError::new(
1✔
403
            "Expected ')' after expression in 'type_of'.".to_string(),
1✔
404
            expression.source_location(),
1✔
405
        ));
406
    }
407
    Ok(ast::Expression::TypeOf(Box::new(expression)))
12✔
408
}
409

410
fn parse_comment(
8✔
411
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
412
    content: &str,
413
    local_namespace: &NamespaceId,
414
    comment_location: &SourceLocation,
415
) -> ParserResult<ast::Expression> {
416
    let expression = parse_expression(tokens, local_namespace)?;
32✔
417
    Ok(ast::Expression::Comment(
418
        content.to_string(),
419
        Box::new(expression),
420
        *comment_location,
421
    ))
422
}
423

424
fn parse_integer(
7✔
425
    value: i64,
426
    base: IntegerBase,
427
    integer_location: &SourceLocation,
428
) -> ParserResult<ast::Expression> {
429
    Ok(ast::Expression::IntegerLiteral(
7✔
430
        value,
14✔
431
        base,
7✔
432
        *integer_location,
7✔
433
    ))
434
}
435

436
fn parse_expression_start<'t>(
402✔
437
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
438
    local_namespace: &NamespaceId,
439
) -> ParserResult<ast::Expression> {
440
    match peek_next_non_whitespace_token(tokens) {
402✔
441
        Some(non_whitespace) => match &non_whitespace.content {
804✔
442
            TokenContent::Whitespace => unreachable!(),
443
            TokenContent::Identifier(identifier) => {
207✔
444
                pop_next_non_whitespace_token(tokens);
414✔
445
                if identifier.as_str() == "let" {
207✔
446
                    parse_let(tokens, local_namespace, &non_whitespace.location)
140✔
447
                } else if identifier.as_str() == "type_of" {
172✔
448
                    parse_type_of(tokens, local_namespace, &non_whitespace.location)
56✔
449
                } else {
450
                    Ok(ast::Expression::Identifier(
158✔
451
                        Name::new(*local_namespace, identifier.clone()),
158✔
452
                        non_whitespace.location,
158✔
453
                    ))
454
                }
455
            }
456
            TokenContent::Assign => Err(ParserError::new(
2✔
457
                "Expected expression, found assignment operator.".to_string(),
2✔
458
                non_whitespace.location,
1✔
459
            )),
UNCOV
460
            TokenContent::LeftParenthesis => {
×
461
                pop_next_non_whitespace_token(tokens);
136✔
462
                parse_lambda(tokens, local_namespace)
204✔
463
            }
464
            TokenContent::RightParenthesis => Err(ParserError::new(
2✔
465
                "Expected expression, found right parenthesis.".to_string(),
2✔
466
                non_whitespace.location,
1✔
467
            )),
UNCOV
468
            TokenContent::LeftBracket => {
×
469
                pop_next_non_whitespace_token(tokens);
94✔
470
                parse_tree_construction(tokens, local_namespace, &non_whitespace.location)
188✔
471
            }
UNCOV
472
            TokenContent::RightBracket => Err(ParserError::new(
×
UNCOV
473
                "Expected expression, found right bracket.".to_string(),
×
UNCOV
474
                non_whitespace.location,
×
475
            )),
UNCOV
476
            TokenContent::LeftBrace => {
×
477
                pop_next_non_whitespace_token(tokens);
50✔
478
                parse_braces(tokens, local_namespace)
75✔
479
            }
UNCOV
480
            TokenContent::RightBrace => Err(ParserError::new(
×
UNCOV
481
                "Expected expression, found right brace.".to_string(),
×
482
                non_whitespace.location,
×
483
            )),
484
            TokenContent::Dot => Err(ParserError::new(
4✔
485
                "Expected expression, found dot.".to_string(),
4✔
486
                non_whitespace.location,
2✔
487
            )),
UNCOV
488
            TokenContent::Colon => Err(ParserError::new(
×
UNCOV
489
                "Expected expression, found colon.".to_string(),
×
UNCOV
490
                non_whitespace.location,
×
491
            )),
492
            TokenContent::Quotes(content) => {
31✔
493
                pop_next_non_whitespace_token(tokens);
62✔
494
                Ok(ast::Expression::StringLiteral(
31✔
495
                    content.clone(),
62✔
496
                    non_whitespace.location,
31✔
497
                ))
498
            }
499
            TokenContent::FatArrow => Err(ParserError::new(
2✔
500
                "Expected expression, found fat arrow.".to_string(),
2✔
501
                non_whitespace.location,
1✔
502
            )),
503
            TokenContent::Comma => Err(ParserError::new(
2✔
504
                "Expected expression, found comma.".to_string(),
2✔
505
                non_whitespace.location,
1✔
506
            )),
507
            TokenContent::EndOfFile => Err(ParserError::new(
6✔
508
                "Expected expression, got end of file.".to_string(),
6✔
509
                non_whitespace.location,
3✔
510
            )),
511
            TokenContent::Comment(content) => {
8✔
512
                pop_next_non_whitespace_token(tokens);
16✔
513
                parse_comment(tokens, content, local_namespace, &non_whitespace.location)
40✔
514
            }
515
            TokenContent::Integer(value, base) => {
14✔
516
                pop_next_non_whitespace_token(tokens);
14✔
517
                parse_integer(*value, *base, &non_whitespace.location)
28✔
518
            }
519
        },
520
        None => todo!(),
521
    }
522
}
523

524
fn parse_apply(
22✔
525
    callee: ast::Expression,
526
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
527
    local_namespace: &NamespaceId,
528
) -> ParserResult<ast::Expression> {
529
    let mut arguments = Vec::new();
44✔
530
    loop {
531
        if try_skip_right_parenthesis(tokens) {
104✔
532
            break;
20✔
533
        }
534
        if !arguments.is_empty() {
535
            expect_comma(tokens)?;
14✔
536
        }
537
        if try_skip_right_parenthesis(tokens) {
31✔
UNCOV
538
            break;
×
539
        }
540
        let argument = parse_expression(tokens, local_namespace)?;
31✔
541
        arguments.push(argument);
542
    }
543
    Ok(ast::Expression::Apply {
20✔
544
        callee: Box::new(callee),
20✔
545
        arguments,
20✔
546
    })
547
}
548

549
pub fn parse_expression<'t>(
402✔
550
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
551
    local_namespace: &NamespaceId,
552
) -> ParserResult<ast::Expression> {
553
    let start = parse_expression_start(tokens, local_namespace)?;
1,608✔
UNCOV
554
    match peek_next_non_whitespace_token(tokens) {
×
555
        Some(more) => match &more.content {
748✔
556
            TokenContent::Whitespace => unreachable!(),
557
            TokenContent::Identifier(_) => Ok(start),
28✔
UNCOV
558
            TokenContent::Assign => Ok(start),
×
UNCOV
559
            TokenContent::LeftParenthesis => {
×
560
                tokens.next();
44✔
561
                parse_apply(start, tokens, local_namespace)
88✔
562
            }
563
            TokenContent::RightParenthesis => Ok(start),
43✔
564
            TokenContent::LeftBracket => Ok(start),
8✔
565
            TokenContent::RightBracket => Ok(start),
22✔
566
            TokenContent::LeftBrace => Ok(start),
1✔
567
            TokenContent::RightBrace => Ok(start),
66✔
568
            TokenContent::Dot => Ok(start),
1✔
UNCOV
569
            TokenContent::Colon => Ok(start),
×
UNCOV
570
            TokenContent::Quotes(_) => Ok(start),
×
UNCOV
571
            TokenContent::FatArrow => Ok(start),
×
572
            TokenContent::Comma => Ok(start),
67✔
573
            TokenContent::EndOfFile => Ok(start),
110✔
UNCOV
574
            TokenContent::Integer(_, _) => Ok(start),
×
575
            TokenContent::Comment(_) => Ok(start),
6✔
576
        },
577
        None => todo!(),
578
    }
579
}
580

581
fn try_pop_identifier(
126✔
582
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
583
) -> ParserResult<Option<(String, SourceLocation)>> {
584
    match peek_next_non_whitespace_token(tokens) {
126✔
585
        Some(non_whitespace) => match &non_whitespace.content {
252✔
586
            TokenContent::Comment(_) => Err(ParserError::new(
2✔
587
                "Comments are currently not supported where an identifier could appear."
2✔
588
                    .to_string(),
1✔
589
                non_whitespace.location,
1✔
590
            )),
591
            TokenContent::Whitespace => unreachable!(),
592
            TokenContent::Identifier(identifier) => {
93✔
593
                pop_next_non_whitespace_token(tokens);
186✔
594
                Ok(Some((identifier.clone(), non_whitespace.location)))
186✔
595
            }
UNCOV
596
            TokenContent::Assign => Ok(None),
×
UNCOV
597
            TokenContent::LeftParenthesis => Ok(None),
×
598
            TokenContent::RightParenthesis => Ok(None),
30✔
UNCOV
599
            TokenContent::LeftBracket => Ok(None),
×
UNCOV
600
            TokenContent::RightBracket => Ok(None),
×
601
            TokenContent::LeftBrace => Ok(None),
×
602
            TokenContent::RightBrace => Ok(None),
1✔
603
            TokenContent::Dot => Ok(None),
×
UNCOV
604
            TokenContent::Colon => Ok(None),
×
UNCOV
605
            TokenContent::Quotes(_) => Ok(None),
×
UNCOV
606
            TokenContent::FatArrow => Ok(None),
×
607
            TokenContent::Comma => Ok(None),
×
608
            TokenContent::Integer(_, _) => Ok(None),
×
609
            TokenContent::EndOfFile => Ok(None),
1✔
610
        },
611
        None => Ok(None),
×
612
    }
613
}
614

615
fn try_skip_comma(
57✔
616
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
617
) -> ParserResult<bool> {
618
    match peek_next_non_whitespace_token(tokens) {
57✔
619
        Some(non_whitespace) => match &non_whitespace.content {
114✔
UNCOV
620
            TokenContent::Comment(_) => Err(ParserError::new(
×
UNCOV
621
                "Comments are currently not supported where a comma could appear.".to_string(),
×
UNCOV
622
                non_whitespace.location,
×
623
            )),
624
            TokenContent::Whitespace => unreachable!(),
625
            TokenContent::Identifier(_identifier) => Ok(false),
2✔
626
            TokenContent::Assign => Ok(false),
×
UNCOV
627
            TokenContent::LeftParenthesis => Ok(false),
×
628
            TokenContent::RightParenthesis => Ok(false),
33✔
UNCOV
629
            TokenContent::LeftBracket => Ok(false),
×
UNCOV
630
            TokenContent::RightBracket => Ok(false),
×
UNCOV
631
            TokenContent::LeftBrace => Ok(false),
×
UNCOV
632
            TokenContent::RightBrace => Ok(false),
×
UNCOV
633
            TokenContent::Dot => Ok(false),
×
UNCOV
634
            TokenContent::Colon => Ok(false),
×
UNCOV
635
            TokenContent::Quotes(_) => Ok(false),
×
UNCOV
636
            TokenContent::FatArrow => Ok(false),
×
637
            TokenContent::Comma => {
638
                pop_next_non_whitespace_token(tokens);
46✔
639
                Ok(true)
23✔
640
            }
UNCOV
641
            TokenContent::Integer(_, _) => Ok(false),
×
UNCOV
642
            TokenContent::EndOfFile => Ok(false),
×
643
        },
UNCOV
644
        None => Ok(false),
×
645
    }
646
}
647

648
fn try_skip_colon(
58✔
649
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
650
) -> ParserResult<bool> {
651
    match peek_next_non_whitespace_token(tokens) {
58✔
652
        Some(non_whitespace) => match &non_whitespace.content {
116✔
UNCOV
653
            TokenContent::Comment(_) => Err(ParserError::new(
×
UNCOV
654
                "Comments are currently not supported where a colon could appear.".to_string(),
×
UNCOV
655
                non_whitespace.location,
×
656
            )),
657
            TokenContent::Whitespace => unreachable!(),
658
            TokenContent::Identifier(_identifier) => Ok(false),
2✔
UNCOV
659
            TokenContent::Assign => Ok(false),
×
UNCOV
660
            TokenContent::LeftParenthesis => Ok(false),
×
661
            TokenContent::RightParenthesis => Ok(false),
19✔
UNCOV
662
            TokenContent::LeftBracket => Ok(false),
×
UNCOV
663
            TokenContent::RightBracket => Ok(false),
×
UNCOV
664
            TokenContent::LeftBrace => Ok(false),
×
UNCOV
665
            TokenContent::RightBrace => Ok(false),
×
UNCOV
666
            TokenContent::Dot => Ok(false),
×
667
            TokenContent::Colon => {
668
                pop_next_non_whitespace_token(tokens);
58✔
669
                Ok(true)
29✔
670
            }
UNCOV
671
            TokenContent::Quotes(_) => Ok(false),
×
UNCOV
672
            TokenContent::FatArrow => Ok(false),
×
673
            TokenContent::Comma => Ok(false),
9✔
UNCOV
674
            TokenContent::Integer(_, _) => Ok(false),
×
UNCOV
675
            TokenContent::EndOfFile => Ok(false),
×
676
        },
UNCOV
677
        None => Ok(false),
×
678
    }
679
}
680

681
fn parse_lambda<'t>(
68✔
682
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
683
    local_namespace: &NamespaceId,
684
) -> ParserResult<ast::Expression> {
685
    let mut parameters = Vec::new();
136✔
686
    while let Some((parameter_name, parameter_location)) = try_pop_identifier(tokens)? {
241✔
UNCOV
687
        let namespaced_name = Name::new(*local_namespace, parameter_name);
×
UNCOV
688
        let mut type_annotation = None;
×
UNCOV
689
        if try_skip_colon(tokens)? {
×
690
            type_annotation = Some(parse_expression(tokens, local_namespace)?);
116✔
691
        }
692
        parameters.push(LambdaParameter::new(
57✔
UNCOV
693
            namespaced_name,
×
UNCOV
694
            parameter_location,
×
UNCOV
695
            type_annotation,
×
696
        ));
UNCOV
697
        if !try_skip_comma(tokens)? {
×
698
            break;
34✔
699
        }
700
    }
701
    if !try_skip_right_parenthesis(tokens) {
66✔
702
        let next_token = peek_next_non_whitespace_token(tokens).unwrap();
3✔
UNCOV
703
        return Err(ParserError::new(
×
UNCOV
704
            "Expected comma or right parenthesis in lambda parameter list.".to_string(),
×
UNCOV
705
            next_token.location,
×
706
        ));
707
    }
708
    expect_fat_arrow(tokens)?;
129✔
709
    let body = parse_expression(tokens, local_namespace)?;
120✔
UNCOV
710
    Ok(ast::Expression::Lambda {
×
UNCOV
711
        parameters,
×
UNCOV
712
        body: Box::new(body),
×
713
    })
714
}
715

716
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
717
pub struct ParserOutput {
718
    pub entry_point: Option<ast::Expression>,
719
    pub errors: Vec<CompilerError>,
720
}
721

722
impl ParserOutput {
723
    pub fn new(entry_point: Option<ast::Expression>, errors: Vec<CompilerError>) -> ParserOutput {
63✔
724
        ParserOutput {
725
            entry_point,
726
            errors,
727
        }
728
    }
729
}
730

731
pub fn parse_expression_tolerantly<'t>(
51✔
732
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
733
    local_namespace: &NamespaceId,
734
) -> ParserOutput {
735
    let mut errors = Vec::new();
102✔
736
    let entry_point_result = parse_expression(tokens, local_namespace);
204✔
737
    match entry_point_result {
51✔
738
        Ok(entry_point) => ParserOutput::new(Some(entry_point), errors),
27✔
739
        Err(error) => {
24✔
740
            errors.push(CompilerError::new(
72✔
741
                format!("Parser error: {}", &error),
72✔
742
                error.location,
24✔
743
            ));
744
            ParserOutput::new(None, errors)
72✔
745
        }
746
    }
747
}
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