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

PhosphorLang / PhosphorCompiler / 7301873073

22 Dec 2023 04:21PM UTC coverage: 64.444% (-0.2%) from 64.622%
7301873073

Pull #15

github

web-flow
Merge 2b5d6af0e into 3e10dc4f0
Pull Request #15: Instantiable classes with methods

482 of 912 branches covered (0.0%)

Branch coverage included in aggregate %.

53 of 101 new or added lines in 20 files covered. (52.48%)

6 existing lines in 3 files now uncovered.

2050 of 3017 relevant lines covered (67.95%)

53.33 hits per line

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

73.47
/src/parser/parser.ts
1
import * as Diagnostic from '../diagnostic';
1✔
2
import * as SyntaxNodes from './syntaxNodes';
1✔
3
import { ElementsList } from './elementsList';
1✔
4
import { Namespace } from './namespace';
1✔
5
import { OperatorOrder } from './operatorOrder';
1✔
6
import { SyntaxNode } from './syntaxNodes';
7
import { Token } from '../lexer/token';
1✔
8
import { TokenKind } from '../lexer/tokenKind';
1✔
9

10
export class Parser
1✔
11
{
12
    private readonly diagnostic: Diagnostic.Diagnostic;
13

14
    private fileName: string;
15
    private tokens: Token[];
16
    private position: number;
17

18
    constructor (diagnostic: Diagnostic.Diagnostic)
19
    {
20
        this.diagnostic = diagnostic;
37✔
21

22
        this.fileName = '';
37✔
23
        this.tokens = [];
37✔
24
        this.position = 0;
37✔
25
    }
26

27
    private getToken (relativePosition: number, increasePosition: boolean): Token
28
    {
29
        const index = this.position + relativePosition;
3,017✔
30
        let result: Token;
31

32
        if ((index < this.tokens.length) && (index >= 0))
3,017✔
33
        {
34
            result = this.tokens[index];
2,983✔
35
        }
36
        else
37
        {
38
            result = new Token(TokenKind.NoToken, '');
34✔
39
        }
40

41
        if (increasePosition)
3,017✔
42
        {
43
            this.position++;
1,066✔
44
        }
45

46
        return result;
3,017✔
47
    }
48

49
    private consumeNextToken (): Token
50
    {
51
        return this.getToken(0, true);
1,066✔
52
    }
53

54
    private getCurrentToken (): Token
55
    {
56
        return this.getToken(0, false);
1,835✔
57
    }
58

59
    private getFollowerToken (): Token
60
    {
61
        return this.getToken(1, false);
107✔
62
    }
63

64
    private getPreviousToken (): Token
65
    {
66
        return this.getToken(-1, false);
9✔
67
    }
68

69
    /**
70
     * Run the parser for a given token list of a file.
71
     * @param tokens The list of tokens
72
     * @param fileName The name/path of the file
73
     * @return The root of the parsed syntax tree.
74
     */
75
    public run (tokens: Token[], fileName: string): SyntaxNodes.File
76
    {
77
        this.tokens = tokens;
37✔
78
        this.fileName = fileName;
37✔
79
        this.position = 0;
37✔
80

81
        const root = this.parseFile();
37✔
82

83
        return root;
34✔
84
    }
85

86
    private parseFile (): SyntaxNodes.File
87
    {
88
        const imports: SyntaxNodes.Import[] = [];
37✔
89
        const functions: SyntaxNodes.FunctionDeclaration[] = [];
37✔
90
        let module: SyntaxNodes.Module|null = null;
37✔
91

92
        while (this.getCurrentToken().kind != TokenKind.NoToken)
37✔
93
        {
94
            switch (this.getCurrentToken().kind)
96!
95
            {
96
                case TokenKind.ModuleKeyword:
97
                case TokenKind.ClassKeyword:
98
                {
99
                    module = this.parseModule();
37✔
100
                    break;
37✔
101
                }
102
                case TokenKind.ImportKeyword:
103
                {
104
                    const importDeclaration = this.parseImport();
1✔
105
                    imports.push(importDeclaration);
1✔
106
                    break;
1✔
107
                }
108
                case TokenKind.FunctionKeyword:
109
                case TokenKind.MethodKeyword:
110
                {
111
                    const functionDeclaration = this.parseFunctionDeclaration(false);
58✔
112
                    functions.push(functionDeclaration);
55✔
113
                    break;
55✔
114
                }
115
                case TokenKind.HeaderKeyword:
116
                {
117
                    const functionDeclaration = this.parseFunctionModifier();
×
118
                    functions.push(functionDeclaration);
×
119
                    break;
×
120
                }
121
                default:
122
                    this.diagnostic.throw(
×
123
                        new Diagnostic.Error(
124
                            `The token "${this.getCurrentToken().content}" is not allowed in the file scope.`,
125
                            Diagnostic.Codes.InvalidTokenInFileScopeError,
126
                            this.getCurrentToken()
127
                        )
128
                    );
129
            }
130
        }
131

132
        if (module == null)
34!
133
        {
134
            this.diagnostic.throw(
×
135
                new Diagnostic.Error(
136
                    `Missing module name in file.`,
137
                    Diagnostic.Codes.MissingModuleNameError,
138
                    {
139
                        fileName: this.fileName,
140
                        lineNumber: 0,
141
                        columnNumber: 0,
142
                    }
143
                )
144
            );
145
        }
146

147
        const fileRoot = new SyntaxNodes.File(this.fileName, module, imports, functions);
34✔
148

149
        return fileRoot;
34✔
150
    }
151

152
    private parseModule (): SyntaxNodes.Module
153
    {
154
        const keyword = this.consumeNextToken();
37✔
155
        const moduleNamespace = this.parseNamespace();
37✔
156

157
        const isClass = keyword.kind == TokenKind.ClassKeyword;
37✔
158

159
        return new SyntaxNodes.Module(keyword, moduleNamespace, isClass);
37✔
160
    }
161

162
    private parseImport (): SyntaxNodes.Import
163
    {
164
        const keyword = this.consumeNextToken();
1✔
165
        const importNamespace = this.parseNamespace();
1✔
166

167
        return new SyntaxNodes.Import(keyword, importNamespace);
1✔
168
    }
169

170
    private parseNamespace (): Namespace
171
    {
172
        let prefixComponents: Token[] = [];
38✔
173
        let pathComponents: Token[] = [];
38✔
174

175
        let nextToken = this.consumeNextToken();
38✔
176
        while (nextToken.kind != TokenKind.SemicolonToken)
38✔
177
        {
178
            switch (nextToken.kind)
38!
179
            {
180
                case TokenKind.IdentifierToken:
181
                    pathComponents.push(nextToken);
38✔
182
                    break;
38✔
183
                case TokenKind.DotToken:
184
                    break;
×
185
                case TokenKind.ColonToken:
186
                {
187
                    const temp = prefixComponents;
×
188
                    prefixComponents = pathComponents;
×
189
                    pathComponents = temp;
×
190

191
                    break;
×
192
                }
193
                default:
194
                    this.diagnostic.throw(
×
195
                        new Diagnostic.Error(
196
                            `Unexpected token "${nextToken.content}" in namespace.`,
197
                            Diagnostic.Codes.UnexpectedTokenInNamespace,
198
                            nextToken
199
                        )
200
                    );
201
            }
202

203
            nextToken = this.consumeNextToken();
38✔
204
        }
205

206
        const name = pathComponents.pop();
38✔
207

208
        if (name == undefined)
38!
209
        {
210
            this.diagnostic.throw(
×
211
                new Diagnostic.Error(
212
                    `Empty namespace.`,
213
                    Diagnostic.Codes.EmptyNamespaceError,
214
                    nextToken
215
                )
216
            );
217
        }
218

219
        const namespace = new Namespace(prefixComponents, pathComponents, name);
38✔
220

221
        return namespace;
38✔
222
    }
223

224
    private parseFunctionModifier (modifiers: Token[] = []): SyntaxNodes.FunctionDeclaration
×
225
    {
226
        let functionDeclaration: SyntaxNodes.FunctionDeclaration;
227

228
        switch (this.getCurrentToken().kind)
×
229
        {
230
            case TokenKind.HeaderKeyword:
231
            {
232
                const newModifier = this.consumeNextToken();
×
233
                modifiers.push(newModifier);
×
234

235
                functionDeclaration = this.parseFunctionModifier(modifiers);
×
236

237
                break;
×
238
            }
239
            case TokenKind.FunctionKeyword:
240
            {
241
                let isExternal = false;
×
242

243
                for (const modifier of modifiers)
×
244
                {
245
                    switch (modifier.kind)
×
246
                    {
247
                        case TokenKind.HeaderKeyword:
248
                            isExternal = true;
×
249
                            break;
×
250
                        default:
251
                            break;
×
252
                    }
253
                }
254

255
                functionDeclaration = this.parseFunctionDeclaration(isExternal);
×
256

257
                break;
×
258
            }
259
            default:
260
                this.diagnostic.throw(
×
261
                    new Diagnostic.Error(
262
                        `Unknown function modifier "${this.getCurrentToken().content}"`,
263
                        Diagnostic.Codes.UnknownFunctionModifierError,
264
                        this.getCurrentToken()
265
                    )
266
                );
267
        }
268

269
        return functionDeclaration;
×
270
    }
271

272
    private parseFunctionDeclaration (isExternal: boolean): SyntaxNodes.FunctionDeclaration
273
    {
274
        const keyword = this.consumeNextToken();
58✔
275
        const identifier = this.consumeNextToken();
58✔
276
        const opening = this.consumeNextToken();
58✔
277
        const parameters = this.parseFunctionParameters();
58✔
278
        const closing = this.consumeNextToken();
58✔
279
        const type = this.parseTypeClause();
58✔
280
        const body = this.parseSection();
58✔
281

282
        const isMethod = keyword.kind == TokenKind.MethodKeyword;
55✔
283

284
        if (isExternal)
55!
285
        {
286
            // The semicolon:
287
            this.consumeNextToken();
×
288
        }
289

290
        return new SyntaxNodes.FunctionDeclaration(keyword, identifier, opening, parameters, closing, type, body, isMethod, isExternal);
55✔
291
    }
292

293
    private parseFunctionParameters (): ElementsList<SyntaxNodes.FunctionParameter>
294
    {
295
        const parameters: SyntaxNodes.FunctionParameter[] = [];
58✔
296
        const separators: Token[] = [];
58✔
297

298
        while ((this.getCurrentToken().kind != TokenKind.ClosingRoundBracketToken) && (this.getCurrentToken().kind != TokenKind.NoToken))
58✔
299
        {
300
            const parameter = this.parseFunctionParameter();
35✔
301
            parameters.push(parameter);
35✔
302

303
            if (this.getCurrentToken().kind == TokenKind.CommaToken)
35✔
304
            {
305
                separators.push(this.consumeNextToken());
15✔
306
            }
307
            else
308
            {
309
                break;
20✔
310
            }
311
        }
312

313
        return new ElementsList(parameters, separators);
58✔
314
    }
315

316
    private parseFunctionParameter (): SyntaxNodes.FunctionParameter
317
    {
318
        const identifier = this.consumeNextToken();
35✔
319
        const type = this.parseTypeClause();
35✔
320

321
        if (type === null)
35!
322
        {
323
            this.diagnostic.throw(
×
324
                new Diagnostic.Error(
325
                    `Missing type clause in parameter definition`,
326
                    Diagnostic.Codes.MissingTypeClauseInParameterDefinitionError,
327
                    identifier
328
                )
329
            );
330
        }
331

332
        return new SyntaxNodes.FunctionParameter(identifier, type);
35✔
333
    }
334

335
    private parseTypeClause (): SyntaxNodes.TypeClause|null
336
    {
337
        if (this.getCurrentToken().kind != TokenKind.ColonToken)
94✔
338
        {
339
            return null;
34✔
340
        }
341
        else
342
        {
343
            const colon = this.consumeNextToken();
60✔
344
            const type = this.parseType();
60✔
345

346
            return new SyntaxNodes.TypeClause(colon, type);
60✔
347
        }
348
    }
349

350
    private parseType (): SyntaxNodes.Type
351
    {
352
        const identifier = this.consumeNextToken();
60✔
353

354
        if (this.getCurrentToken().kind == TokenKind.OpeningSquareBracketToken)
60!
355
        {
356
            const opening = this.consumeNextToken();
×
357
            const typeArguments = this.parseTypeArguments();
×
358
            const closing = this.consumeNextToken();
×
359

360
            return new SyntaxNodes.Type(identifier, opening, typeArguments, closing);
×
361
        }
362
        else
363
        {
364
            return new SyntaxNodes.Type(identifier);
60✔
365
        }
366
    }
367

368
    private parseTypeArguments (): ElementsList<SyntaxNodes.Type|SyntaxNodes.LiteralExpression>
369
    {
370
        const elements: (SyntaxNodes.Type|SyntaxNodes.LiteralExpression)[] = [];
×
371
        const separators: Token[] = [];
×
372

373
        while (true)
×
374
        {
375
            const currentToken = this.getCurrentToken();
×
376
            let element: SyntaxNodes.Type|SyntaxNodes.LiteralExpression|null;
377

378
            switch (currentToken.kind)
×
379
            {
380
                case TokenKind.IdentifierToken:
381
                    element = this.parseType();
×
382
                    break;
×
383
                case TokenKind.IntegerToken:
384
                case TokenKind.StringToken:
385
                case TokenKind.TrueKeyword:
386
                case TokenKind.FalseKeyword:
387
                    element = this.parseLiteralExpression();
×
388
                    break;
×
389
                case TokenKind.ClosingSquareBracketToken:
390
                case TokenKind.NoToken:
391
                    element = null;
×
392
                    break;
×
393
                default:
394
                    this.diagnostic.throw(
×
395
                        new Diagnostic.Error(
396
                            `Invalid token in type argument "${currentToken.content}"`,
397
                            Diagnostic.Codes.InvalidTokenInTypeArgumentError,
398
                            currentToken
399
                        )
400
                    );
401
            }
402

403
            if (element === null)
×
404
            {
405
                break;
×
406
            }
407

408
            elements.push(element);
×
409

410
            if (this.getCurrentToken().kind == TokenKind.CommaToken)
×
411
            {
412
                separators.push(this.consumeNextToken());
×
413
            }
414
        }
415

416
        return new ElementsList(elements, separators);
×
417
    }
418

419
    private parseSection (): SyntaxNodes.Section|null
420
    {
421
        if (this.getCurrentToken().kind != TokenKind.OpeningCurlyBracketToken)
71!
422
        {
423
            return null;
×
424
        }
425

426
        const opening = this.consumeNextToken();
71✔
427

428
        const statements: SyntaxNode[] = [];
71✔
429

430
        while (true)
71✔
431
        {
432
            while (this.getCurrentToken().kind == TokenKind.LineCommentToken)
151✔
433
            {
434
                this.consumeNextToken();
×
435

436
                // TODO: Instead of ignoring the comment here, the lexer should add it as trivia to real tokens.
437
            }
438

439
            if ((this.getCurrentToken().kind == TokenKind.ClosingCurlyBracketToken) || (this.getCurrentToken().kind == TokenKind.NoToken))
151✔
440
            {
441
                break;
68✔
442
            }
443

444
            const statement = this.parseStatement();
83✔
445
            statements.push(statement);
80✔
446

447
            // TODO: Prevent an infinite loop when there is a syntax error.
448
        }
449

450
        const closing = this.consumeNextToken();
68✔
451

452
        return new SyntaxNodes.Section(opening, statements, closing);
68✔
453
    }
454

455
    private parseStatement (): SyntaxNode
456
    {
457
        let result: SyntaxNode;
458

459
        switch (this.getCurrentToken().kind)
83✔
460
        {
461
            case TokenKind.VarKeyword:
462
                result = this.parseVariableDeclaration();
6✔
463
                break;
4✔
464
            case TokenKind.ReturnKeyword:
465
                result = this.parseReturnStatement();
31✔
466
                break;
31✔
467
            case TokenKind.IfKeyword:
468
                result = this.parseIfStatement();
6✔
469
                break;
6✔
470
            case TokenKind.WhileKeyword:
471
                result = this.parseWhileStatement();
2✔
472
                break;
2✔
473
            default:
474
            {
475
                if (this.isAssignment())
38✔
476
                {
477
                    result = this.parseAssignment();
12✔
478
                }
479
                else
480
                {
481
                    result = this.parseExpression();
26✔
482
                }
483
            }
484
        }
485

486
        if (this.getCurrentToken().kind == TokenKind.SemicolonToken)
81✔
487
        {
488
            // Remove the correct token:
489
            this.consumeNextToken();
72✔
490
        }
491
        // No semicolon needed after a closing curly bracket (often a section):
492
        else if (this.getPreviousToken().kind != TokenKind.ClosingCurlyBracketToken)
9✔
493
        {
494
            this.diagnostic.throw(
1✔
495
                new Diagnostic.Error(
496
                    `Missing semicolon after statement`,
497
                    Diagnostic.Codes.MissingSemicolonAfterStatementError,
498
                    this.getCurrentToken()
499
                )
500
            );
501
        }
502

503
        return result;
80✔
504
    }
505

506
    private parseReturnStatement (): SyntaxNodes.ReturnStatement
507
    {
508
        const keyword = this.consumeNextToken();
31✔
509

510
        let expression: SyntaxNodes.Expression|null = null;
31✔
511

512
        if (this.getCurrentToken().kind != TokenKind.SemicolonToken)
31✔
513
        {
514
            expression = this.parseExpression();
29✔
515
        }
516

517
        return new SyntaxNodes.ReturnStatement(keyword, expression);
31✔
518
    }
519

520
    private parseVariableDeclaration (): SyntaxNodes.VariableDeclaration
521
    {
522
        const keyword = this.consumeNextToken();
6✔
523
        const identifier = this.consumeNextToken();
6✔
524
        let type: SyntaxNodes.TypeClause|null = null;
6✔
525
        let assignment: Token|null = null;
6✔
526
        let initialiser: SyntaxNodes.Expression|null = null;
6✔
527

528
        switch (this.getCurrentToken().kind)
6✔
529
        {
530
            case TokenKind.AssignmentOperator:
531
                assignment = this.consumeNextToken();
3✔
532
                initialiser = this.parseExpression();
3✔
533
                break;
3✔
534
            case TokenKind.ColonToken:
535
                type = this.parseTypeClause();
1✔
536
                break;
1✔
537
            default:
538
                this.diagnostic.throw(
2✔
539
                    new Diagnostic.Error(
540
                        `Unexpected token "${this.getFollowerToken().content}" after variable declaration identifier`,
541
                        Diagnostic.Codes.UnexpectedTokenAfterVariableDeclarationIdentifierError,
542
                        this.getCurrentToken()
543
                    )
544
                );
545
        }
546

547
        return new SyntaxNodes.VariableDeclaration(keyword, identifier, type, assignment, initialiser);
4✔
548
    }
549

550
    private parseIfStatement (): SyntaxNodes.IfStatement
551
    {
552
        const keyword = this.consumeNextToken();
8✔
553
        const condition = this.parseExpression();
8✔
554
        const section = this.parseSection();
8✔
555

556
        if (section === null)
8!
557
        {
558
            this.diagnostic.throw(
×
559
                new Diagnostic.Error(
560
                    'Missing section in if statement.',
561
                    Diagnostic.Codes.MissingSectionInIfStatementError,
562
                    keyword
563
                )
564
            );
565
        }
566

567
        let elseClause: SyntaxNodes.ElseClause|null = null;
8✔
568

569
        if (this.getCurrentToken().kind == TokenKind.ElseKeyword)
8✔
570
        {
571
            elseClause = this.parseElseClause();
5✔
572
        }
573

574
        return new SyntaxNodes.IfStatement(keyword, condition, section, elseClause);
8✔
575
    }
576

577
    private parseElseClause (): SyntaxNodes.ElseClause
578
    {
579
        const keyword = this.consumeNextToken();
5✔
580
        let followUp: SyntaxNodes.Section | SyntaxNodes.IfStatement;
581

582
        if (this.getCurrentToken().kind == TokenKind.IfKeyword)
5✔
583
        {
584
            followUp = this.parseIfStatement();
2✔
585
        }
586
        else
587
        {
588
            const section = this.parseSection();
3✔
589

590
            if (section === null)
3!
591
            {
592
                this.diagnostic.throw(
×
593
                    new Diagnostic.Error(
594
                        'Missing section in else clause.',
595
                        Diagnostic.Codes.MissingSectionInElseClauseError,
596
                        keyword
597
                    )
598
                );
599
            }
600

601
            followUp = section;
3✔
602
        }
603

604
        return new SyntaxNodes.ElseClause(keyword, followUp);
5✔
605
    }
606

607
    private parseWhileStatement (): SyntaxNodes.WhileStatement
608
    {
609
        const keyword = this.consumeNextToken();
2✔
610
        const condition = this.parseExpression();
2✔
611
        const section = this.parseSection();
2✔
612

613
        if (section === null)
2!
614
        {
615
            this.diagnostic.throw(
×
616
                new Diagnostic.Error(
617
                    'Missing section in while statement.',
618
                    Diagnostic.Codes.MissingSectionInWhileStatementError,
619
                    keyword
620
                )
621
            );
622
        }
623

624
        return new SyntaxNodes.WhileStatement(keyword, condition, section);
2✔
625
    }
626

627
    private isAssignment (): boolean
628
    {
629
        const result = (this.getCurrentToken().kind == TokenKind.IdentifierToken) && (this.getFollowerToken().kind == TokenKind.AssignmentOperator);
38✔
630

631
        return result;
38✔
632
    }
633

634
    private parseAssignment (): SyntaxNodes.Assignment
635
    {
636
        const identifierToken = this.consumeNextToken();
12✔
637
        const operatorToken = this.consumeNextToken();
12✔
638
        const rightSide = this.parseExpression();
12✔
639

640
        const result = new SyntaxNodes.Assignment(identifierToken, operatorToken, rightSide);
12✔
641

642
        return result;
12✔
643
    }
644

645
    private parseExpression (parentPriority = 0): SyntaxNodes.Expression
119✔
646
    {
647
        let left: SyntaxNodes.Expression;
648

649
        if (this.isUnaryExpression(parentPriority))
153✔
650
        {
651
            left = this.parseUnaryExpression();
6✔
652
        }
653
        else
654
        {
655
            left = this.parsePrimaryExpression();
147✔
656
        }
657

658
        while (this.isBinaryExpression(parentPriority))
153✔
659
        {
660
            left = this.parseBinaryExpression(left);
28✔
661
        }
662

663
        return left;
153✔
664
    }
665

666
    private isUnaryExpression (parentPriority: number): boolean
667
    {
668
        const unaryPriority = OperatorOrder.getUnaryPriority(this.getCurrentToken());
153✔
669

670
        const result = (unaryPriority !== 0) && (unaryPriority >= parentPriority);
153✔
671

672
        return result;
153✔
673
    }
674

675
    private isBinaryExpression (parentPriority: number): boolean
676
    {
677
        const binaryPriority = OperatorOrder.getBinaryPriority(this.getCurrentToken());
181✔
678

679
        const result = (binaryPriority !== 0) && (binaryPriority > parentPriority);
181✔
680

681
        return result;
181✔
682
    }
683

684
    private parseUnaryExpression (): SyntaxNodes.UnaryExpression
685
    {
686
        const operator = this.consumeNextToken();
6✔
687
        const operatorPriority = OperatorOrder.getUnaryPriority(operator);
6✔
688
        const operand = this.parseExpression(operatorPriority);
6✔
689

690
        return new SyntaxNodes.UnaryExpression(operator, operand);
6✔
691
    }
692

693
    private parseBinaryExpression (left: SyntaxNodes.Expression): SyntaxNodes.BinaryExpression
694
    {
695
        const operator = this.consumeNextToken();
28✔
696
        const operatorPriority = OperatorOrder.getBinaryPriority(operator);
28✔
697
        const right = this.parseExpression(operatorPriority);
28✔
698

699
        return new SyntaxNodes.BinaryExpression(left, operator, right);
28✔
700
    }
701

702
    private parsePrimaryExpression (): SyntaxNodes.Expression
703
    {
704
        switch (this.getCurrentToken().kind)
147!
705
        {
706
            case TokenKind.OpeningRoundBracketToken:
707
                return this.parseBracketedExpression();
1✔
708
            case TokenKind.IntegerToken:
709
            case TokenKind.StringToken:
710
            case TokenKind.TrueKeyword:
711
            case TokenKind.FalseKeyword:
712
                return this.parseLiteralExpression();
79✔
713
            case TokenKind.IdentifierToken:
714
                return this.parseIdentifierExpression();
67✔
715
            case TokenKind.NewKeyword:
NEW
716
                return this.parseInstantiationExpression();
×
717
            default:
718
                this.diagnostic.throw(
×
719
                    new Diagnostic.Error(
720
                        `Unknown expression "${this.getCurrentToken().content}"`,
721
                        Diagnostic.Codes.UnknownExpressionError,
722
                        this.getCurrentToken()
723
                    )
724
                );
725
        }
726
    }
727

728
    private parseBracketedExpression (): SyntaxNodes.BracketedExpression
729
    {
730
        const opening = this.consumeNextToken();
1✔
731
        const expression = this.parseExpression();
1✔
732
        const closing = this.consumeNextToken();
1✔
733

734
        return new SyntaxNodes.BracketedExpression(opening, expression, closing);
1✔
735
    }
736

737
    private parseLiteralExpression (): SyntaxNodes.LiteralExpression
738
    {
739
        const literal = this.consumeNextToken();
79✔
740

741
        return new SyntaxNodes.LiteralExpression(literal);
79✔
742
    }
743

744
    private parseIdentifierExpression (): SyntaxNodes.Expression
745
    {
746
        switch (this.getFollowerToken().kind)
67!
747
        {
748
            case TokenKind.DotToken:
749
                return this.parseAccessExpression();
×
750
            case TokenKind.OpeningRoundBracketToken:
751
                return this.parseCallExpression();
28✔
752
            default:
753
                return this.parseVariableExpression();
39✔
754
        }
755
    }
756

757
    private parseAccessExpression (): SyntaxNodes.AccessExpression
758
    {
759
        const identifier = this.consumeNextToken();
×
760
        const dot = this.consumeNextToken();
×
761
        const functionCall = this.parseCallExpression();
×
762

763
        return new SyntaxNodes.AccessExpression(identifier, dot, functionCall);
×
764
    }
765

766
    private parseCallExpression (): SyntaxNodes.CallExpression
767
    {
768
        const identifier = this.consumeNextToken();
28✔
769
        const opening = this.consumeNextToken();
28✔
770
        const callArguments = this.parseCallArguments();
28✔
771
        const closing = this.consumeNextToken();
28✔
772

773
        return new SyntaxNodes.CallExpression(identifier, opening, callArguments, closing);
28✔
774
    }
775

776
    private parseCallArguments (): ElementsList<SyntaxNodes.Expression>
777
    {
778
        const expressions: SyntaxNodes.Expression[] = [];
28✔
779
        const separators: Token[] = [];
28✔
780

781
        while ((this.getCurrentToken().kind != TokenKind.ClosingRoundBracketToken) && (this.getCurrentToken().kind != TokenKind.NoToken))
28✔
782
        {
783
            const expression = this.parseExpression();
38✔
784
            expressions.push(expression);
38✔
785

786
            if (this.getCurrentToken().kind == TokenKind.CommaToken)
38✔
787
            {
788
                separators.push(this.consumeNextToken());
16✔
789
            }
790
            else
791
            {
792
                break;
22✔
793
            }
794
        }
795

796
        return new ElementsList(expressions, separators);
28✔
797
    }
798

799
    private parseInstantiationExpression (): SyntaxNodes.InstantiationExpression
800
    {
NEW
801
        const keyword = this.consumeNextToken();
×
802
        const type = this.parseType();
×
803
        const opening = this.consumeNextToken();
×
NEW
804
        const constructorArguments = this.parseCallArguments();
×
805
        const closing = this.consumeNextToken();
×
806

NEW
807
        return new SyntaxNodes.InstantiationExpression(keyword, type, opening, constructorArguments, closing);
×
808
    }
809

810
    private parseVariableExpression (): SyntaxNodes.VariableExpression
811
    {
812
        const identifier = this.consumeNextToken();
39✔
813

814
        return new SyntaxNodes.VariableExpression(identifier);
39✔
815
    }
816
}
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