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

TyRoXx / NonlocalityOS / 15236757767

25 May 2025 10:08AM UTC coverage: 72.675% (+0.04%) from 72.638%
15236757767

Pull #258

github

web-flow
Merge 74b975976 into 6ff867199
Pull Request #258: GH-256: extend AST for optional parameter type annotations

24 of 31 new or added lines in 3 files covered. (77.42%)

3 existing lines in 1 file now uncovered.

3266 of 4494 relevant lines covered (72.67%)

2233.17 hits per line

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

74.38
/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 {
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>(
281✔
29
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
30
) -> Option<&'t Token> {
31
    let token = peek_next_non_whitespace_token(tokens);
281✔
32
    if token.is_some() {
562✔
33
        tokens.next();
281✔
34
    }
35

36
    token
281✔
37
}
38

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

70
fn expect_right_brace(
8✔
71
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
72
) -> ParserResult<()> {
73
    match peek_next_non_whitespace_token(tokens) {
8✔
74
        Some(non_whitespace) => match &non_whitespace.content {
8✔
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);
8✔
85
                Ok(())
8✔
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(
51✔
99
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
100
) -> bool {
101
    match peek_next_non_whitespace_token(tokens) {
51✔
102
        Some(non_whitespace) => match &non_whitespace.content {
51✔
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);
34✔
109
                true
34✔
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 expect_fat_arrow(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) {
28✔
127
    match pop_next_non_whitespace_token(tokens) {
28✔
128
        Some(non_whitespace) => match &non_whitespace.content {
28✔
129
            TokenContent::Whitespace => todo!(),
130
            TokenContent::Identifier(_identifier) => todo!(),
131
            TokenContent::Assign => todo!(),
132
            TokenContent::LeftParenthesis => todo!(),
133
            TokenContent::RightParenthesis => todo!(),
134
            TokenContent::LeftBracket => todo!(),
135
            TokenContent::RightBracket => todo!(),
136
            TokenContent::LeftBrace => todo!(),
137
            TokenContent::RightBrace => todo!(),
138
            TokenContent::Dot => todo!(),
139
            TokenContent::Colon => todo!(),
140
            TokenContent::Quotes(_) => todo!(),
141
            TokenContent::FatArrow => {}
28✔
142
            TokenContent::Comma => todo!(),
143
            TokenContent::EndOfFile => todo!(),
144
        },
145
        None => todo!(),
146
    }
147
}
148

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

172
fn skip_right_bracket(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
115✔
173
    let maybe_right_bracket = peek_next_non_whitespace_token(tokens);
115✔
174
    match maybe_right_bracket {
115✔
175
        Some(token) => match &token.content {
115✔
176
            TokenContent::Whitespace => unreachable!(),
177
            TokenContent::Identifier(_) => false,
49✔
178
            TokenContent::Assign => false,
×
179
            TokenContent::LeftParenthesis => false,
×
180
            TokenContent::RightParenthesis => false,
×
181
            TokenContent::LeftBracket => false,
2✔
182
            TokenContent::RightBracket => {
183
                tokens.next();
32✔
184
                true
32✔
185
            }
186
            TokenContent::LeftBrace => todo!(),
187
            TokenContent::RightBrace => todo!(),
188
            TokenContent::Dot => false,
×
189
            TokenContent::Colon => todo!(),
190
            TokenContent::Quotes(_) => false,
9✔
191
            TokenContent::FatArrow => false,
×
192
            TokenContent::Comma => false,
23✔
193
            TokenContent::EndOfFile => todo!(),
194
        },
195
        None => false,
×
196
    }
197
}
198

199
fn parse_tree_construction(
32✔
200
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
201
    local_namespace: &NamespaceId,
202
) -> ParserResult<ast::Expression> {
203
    let mut elements = Vec::new();
32✔
204
    loop {
205
        if skip_right_bracket(tokens) {
68✔
206
            break;
21✔
207
        }
208
        if !elements.is_empty() {
70✔
209
            expect_comma(tokens);
23✔
210
        }
211
        if skip_right_bracket(tokens) {
212
            break;
11✔
213
        }
214
        let element = parse_expression(tokens, local_namespace)?;
36✔
215
        elements.push(element);
216
    }
217
    Ok(ast::Expression::ConstructTree(elements))
32✔
218
}
219

220
fn parse_braces(
8✔
221
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
222
    local_namespace: &NamespaceId,
223
) -> ParserResult<ast::Expression> {
224
    let content = parse_expression(tokens, local_namespace)?;
16✔
225
    expect_right_brace(tokens)?;
×
226
    Ok(ast::Expression::Braces(Box::new(content)))
8✔
227
}
228

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

285
fn parse_apply(
7✔
286
    callee: ast::Expression,
287
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
288
    local_namespace: &NamespaceId,
289
) -> ParserResult<ast::Expression> {
290
    let mut arguments = Vec::new();
7✔
291
    loop {
292
        if try_skip_right_parenthesis(tokens) {
14✔
293
            break;
6✔
294
        }
295
        if !arguments.is_empty() {
10✔
296
            expect_comma(tokens);
2✔
297
        }
298
        if try_skip_right_parenthesis(tokens) {
299
            break;
×
300
        }
301
        let argument = parse_expression(tokens, local_namespace)?;
8✔
302
        arguments.push(argument);
303
    }
304
    Ok(ast::Expression::Apply {
6✔
305
        callee: Box::new(callee),
6✔
306
        arguments,
6✔
307
    })
308
}
309

310
pub fn parse_expression<'t>(
135✔
311
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
312
    local_namespace: &NamespaceId,
313
) -> ParserResult<ast::Expression> {
314
    let start = parse_expression_start(tokens, local_namespace)?;
270✔
315
    match peek_next_non_whitespace_token(tokens) {
×
316
        Some(more) => match &more.content {
131✔
317
            TokenContent::Whitespace => unreachable!(),
318
            TokenContent::Identifier(_) => Ok(start),
×
319
            TokenContent::Assign => Ok(start),
×
320
            TokenContent::LeftParenthesis => {
×
321
                tokens.next();
7✔
322
                parse_apply(start, tokens, local_namespace)
7✔
323
            }
324
            TokenContent::RightParenthesis => Ok(start),
8✔
325
            TokenContent::LeftBracket => todo!(),
326
            TokenContent::RightBracket => Ok(start),
13✔
327
            TokenContent::LeftBrace => todo!(),
328
            TokenContent::RightBrace => Ok(start),
11✔
329
            TokenContent::Dot => todo!(),
330
            TokenContent::Colon => todo!(),
331
            TokenContent::Quotes(_) => todo!(),
332
            TokenContent::FatArrow => todo!(),
333
            TokenContent::Comma => Ok(start),
25✔
334
            TokenContent::EndOfFile => Ok(start),
67✔
335
        },
336
        None => todo!(),
337
    }
338
}
339

340
fn try_pop_identifier(
40✔
341
    tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>,
342
) -> Option<(String, SourceLocation)> {
343
    match peek_next_non_whitespace_token(tokens) {
40✔
344
        Some(non_whitespace) => match &non_whitespace.content {
40✔
345
            TokenContent::Whitespace => todo!(),
346
            TokenContent::Identifier(identifier) => {
27✔
347
                pop_next_non_whitespace_token(tokens);
27✔
348
                Some((identifier.clone(), non_whitespace.location))
27✔
349
            }
350
            TokenContent::Assign => todo!(),
351
            TokenContent::LeftParenthesis => todo!(),
352
            TokenContent::RightParenthesis => None,
13✔
353
            TokenContent::LeftBracket => todo!(),
354
            TokenContent::RightBracket => todo!(),
355
            TokenContent::LeftBrace => todo!(),
356
            TokenContent::RightBrace => todo!(),
357
            TokenContent::Dot => todo!(),
358
            TokenContent::Colon => todo!(),
359
            TokenContent::Quotes(_) => todo!(),
360
            TokenContent::FatArrow => todo!(),
361
            TokenContent::Comma => None,
×
362
            TokenContent::EndOfFile => None,
×
363
        },
364
        None => None,
×
365
    }
366
}
367

368
fn try_skip_comma(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
27✔
369
    match peek_next_non_whitespace_token(tokens) {
27✔
370
        Some(non_whitespace) => match &non_whitespace.content {
27✔
371
            TokenContent::Whitespace => todo!(),
372
            TokenContent::Identifier(_identifier) => false,
1✔
373
            TokenContent::Assign => todo!(),
374
            TokenContent::LeftParenthesis => todo!(),
375
            TokenContent::RightParenthesis => false,
15✔
376
            TokenContent::LeftBracket => todo!(),
377
            TokenContent::RightBracket => todo!(),
378
            TokenContent::LeftBrace => todo!(),
379
            TokenContent::RightBrace => todo!(),
380
            TokenContent::Dot => todo!(),
381
            TokenContent::Colon => todo!(),
382
            TokenContent::Quotes(_) => todo!(),
383
            TokenContent::FatArrow => todo!(),
384
            TokenContent::Comma => {
385
                pop_next_non_whitespace_token(tokens);
11✔
386
                true
11✔
387
            }
388
            TokenContent::EndOfFile => false,
×
389
        },
390
        None => false,
×
391
    }
392
}
393

394
fn try_skip_colon(tokens: &mut std::iter::Peekable<std::slice::Iter<'_, Token>>) -> bool {
27✔
395
    match peek_next_non_whitespace_token(tokens) {
27✔
396
        Some(non_whitespace) => match &non_whitespace.content {
27✔
397
            TokenContent::Whitespace => todo!(),
398
            TokenContent::Identifier(_identifier) => false,
1✔
399
            TokenContent::Assign => todo!(),
400
            TokenContent::LeftParenthesis => todo!(),
401
            TokenContent::RightParenthesis => false,
14✔
402
            TokenContent::LeftBracket => todo!(),
403
            TokenContent::RightBracket => todo!(),
404
            TokenContent::LeftBrace => todo!(),
405
            TokenContent::RightBrace => todo!(),
406
            TokenContent::Dot => todo!(),
407
            TokenContent::Colon => {
408
                pop_next_non_whitespace_token(tokens);
1✔
409
                true
1✔
410
            }
411
            TokenContent::Quotes(_) => todo!(),
412
            TokenContent::FatArrow => todo!(),
413
            TokenContent::Comma => false,
11✔
NEW
414
            TokenContent::EndOfFile => false,
×
415
        },
NEW
416
        None => false,
×
417
    }
418
}
419

420
fn parse_lambda<'t>(
29✔
421
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
422
    local_namespace: &NamespaceId,
423
) -> ParserResult<ast::Expression> {
424
    let mut parameters = Vec::new();
29✔
425
    while let Some((parameter_name, parameter_location)) = try_pop_identifier(tokens) {
67✔
NEW
426
        let namespaced_name = Name::new(*local_namespace, parameter_name);
×
NEW
427
        let mut type_annotation = None;
×
NEW
428
        if try_skip_colon(tokens) {
×
429
            type_annotation = Some(parse_expression(tokens, local_namespace)?);
2✔
430
        }
431
        parameters.push(LambdaParameter::new(
27✔
432
            namespaced_name,
27✔
433
            parameter_location,
27✔
434
            type_annotation,
27✔
435
        ));
436
        if !try_skip_comma(tokens) {
27✔
437
            break;
16✔
438
        }
439
    }
440
    if !try_skip_right_parenthesis(tokens) {
29✔
441
        let next_token = peek_next_non_whitespace_token(tokens).unwrap();
1✔
442
        return Err(ParserError::new(
1✔
443
            "Expected comma or right parenthesis in lambda parameter list.".to_string(),
1✔
444
            next_token.location,
1✔
445
        ));
446
    }
447
    expect_fat_arrow(tokens);
28✔
448
    let body = parse_expression(tokens, local_namespace)?;
56✔
449
    Ok(ast::Expression::Lambda {
×
NEW
450
        parameters,
×
451
        body: Box::new(body),
×
452
    })
453
}
454

455
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
456
pub struct ParserOutput {
457
    pub entry_point: Option<ast::Expression>,
458
    pub errors: Vec<CompilerError>,
459
}
460

461
impl ParserOutput {
462
    pub fn new(entry_point: Option<ast::Expression>, errors: Vec<CompilerError>) -> ParserOutput {
18✔
463
        ParserOutput {
464
            entry_point,
465
            errors,
466
        }
467
    }
468
}
469

470
pub fn parse_expression_tolerantly<'t>(
16✔
471
    tokens: &mut std::iter::Peekable<std::slice::Iter<'t, Token>>,
472
    local_namespace: &NamespaceId,
473
) -> ParserOutput {
474
    let mut errors = Vec::new();
16✔
475
    let entry_point_result = parse_expression(tokens, local_namespace);
16✔
476
    match entry_point_result {
16✔
477
        Ok(entry_point) => ParserOutput::new(Some(entry_point), errors),
13✔
478
        Err(error) => {
3✔
479
            errors.push(CompilerError::new(
3✔
480
                format!("Parser error: {}", &error),
3✔
481
                error.location,
3✔
482
            ));
483
            ParserOutput::new(None, errors)
3✔
484
        }
485
    }
486
}
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