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

rokucommunity / brs / #103

01 May 2024 07:50PM UTC coverage: 89.383% (-0.2%) from 89.58%
#103

push

web-flow
Implement `try...catch` and `throw` (#72)

* Implemented try..catch and throw using existing interpreter.stack

* Added the Stack Trace to the Environment

* Updated e2e test case

* Updated Interpreter to use Roku Runtime Error messages

* Added stack trace back to Interpreter

* Improvements on try..catch error handling

* Added unit tests for Try Catch

* Re-added the return to prevent lint error on select case missing break

* Updated comment

* Removed unused references

* Code review recommendations

* Fixed Stack Trace test

* Removed redundant parameter in RuntimeError

* Changed parameter order on RuntimeError

* Prettier fix

* Renamed type ErrorCode to ErrorDetail

2096 of 2532 branches covered (82.78%)

Branch coverage included in aggregate %.

163 of 195 new or added lines in 12 files covered. (83.59%)

1 existing line in 1 file now uncovered.

5936 of 6454 relevant lines covered (91.97%)

29015.56 hits per line

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

74.19
/src/Error.ts
1
import { BrsType } from "./brsTypes";
2
import { TracePoint } from "./interpreter";
3
import type { Location } from "./lexer";
4
import chalk from "chalk";
144✔
5

6
export class BrsError extends Error {
144✔
7
    constructor(message: string, readonly location: Location, public backTrace?: TracePoint[]) {
318✔
8
        super(message);
318✔
9
    }
10

11
    /**
12
     * Formats the error into a human-readable string including filename, starting and ending line
13
     * and column, and the message associated with the error, e.g.:
14
     *
15
     * `lorem.brs(1,1-3): Expected '(' after sub name`
16
     * @see BrsError#format
17
     */
18
    format() {
19
        return BrsError.format(this.message, this.location);
17✔
20
    }
21

22
    /**
23
     * Formats a location and message into a human-readable string including filename, starting
24
     * and ending line and column, and the message associated with the error, e.g.:
25
     *
26
     * `lorem.brs(1,1-3): Expected '(' after sub name`
27
     *
28
     * @param message a string describing the error
29
     * @param location where the error occurred
30
     */
31
    static format(message: string, location: Location): string {
32
        let formattedLocation: string;
33

34
        if (location.start.line === location.end.line) {
17!
35
            let columns = `${location.start.column}`;
17✔
36
            if (location.start.column !== location.end.column) {
17✔
37
                columns += `-${location.end.column}`;
17✔
38
            }
39
            formattedLocation = `${location.file}(${location.start.line},${columns})`;
17✔
40
        } else {
41
            formattedLocation = `${location.file}(${location.start.line},${location.start.column},${location.end.line},${location.end.line})`;
×
42
        }
43

44
        return chalk.redBright(`${formattedLocation}: ${message}\n`);
17✔
45
    }
46
}
47

48
/** An error thrown when a BrightScript runtime error is encountered. */
49
export class RuntimeError extends BrsError {
144✔
50
    constructor(
51
        readonly errorDetail: ErrorDetail,
106✔
52
        location: Location,
53
        readonly backTrace?: TracePoint[],
106✔
54
        readonly extraFields?: Map<string, BrsType>
106✔
55
    ) {
56
        super(errorDetail.message, location, backTrace);
106✔
57
    }
58
}
59

60
/** Any error detail provided by the reference brightscript implementation. */
61
export type ErrorDetail = {
62
    /** The unique ID of the error. */
63
    errno: number;
64
    /** The human-readable version */
65
    message: string;
66
};
67

68
/**
69
 * Function to find the error detail by the errno
70
 * @param errno number of the error code
71
 * @returns the error detail object
72
 */
73
export function findErrorDetail(errno: number): ErrorDetail | null {
144✔
NEW
74
    for (const [_, value] of Object.entries(RuntimeErrorDetail)) {
×
NEW
75
        if (value.errno === errno) {
×
NEW
76
            return value;
×
77
        }
78
    }
NEW
79
    return null;
×
80
}
81

82
/** Enumerator with the RBI Runtime Error codes */
83
export const RuntimeErrorDetail = {
144✔
84
    NextWithoutFor: {
85
        errno: 0,
86
        message: "Next Without For.",
87
    },
88
    BadSyntax: {
89
        message: "Syntax Error.",
90
        errno: 2,
91
    },
92
    ReturnedWithoutGosub: {
93
        message: "Return Without Gosub.",
94
        errno: 4,
95
    },
96
    OutOfData: {
97
        message: "Out of Data on READ.",
98
        errno: 6,
99
    },
100
    BadFunctionOrArrayParam: {
101
        message: "Invalid parameter passed to function/array (e.g neg matrix dim or sqr root).",
102
        errno: 8,
103
    },
104
    OutOfMemory: {
105
        message: "Out Of Memory.",
106
        errno: 12,
107
    },
108
    MissingLineNumber: {
109
        message: "Label/Line Not Found.",
110
        errno: 14,
111
    },
112
    IndexOutOfBounds: {
113
        message: "Array subscript out of bounds.",
114
        errno: 16,
115
    },
116
    RedimensionArray: {
117
        message: "Attempted to redimension an array.",
118
        errno: 18,
119
    },
120
    DivideByZero: {
121
        message: "Divide by Zero.",
122
        errno: 20,
123
    },
124
    TypeMismatch: {
125
        message: "Type Mismatch.",
126
        errno: 24,
127
    },
128
    OutOfMemoryStringOp: {
129
        message: "Out of Memory when doing string operation.",
130
        errno: 26,
131
    },
132
    StringTooLong: {
133
        message: "String Too Long.",
134
        errno: 28,
135
    },
136
    BadBitShift: {
137
        message: "Invalid Bitwise Shift.",
138
        errno: 30,
139
    },
140
    NoContinue: {
141
        message: "Continue Not Allowed.",
142
        errno: 32,
143
    },
144
    OutOfRange: {
145
        message: "Constant Out Of Range",
146
        errno: 34,
147
    },
148
    ExecutionTimeout: {
149
        message: "Execution timeout",
150
        errno: 35,
151
    },
152
    InvalidFormatSpecifier: {
153
        message: "Invalid Format Specifier",
154
        errno: 36,
155
    },
156
    MalformedThrow: {
157
        message: "Invalid argument to Throw",
158
        errno: 38,
159
    },
160
    UserDefined: {
161
        message: "User-specified exception",
162
        errno: 40,
163
    },
164
    TooManyTasks: {
165
        message: "Too many task threads",
166
        errno: 41,
167
    },
168
    RunNotSupported: {
169
        message: "run() is unsupported.",
170
        errno: 140,
171
    },
172
    ContinueForWithoutFor: {
173
        message: "Continue For is not inside a For loop",
174
        errno: 141,
175
    },
176
    ContinueWhileWithoutWhile: {
177
        message: "Continue While is not inside a While",
178
        errno: 142,
179
    },
180
    TryContainsLabel: {
181
        message: "Labels are illegal inside a TRY clause.",
182
        errno: 143,
183
    },
184
    EvalDisabled: {
185
        message: "eval() is deprecated. You must eliminate usage of eval().",
186
        errno: 144,
187
    },
188
    FunctionNotFound: {
189
        message: "Function is not defined in component's namespace",
190
        errno: 145,
191
    },
192
    NameShadowsBuiltin: {
193
        message: "Syntax Error. Builtin function call expected.",
194
        errno: 157,
195
    },
196
    VarShadowsFunctionName: {
197
        message: "Variable name cannot be the same as that of a declared function.",
198
        errno: 160,
199
    },
200
    LabelLimitExceeded: {
201
        message: "Too Many Labels. Internal Label table size exceeded.",
202
        errno: 161,
203
    },
204
    ClassNotFound: {
205
        message: "Class Not Found.",
206
        errno: 162,
207
    },
208
    InterfaceTooLarge: {
209
        message: "Interface has too many functions for bytecode.",
210
        errno: 163,
211
    },
212
    NoInitializer: {
213
        message: "Assignment initializer missing.",
214
        errno: 164,
215
    },
216
    ExitForWithoutFor: {
217
        message: "Exit For is not inside a For loop.",
218
        errno: 165,
219
    },
220
    Deprecated: {
221
        message: "Statement type no longer supported.",
222
        errno: 166,
223
    },
224
    BadType: {
225
        message: "Type is Invalid.",
226
        errno: 167,
227
    },
228
    MissingReturnType: {
229
        message: "Function must have a return type.",
230
        errno: 168,
231
    },
232
    ReturnWithoutValue: {
233
        message: "Return must return a value.",
234
        errno: 169,
235
    },
236
    ReturnWithValue: {
237
        message:
238
            "Return can not have a return-value if inside a Sub or Function with Void return type.",
239
        errno: 170,
240
    },
241
    TypeMismatchForEachIndex: {
242
        message: "For-Each index variable must be 'dynamic' type.",
243
        errno: 171,
244
    },
245
    MissingMainFunction: {
246
        message: "No Main() Found.",
247
        errno: 172,
248
    },
249
    DuplicateSub: {
250
        message: "SUB or FUNCTION defined twice.",
251
        errno: 173,
252
    },
253
    LimitExceeded: {
254
        message: "Internal limit size exceeded.",
255
        errno: 174,
256
    },
257
    ExitWhileWithoutWhile: {
258
        message: "Exit While is not inside a While.",
259
        errno: 175,
260
    },
261
    TooManyVariables: {
262
        message: "Variable table size exceeded.",
263
        errno: 176,
264
    },
265
    TooManyConstants: {
266
        message: "Constant table size exceeded.",
267
        errno: 177,
268
    },
269
    FunctionNotExpected: {
270
        message: "Function not expected here.",
271
        errno: 178,
272
    },
273
    UnterminatedString: {
274
        message: `String missing ending quote.`,
275
        errno: 179,
276
    },
277
    DuplicateLabel: {
278
        message: "Label/LineNumber defined more than once.",
279
        errno: 180,
280
    },
281
    UnterminatedBlock: {
282
        message: "A block (such as FOR/NEXT or IF/ENDIF) was not terminated correctly.",
283
        errno: 181,
284
    },
285
    BadNext: {
286
        message: "Variable in NEXT does not match correct FOR.",
287
        errno: 182,
288
    },
289
    EndOfFile: {
290
        message: "Unexpected End-Of-File.",
291
        errno: 183,
292
    },
293
    CannotReadFile: {
294
        message: "Error loading file.",
295
        errno: 185,
296
    },
297
    LineNumberSequenceError: {
298
        message: "Classic BASIC style line number is out of sequence.",
299
        errno: 186,
300
    },
301
    NoLineNumber: {
302
        message: "Line Number not found where expected.",
303
        errno: 187,
304
    },
305
    IfWithoutEndIf: {
306
        message: "ENDIF Missing.",
307
        errno: 189,
308
    },
309
    WhileWithoutEndWhile: {
310
        message: "While Statement is missing a matching EndWhile.",
311
        errno: 190,
312
    },
313
    EndWhileWithoutWhile: {
314
        message: "EndWhile Without While.",
315
        errno: 191,
316
    },
317
    ExceptionThrownOnStack: {
318
        message: "UNEXPECTED INTERNAL (Exception on stack)",
319
        errno: 222,
320
    },
321
    StackOverflow: {
322
        message: "Stack overflow.",
323
        errno: 223,
324
    },
325
    NotAFunction: {
326
        message: "Function Call Operator ( ) attempted on non-function.",
327
        errno: 224,
328
    },
329
    UnsupportedUnicode: {
330
        message: "Error: Unicode not supported.",
331
        errno: 225,
332
    },
333
    ValueReturn: {
334
        message: "Return from non-function.",
335
        errno: 226,
336
    },
337
    BadNumberOfIndexes: {
338
        message: "Invalid number of Array indexes.",
339
        errno: 227,
340
    },
341
    BadLHS: {
342
        message: "Invalid value for left-side of expression.",
343
        errno: 228,
344
    },
345
    MissingReturnValue: {
346
        message: "Function does not have a required return.",
347
        errno: 229,
348
    },
349
    UninitializedFunction: {
350
        message: "Use of a reference to a function/sub that is not initialized.",
351
        errno: 230,
352
    },
353
    UndimmedArray: {
354
        message: "Array operation attempted on variable not DIM'd.",
355
        errno: 231,
356
    },
357
    NonNumericArrayIndex: {
358
        message: "Attempt to use a non-numeric array index not allowed.",
359
        errno: 232,
360
    },
361
    UninitializedVariable: {
362
        message: "Use of uninitialized variable.",
363
        errno: 233,
364
    },
365
    TypelessOperation: {
366
        message: "Operation on UnTyped operand(s) attempted.",
367
        errno: 235,
368
    },
369
    DotOnNonObject: {
370
        message:
371
            "'Dot' Operator attempted with invalid BrightScript Component or interface reference.",
372
        errno: 236,
373
    },
374
    NonStaticInterfaceCall: {
375
        message: "Interface function calls from type rotINTERFACE must by static.",
376
        errno: 237,
377
    },
378
    NotWaitable: {
379
        message:
380
            "Tried to Wait on an BrightScript Component that does not have MessagePort interface.",
381
        errno: 238,
382
    },
383
    NotPrintable: {
384
        message: "Non printable value.",
385
        errno: 239,
386
    },
387
    ReturnValueIgnored: {
388
        message: "Function returns a value that is ignored.",
389
        errno: 240,
390
    },
391
    WrongNumberOfParams: {
392
        message: "Wrong number of function parameters.",
393
        errno: 241,
394
    },
395
    TooManyParams: {
396
        message: "Too many function parameters (internal limit exceeded).",
397
        errno: 242,
398
    },
399
    InterfaceNotAMember: {
400
        message: "Interface not a member of BrightScript Component",
401
        errno: 243,
402
    },
403
    MemberFunctionNotFound: {
404
        message: "Member function not found in BrightScript Component or interface.",
405
        errno: 244,
406
    },
407
    RoWrongNumberOfParams: {
408
        message:
409
            "BrightScript Component function call does not have the correct number of parameters.",
410
        errno: 245,
411
    },
412
    ObjectClassNotFound: {
413
        message: "BrightScript Component Class not Found.",
414
        errno: 246,
415
    },
416
    Stop: {
417
        message: "STOP",
418
        errno: 247,
419
    },
420
    Break: {
421
        message: "BREAK",
422
        errno: 248,
423
    },
424
    StackUnderflow: {
425
        message: "Stack Underflow.",
426
        errno: 249,
427
    },
428
    MissingParenthesis: {
429
        message: "Missing Parentheses",
430
        errno: 250,
431
    },
432
    UndefinedOperator: {
433
        message: "Unsupported expression operator.",
434
        errno: 251,
435
    },
436
    NormalEnd: {
437
        message: "Normal End.",
438
        errno: 252,
439
    },
440
    UndefinedOpCode: {
441
        message: "Undefined Op Code.",
442
        errno: 253,
443
    },
444
    Internal: {
445
        message: "UNEXPECTED INTERNAL.",
446
        errno: 254,
447
    },
448
    Okay: {
449
        message: "OKAY",
450
        errno: 255,
451
    },
452
};
453

454
/**
455
 * Logs a detected BRS error to console.
456
 * @param err the error to log to console
457
 */
458
export function logConsoleError(err: BrsError) {
144✔
459
    console.error(err.format());
×
460
}
461

462
/**
463
 * Produces a function that writes errors to the given error stream.
464
 * @param errorStream write stream to write errors to.
465
 * @returns function that writes to given write stream.
466
 */
467
export function getLoggerUsing(errorStream: NodeJS.WriteStream): (err: BrsError) => boolean {
144✔
468
    return (err) => errorStream.write(err.format());
4,638✔
469
}
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