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

rokucommunity / brs / #305

18 Jan 2024 09:05PM UTC coverage: 91.463% (+5.2%) from 86.214%
#305

push

TwitchBronBron
0.45.4

1796 of 2095 branches covered (85.73%)

Branch coverage included in aggregate %.

5275 of 5636 relevant lines covered (93.59%)

8947.19 hits per line

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

91.98
/src/parser/Statement.ts
1
import * as Expr from "./Expression";
2
import { Token, Identifier, Location, Lexeme } from "../lexer";
3
import { BrsType, BrsInvalid } from "../brsTypes";
4
import { AstNode } from "./AstNode";
139✔
5

6
/** A set of reasons why a `Block` stopped executing. */
7
export * from "./BlockEndReason";
139✔
8

9
export interface Visitor<T> {
10
    visitAssignment(statement: Assignment): BrsType;
11
    visitDim(statement: Dim): BrsType;
12
    visitExpression(statement: Expression): BrsType;
13
    visitExitFor(statement: ExitFor): never;
14
    visitExitWhile(statement: ExitWhile): never;
15
    visitPrint(statement: Print): BrsType;
16
    visitIf(statement: If): BrsType;
17
    visitBlock(block: Block): BrsType;
18
    visitFor(statement: For): BrsType;
19
    visitForEach(statement: ForEach): BrsType;
20
    visitWhile(statement: While): BrsType;
21
    visitNamedFunction(statement: Function): BrsType;
22
    visitReturn(statement: Return): never;
23
    visitDottedSet(statement: DottedSet): BrsType;
24
    visitIndexedSet(statement: IndexedSet): BrsType;
25
    visitIncrement(expression: Increment): BrsInvalid;
26
    visitLibrary(statement: Library): BrsInvalid;
27
    visitTryCatch(statement: TryCatch): BrsInvalid;
28
}
29

30
let statementTypes = new Set<string>([
139✔
31
    "Assignment",
32
    "Expression",
33
    "ExitFor",
34
    "ExitWhile",
35
    "Print",
36
    "If",
37
    "Block",
38
    "For",
39
    "ForEach",
40
    "While",
41
    "Stmt_Function",
42
    "Return",
43
    "DottedSet",
44
    "IndexedSet",
45
    "Increment",
46
    "Library",
47
    "Dim",
48
]);
49

50
/**
51
 * Returns a boolean of whether or not the given object is a Statement.
52
 * @param obj object to check
53
 */
54
export function isStatement(obj: Expr.Expression | Statement): obj is Statement {
139✔
55
    return statementTypes.has(obj.type);
475✔
56
}
57

58
/** A BrightScript statement */
59
export interface Statement extends AstNode {
60
    /**
61
     * Handles the enclosing `Statement` with `visitor`.
62
     * @param visitor the `Visitor` that will handle the enclosing `Statement`
63
     * @returns a BrightScript value (typically `invalid`) and the reason why
64
     *          the statement exited (typically `StopReason.End`)
65
     */
66
    accept<R>(visitor: Visitor<R>): BrsType;
67
}
68

69
export class Assignment extends AstNode implements Statement {
139✔
70
    constructor(
71
        readonly tokens: {
3,304✔
72
            equals: Token;
73
        },
74
        readonly name: Identifier,
3,304✔
75
        readonly value: Expr.Expression
3,304✔
76
    ) {
77
        super("Assignment");
3,304✔
78
    }
79

80
    accept<R>(visitor: Visitor<R>): BrsType {
81
        return visitor.visitAssignment(this);
778✔
82
    }
83

84
    get location() {
85
        return {
811✔
86
            file: this.name.location.file,
87
            start: this.name.location.start,
88
            end: this.value.location.end,
89
        };
90
    }
91
}
92

93
export class Dim extends AstNode implements Statement {
139✔
94
    constructor(
95
        readonly tokens: {
5✔
96
            dim: Token;
97
            closingBrace: Token;
98
        },
99
        readonly name: Identifier,
5✔
100
        readonly dimensions: Expr.Expression[]
5✔
101
    ) {
102
        super("Dim");
5✔
103
    }
104

105
    accept<R>(visitor: Visitor<R>): BrsType {
106
        return visitor.visitDim(this);
4✔
107
    }
108

109
    get location() {
110
        return {
7✔
111
            file: this.tokens.dim.location.file,
112
            start: this.tokens.dim.location.start,
113
            end: this.tokens.closingBrace.location.end,
114
        };
115
    }
116
}
117

118
export class Block extends AstNode implements Statement {
139✔
119
    constructor(readonly statements: ReadonlyArray<Statement>, readonly location: Location) {
4,284✔
120
        super("Block");
4,284✔
121
    }
122

123
    accept<R>(visitor: Visitor<R>): BrsType {
124
        return visitor.visitBlock(this);
908✔
125
    }
126
}
127

128
export class Expression extends AstNode implements Statement {
139✔
129
    constructor(readonly expression: Expr.Expression) {
4,927✔
130
        super("Expression");
4,927✔
131
    }
132

133
    accept<R>(visitor: Visitor<R>): BrsType {
134
        return visitor.visitExpression(this);
567✔
135
    }
136

137
    get location() {
138
        return this.expression.location;
606✔
139
    }
140
}
141

142
export class ExitFor extends AstNode implements Statement {
139✔
143
    constructor(
144
        readonly tokens: {
8✔
145
            exitFor: Token;
146
        }
147
    ) {
148
        super("ExitFor");
8✔
149
    }
150

151
    accept<R>(visitor: Visitor<R>): BrsType {
152
        return visitor.visitExitFor(this);
4✔
153
    }
154

155
    get location() {
156
        return this.tokens.exitFor.location;
11✔
157
    }
158
}
159

160
export class ExitWhile extends AstNode implements Statement {
139✔
161
    constructor(
162
        readonly tokens: {
4✔
163
            exitWhile: Token;
164
        }
165
    ) {
166
        super("ExitWhile");
4✔
167
    }
168

169
    accept<R>(visitor: Visitor<R>): BrsType {
170
        return visitor.visitExitWhile(this);
3✔
171
    }
172

173
    get location() {
174
        return this.tokens.exitWhile.location;
9✔
175
    }
176
}
177

178
export class Function extends AstNode implements Statement {
139✔
179
    constructor(readonly name: Identifier, readonly func: Expr.Function) {
3,566✔
180
        super("Stmt_Function");
3,566✔
181
    }
182

183
    accept<R>(visitor: Visitor<R>): BrsType {
184
        return visitor.visitNamedFunction(this);
4,337✔
185
    }
186

187
    get location() {
188
        return {
4,364✔
189
            file: this.name.location.file,
190
            start: this.func.location.start,
191
            end: this.func.location.end,
192
        };
193
    }
194
}
195

196
export interface ElseIf {
197
    condition: Expr.Expression;
198
    thenBranch: Block;
199
    /** Signal to ESLint to walk condition and thenBranch */
200
    type: string;
201
}
202

203
export class If extends AstNode implements Statement {
139✔
204
    constructor(
205
        readonly tokens: {
250✔
206
            if: Token;
207
            then?: Token;
208
            // TODO: figure a decent way to represent the if/then + elseif/then pairs to enable a
209
            // linter to check for the lack of `then` with this AST. maybe copy ESTree's format?
210
            elseIfs?: Token[];
211
            else?: Token;
212
            endIf?: Token;
213
        },
214
        readonly condition: Expr.Expression,
250✔
215
        readonly thenBranch: Block,
250✔
216
        readonly elseIfs: ElseIf[],
250✔
217
        readonly elseBranch?: Block
250✔
218
    ) {
219
        super("If");
250✔
220
    }
221

222
    accept<R>(visitor: Visitor<R>): BrsType {
223
        return visitor.visitIf(this);
308✔
224
    }
225

226
    private getEndLocation(): Location {
227
        if (this.tokens.endIf) {
335✔
228
            return this.tokens.endIf.location;
93✔
229
        } else if (this.elseBranch) {
242✔
230
            return this.elseBranch.location;
3✔
231
        } else if (this.elseIfs.length) {
239!
232
            return this.elseIfs[this.elseIfs.length - 1].thenBranch.location;
×
233
        } else {
234
            return this.thenBranch.location;
239✔
235
        }
236
    }
237

238
    get location() {
239
        return {
335✔
240
            file: this.tokens.if.location.file,
241
            start: this.tokens.if.location.start,
242
            end: this.getEndLocation().end,
243
        };
244
    }
245
}
246

247
export class Increment extends AstNode implements Statement {
139✔
248
    constructor(readonly value: Expr.Expression, readonly token: Token) {
21✔
249
        super("Increment");
21✔
250
    }
251

252
    accept<R>(visitor: Visitor<R>): BrsType {
253
        return visitor.visitIncrement(this);
256✔
254
    }
255

256
    get location() {
257
        return {
264✔
258
            file: this.value.location.file,
259
            start: this.value.location.start,
260
            end: this.token.location.end,
261
        };
262
    }
263
}
264

265
/** The set of all accepted `print` statement separators. */
266
export namespace PrintSeparator {
267
    /** Used to indent the current `print` position to the next 16-character-width output zone. */
268
    export interface Tab extends Token {
269
        kind: Lexeme.Comma;
270
    }
271

272
    /** Used to insert a single whitespace character at the current `print` position. */
273
    export interface Space extends Token {
274
        kind: Lexeme.Semicolon;
275
    }
276
}
277

278
/**
279
 * Represents a `print` statement within BrightScript.
280
 */
281
export class Print extends AstNode implements Statement {
139✔
282
    /**
283
     * Creates a new internal representation of a BrightScript `print` statement.
284
     * @param expressions an array of expressions or `PrintSeparator`s to be
285
     *                    evaluated and printed.
286
     */
287
    constructor(
288
        readonly tokens: {
9,538✔
289
            print: Token;
290
        },
291
        readonly expressions: (Expr.Expression | Token)[]
9,538✔
292
    ) {
293
        super("Print");
9,538✔
294
    }
295

296
    accept<R>(visitor: Visitor<R>): BrsType {
297
        return visitor.visitPrint(this);
1,006✔
298
    }
299

300
    get location() {
301
        let end = this.expressions.length
1,017!
302
            ? this.expressions[this.expressions.length - 1].location.end
303
            : this.tokens.print.location.end;
304

305
        return {
1,017✔
306
            file: this.tokens.print.location.file,
307
            start: this.tokens.print.location.start,
308
            end: end,
309
        };
310
    }
311
}
312

313
export class Goto extends AstNode implements Statement {
139✔
314
    constructor(
315
        readonly tokens: {
3✔
316
            goto: Token;
317
            label: Token;
318
        }
319
    ) {
320
        super("Goto");
3✔
321
    }
322

323
    accept<R>(_visitor: Visitor<R>): BrsType {
324
        //should search the code for the corresponding label, and set that as the next line to execute
325
        throw new Error("Not implemented");
×
326
    }
327

328
    get location() {
329
        return {
×
330
            file: this.tokens.goto.location.file,
331
            start: this.tokens.goto.location.start,
332
            end: this.tokens.label.location.end,
333
        };
334
    }
335
}
336

337
export class Label extends AstNode implements Statement {
139✔
338
    constructor(
339
        readonly tokens: {
2✔
340
            identifier: Token;
341
            colon: Token;
342
        }
343
    ) {
344
        super("Label");
2✔
345
    }
346

347
    accept<R>(_visitor: Visitor<R>): BrsType {
348
        throw new Error("Not implemented");
×
349
    }
350

351
    get location() {
352
        return {
×
353
            file: this.tokens.identifier.location.file,
354
            start: this.tokens.identifier.location.start,
355
            end: this.tokens.colon.location.end,
356
        };
357
    }
358
}
359

360
export class Return extends AstNode implements Statement {
139✔
361
    constructor(
362
        readonly tokens: {
1,150✔
363
            return: Token;
364
        },
365
        readonly value?: Expr.Expression
1,150✔
366
    ) {
367
        super("Return");
1,150✔
368
    }
369

370
    accept<R>(visitor: Visitor<R>): BrsType {
371
        return visitor.visitReturn(this);
130✔
372
    }
373

374
    get location() {
375
        return {
134✔
376
            file: this.tokens.return.location.file,
377
            start: this.tokens.return.location.start,
378
            end: (this.value && this.value.location.end) || this.tokens.return.location.end,
268✔
379
        };
380
    }
381
}
382

383
export class End extends AstNode implements Statement {
139✔
384
    constructor(
385
        readonly tokens: {
1✔
386
            end: Token;
387
        }
388
    ) {
389
        super("End");
1✔
390
    }
391

392
    accept<R>(_visitor: Visitor<R>): BrsType {
393
        //TODO implement this in the runtime. It should immediately terminate program execution, without error
394
        throw new Error("Not implemented");
×
395
    }
396

397
    get location() {
398
        return {
×
399
            file: this.tokens.end.location.file,
400
            start: this.tokens.end.location.start,
401
            end: this.tokens.end.location.end,
402
        };
403
    }
404
}
405

406
export class Stop extends AstNode implements Statement {
139✔
407
    constructor(
408
        readonly tokens: {
2✔
409
            stop: Token;
410
        }
411
    ) {
412
        super("Stop");
2✔
413
    }
414

415
    accept<R>(_visitor: Visitor<R>): BrsType {
416
        //TODO implement this in the runtime. It should pause code execution until a `c` command is issued from the console
417
        throw new Error("Not implemented");
×
418
    }
419

420
    get location() {
421
        return {
×
422
            file: this.tokens.stop.location.file,
423
            start: this.tokens.stop.location.start,
424
            end: this.tokens.stop.location.end,
425
        };
426
    }
427
}
428

429
export class For extends AstNode implements Statement {
139✔
430
    constructor(
431
        readonly tokens: {
25✔
432
            for: Token;
433
            to: Token;
434
            step?: Token;
435
            endFor: Token;
436
        },
437
        readonly counterDeclaration: Assignment,
25✔
438
        readonly finalValue: Expr.Expression,
25✔
439
        readonly increment: Expr.Expression,
25✔
440
        readonly body: Block
25✔
441
    ) {
442
        super("For");
25✔
443
    }
444

445
    accept<R>(visitor: Visitor<R>): BrsType {
446
        return visitor.visitFor(this);
20✔
447
    }
448

449
    get location() {
450
        return {
24✔
451
            file: this.tokens.for.location.file,
452
            start: this.tokens.for.location.start,
453
            end: this.tokens.endFor.location.end,
454
        };
455
    }
456
}
457

458
export class ForEach extends AstNode implements Statement {
139✔
459
    constructor(
460
        readonly tokens: {
71✔
461
            forEach: Token;
462
            in: Token;
463
            endFor: Token;
464
        },
465
        readonly item: Token,
71✔
466
        readonly target: Expr.Expression,
71✔
467
        readonly body: Block
71✔
468
    ) {
469
        super("ForEach");
71✔
470
    }
471

472
    accept<R>(visitor: Visitor<R>): BrsType {
473
        return visitor.visitForEach(this);
19✔
474
    }
475

476
    get location() {
477
        return {
23✔
478
            file: this.tokens.forEach.location.file,
479
            start: this.tokens.forEach.location.start,
480
            end: this.tokens.endFor.location.end,
481
        };
482
    }
483
}
484

485
export class While extends AstNode implements Statement {
139✔
486
    constructor(
487
        readonly tokens: {
17✔
488
            while: Token;
489
            endWhile: Token;
490
        },
491
        readonly condition: Expr.Expression,
17✔
492
        readonly body: Block
17✔
493
    ) {
494
        super("While");
17✔
495
    }
496

497
    accept<R>(visitor: Visitor<R>): BrsType {
498
        return visitor.visitWhile(this);
15✔
499
    }
500

501
    get location() {
502
        return {
19✔
503
            file: this.tokens.while.location.file,
504
            start: this.tokens.while.location.start,
505
            end: this.tokens.endWhile.location.end,
506
        };
507
    }
508
}
509

510
export class DottedSet extends AstNode implements Statement {
139✔
511
    constructor(
512
        readonly obj: Expr.Expression,
2,730✔
513
        readonly name: Identifier,
2,730✔
514
        readonly value: Expr.Expression
2,730✔
515
    ) {
516
        super("DottedSet");
2,730✔
517
    }
518

519
    accept<R>(visitor: Visitor<R>): BrsType {
520
        return visitor.visitDottedSet(this);
147✔
521
    }
522

523
    get location() {
524
        return {
151✔
525
            file: this.obj.location.file,
526
            start: this.obj.location.start,
527
            end: this.value.location.end,
528
        };
529
    }
530
}
531

532
export class IndexedSet extends AstNode implements Statement {
139✔
533
    constructor(
534
        readonly obj: Expr.Expression,
75✔
535
        readonly index: Expr.Expression,
75✔
536
        readonly value: Expr.Expression,
75✔
537
        readonly closingSquare: Token
75✔
538
    ) {
539
        super("IndexedSet");
75✔
540
    }
541

542
    accept<R>(visitor: Visitor<R>): BrsType {
543
        return visitor.visitIndexedSet(this);
23✔
544
    }
545

546
    get location() {
547
        return {
30✔
548
            file: this.obj.location.file,
549
            start: this.obj.location.start,
550
            end: this.value.location.end,
551
        };
552
    }
553
}
554

555
export class Library extends AstNode implements Statement {
139✔
556
    constructor(
557
        readonly tokens: {
7✔
558
            library: Token;
559
            filePath: Token | undefined;
560
        }
561
    ) {
562
        super("Library");
7✔
563
    }
564

565
    accept<R>(visitor: Visitor<R>): BrsType {
566
        return visitor.visitLibrary(this);
×
567
    }
568

569
    get location() {
570
        return {
2✔
571
            file: this.tokens.library.location.file,
572
            start: this.tokens.library.location.start,
573
            end: this.tokens.filePath
2!
574
                ? this.tokens.filePath.location.end
575
                : this.tokens.library.location.end,
576
        };
577
    }
578
}
579

580
export class TryCatch extends AstNode implements Statement {
139✔
581
    constructor(
582
        readonly tryBlock: Block,
5✔
583
        readonly catchBlock: Block,
5✔
584
        readonly errorBinding: Expr.Variable,
5✔
585
        readonly tokens: {
5✔
586
            try: Token;
587
            catch: Token;
588
            endtry: Token;
589
        }
590
    ) {
591
        super("TryCatch");
5✔
592
    }
593

594
    accept<R>(visitor: Visitor<R>): BrsType {
595
        return visitor.visitTryCatch(this);
1✔
596
    }
597

598
    get location() {
599
        return {
1✔
600
            file: this.tokens.try.location.file,
601
            start: this.tokens.endtry.location.start,
602
            end: this.tokens.endtry.location.end,
603
        };
604
    }
605
}
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