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

rokucommunity / brs / #310

01 Dec 2023 08:37PM UTC coverage: 91.498% (+1.0%) from 90.458%
#310

push

TwitchBronBron
0.45.3

1784 of 2079 branches covered (85.81%)

Branch coverage included in aggregate %.

5265 of 5625 relevant lines covered (93.6%)

8959.42 hits per line

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

94.59
/src/brsTypes/components/RoString.ts
1
import { BrsComponent } from "./BrsComponent";
139✔
2
import { RoArray } from "./RoArray";
139✔
3
import { BrsValue, ValueKind, BrsString, BrsBoolean, BrsInvalid, Comparable } from "../BrsType";
139✔
4
import { Callable, StdlibArgument } from "../Callable";
139✔
5
import { Interpreter } from "../../interpreter";
6
import { BrsType } from "..";
7
import { Unboxable } from "../Boxing";
8
import { Int32 } from "../Int32";
139✔
9
import { Float } from "../Float";
139✔
10

11
export class RoString extends BrsComponent implements BrsValue, Comparable, Unboxable {
139✔
12
    readonly kind = ValueKind.Object;
226✔
13
    private intrinsic: BrsString = new BrsString("");
226✔
14

15
    public getValue(): string {
16
        return this.intrinsic.value;
4✔
17
    }
18

19
    constructor(initialValue?: BrsString) {
20
        super("roString");
226✔
21

22
        if (initialValue) {
226✔
23
            this.intrinsic = initialValue;
224✔
24
        }
25

26
        this.registerMethods({
226✔
27
            ifString: [this.setString, this.getString],
28
            ifStringOps: [
29
                this.appendString,
30
                this.len,
31
                this.left,
32
                this.right,
33
                this.mid,
34
                this.instr,
35
                this.replace,
36
                this.trim,
37
                this.toInt,
38
                this.toFloat,
39
                this.tokenize,
40
                this.setString,
41
                this.split,
42
                this.getEntityEncode,
43
                this.escape,
44
                this.unescape,
45
                this.encodeUri,
46
                this.decodeUri,
47
                this.encodeUriComponent,
48
                this.decodeUriComponent,
49
                this.isEmpty,
50
            ],
51
            ifToStr: [this.toStr],
52
        });
53
    }
54

55
    equalTo(other: BrsType): BrsBoolean {
56
        if (other.kind === ValueKind.String) {
12✔
57
            return BrsBoolean.from(other.value === this.intrinsic.value);
6✔
58
        }
59

60
        if (other instanceof RoString) {
6✔
61
            return BrsBoolean.from(other.intrinsic.value === this.intrinsic.value);
2✔
62
        }
63

64
        return BrsBoolean.False;
4✔
65
    }
66

67
    lessThan(other: BrsType): BrsBoolean {
68
        if (other.kind === ValueKind.String) {
3✔
69
            return this.unbox().lessThan(other);
1✔
70
        }
71

72
        if (other instanceof RoString) {
2!
73
            return this.unbox().lessThan(other.unbox());
×
74
        }
75

76
        return BrsBoolean.False;
2✔
77
    }
78

79
    greaterThan(other: BrsType): BrsBoolean {
80
        if (other.kind === ValueKind.String) {
3✔
81
            return this.unbox().greaterThan(other);
1✔
82
        }
83

84
        if (other instanceof RoString) {
2!
85
            return this.unbox().greaterThan(other.unbox());
×
86
        }
87

88
        return BrsBoolean.False;
2✔
89
    }
90

91
    unbox() {
92
        return this.intrinsic;
8✔
93
    }
94

95
    toString(parent?: BrsType): string {
96
        return this.intrinsic.toString(parent);
1✔
97
    }
98

99
    /**
100
     * Sets the string to the first len characters of s.
101
     * Note: this method is implemented in the ifString and ifStringOps interfaces
102
     */
103
    private setString = new Callable(
226✔
104
        "SetString",
105
        {
106
            signature: {
107
                args: [new StdlibArgument("s", ValueKind.String)],
108
                returns: ValueKind.Void,
109
            },
110
            impl: (_interpreter, s: BrsString) => {
111
                this.intrinsic = new BrsString(s.value);
4✔
112
                return BrsInvalid.Instance;
4✔
113
            },
114
        },
115
        {
116
            signature: {
117
                args: [
118
                    new StdlibArgument("s", ValueKind.String),
119
                    new StdlibArgument("len", ValueKind.Int32),
120
                ],
121
                returns: ValueKind.Void,
122
            },
123
            impl: (_interpreter, s: BrsString, len: Int32) => {
124
                this.intrinsic = new BrsString(s.value.substr(0, len.getValue()));
4✔
125
                return BrsInvalid.Instance;
4✔
126
            },
127
        }
128
    );
129

130
    private getString = new Callable("GetString", {
226✔
131
        signature: {
132
            args: [],
133
            returns: ValueKind.String,
134
        },
135
        impl: (_interpreter) => this.intrinsic,
4✔
136
    });
137

138
    // ---------- ifStringOps ----------
139
    /** Appends the first len characters of s to the end of the string. */
140
    private appendString = new Callable("AppendString", {
226✔
141
        signature: {
142
            args: [
143
                new StdlibArgument("s", ValueKind.String),
144
                new StdlibArgument("len", ValueKind.Int32),
145
            ],
146
            returns: ValueKind.Void,
147
        },
148
        impl: (_interpreter, s: BrsString, len: Int32) => {
149
            this.intrinsic = this.intrinsic.concat(
5✔
150
                new BrsString(s.value.substr(0, len.getValue()))
151
            );
152
            return BrsInvalid.Instance;
5✔
153
        },
154
    });
155

156
    /** Returns the number of characters in the string. */
157
    private len = new Callable("Len", {
226✔
158
        signature: {
159
            args: [],
160
            returns: ValueKind.Int32,
161
        },
162
        impl: (_interpreter) => {
163
            return new Int32(this.intrinsic.value.length);
2✔
164
        },
165
    });
166

167
    /** Returns a string consisting of the first len characters of the string. */
168
    private left = new Callable("Left", {
226✔
169
        signature: {
170
            args: [new StdlibArgument("len", ValueKind.Int32)],
171
            returns: ValueKind.String,
172
        },
173
        impl: (_interpreter, len: Int32) => {
174
            return new BrsString(this.intrinsic.value.substr(0, len.getValue()));
4✔
175
        },
176
    });
177

178
    /** Returns a string consisting of the last len characters of the string. */
179
    private right = new Callable("Right", {
226✔
180
        signature: {
181
            args: [new StdlibArgument("len", ValueKind.Int32)],
182
            returns: ValueKind.String,
183
        },
184
        impl: (_interpreter, len: Int32) => {
185
            let source = this.intrinsic.value;
4✔
186
            return new BrsString(source.substr(source.length - len.getValue()));
4✔
187
        },
188
    });
189

190
    private mid = new Callable(
226✔
191
        "Mid",
192
        /**
193
         * Returns a string consisting of the last characters of the string, starting at the
194
         * zero-based start_index.
195
         */
196
        {
197
            signature: {
198
                args: [new StdlibArgument("start_index", ValueKind.Int32)],
199
                returns: ValueKind.String,
200
            },
201
            impl: (_interpreter, startIndex: Int32) => {
202
                return new BrsString(this.intrinsic.value.substr(startIndex.getValue()));
4✔
203
            },
204
        },
205

206
        /**
207
         * Returns a string consisting of num_chars characters of the string, starting at the
208
         * zero-based start_index.
209
         */
210
        {
211
            signature: {
212
                args: [
213
                    new StdlibArgument("start_index", ValueKind.Int32),
214
                    new StdlibArgument("num_chars", ValueKind.Int32),
215
                ],
216
                returns: ValueKind.String,
217
            },
218
            impl: (_interpreter, startIndex: Int32, numChars: Int32) => {
219
                let source = this.intrinsic.value;
4✔
220
                return new BrsString(
4✔
221
                    this.intrinsic.value.substr(startIndex.getValue(), numChars.getValue())
222
                );
223
            },
224
        }
225
    );
226

227
    private instr = new Callable(
226✔
228
        "Instr",
229
        /** Returns the zero-based index of the first occurrence of substring in the string. */
230
        {
231
            signature: {
232
                args: [new StdlibArgument("substring", ValueKind.String)],
233
                returns: ValueKind.Int32,
234
            },
235
            impl: (_interpreter, substring: BrsString) => {
236
                return new Int32(this.intrinsic.value.indexOf(substring.value));
2✔
237
            },
238
        },
239
        /**
240
         * Returns the zero-based index of the first occurrence of substring in the string, starting
241
         * at the specified zero-based start_index.
242
         */
243
        {
244
            signature: {
245
                args: [
246
                    new StdlibArgument("start_index", ValueKind.Int32),
247
                    new StdlibArgument("substring", ValueKind.String),
248
                ],
249
                returns: ValueKind.Int32,
250
            },
251
            impl: (_interpreter, startIndex: Int32, substring: BrsString) => {
252
                return new Int32(
9✔
253
                    this.intrinsic.value.indexOf(substring.value, startIndex.getValue())
254
                );
255
            },
256
        }
257
    );
258

259
    /**
260
     * Returns a copy of the string with all instances of fromStr replaced with toStr. If fromStr is
261
     * empty the return value is the same as the source string.
262
     */
263
    private replace = new Callable("Replace", {
226✔
264
        signature: {
265
            args: [
266
                new StdlibArgument("from", ValueKind.String),
267
                new StdlibArgument("to", ValueKind.String),
268
            ],
269
            returns: ValueKind.String,
270
        },
271
        impl: (_interpreter, from: BrsString, to: BrsString) => {
272
            if (from.value === "") {
3✔
273
                return this.intrinsic;
1✔
274
            }
275

276
            // From Mozilla's guide to escaping regex:
277
            // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Escaping
278
            let escapedFrom = from.value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2✔
279
            return new BrsString(
2✔
280
                this.intrinsic.value.replace(new RegExp(escapedFrom, "g"), to.value)
281
            );
282
        },
283
    });
284

285
    /**
286
     * Returns the string with any leading and trailing whitespace characters (space, TAB, LF, CR,
287
     * VT, FF, NO-BREAK SPACE, et al) removed.
288
     */
289
    private trim = new Callable("Trim", {
226✔
290
        signature: {
291
            args: [],
292
            returns: ValueKind.String,
293
        },
294
        impl: (_interpreter) => {
295
            return new BrsString(this.intrinsic.value.trim());
2✔
296
        },
297
    });
298

299
    /** Returns the value of the string interpreted as a decimal number. */
300
    private toInt = new Callable("ToInt", {
226✔
301
        signature: {
302
            args: [],
303
            returns: ValueKind.Int32,
304
        },
305
        impl: (_interpreter) => {
306
            let int = Math.trunc(Number.parseFloat(this.intrinsic.value));
6✔
307

308
            if (Number.isNaN(int)) {
6✔
309
                // non-integers are returned as "0"
310
                return new Int32(0);
2✔
311
            }
312

313
            return new Int32(int);
4✔
314
        },
315
    });
316

317
    /** Returns the value of the string interpreted as a floating point number. */
318
    private toFloat = new Callable("ToFloat", {
226✔
319
        signature: {
320
            args: [],
321
            returns: ValueKind.Float,
322
        },
323
        impl: (_interpreter) => {
324
            let float = Number.parseFloat(this.intrinsic.value);
5✔
325

326
            if (Number.isNaN(float)) {
5✔
327
                // non-integers are returned as "0"
328
                return new Float(0);
2✔
329
            }
330

331
            return new Float(float);
3✔
332
        },
333
    });
334

335
    /**
336
     * Splits the string into separate substrings separated by a single delimiter character. Returns
337
     * an roList containing each of the substrings. The delimiters are not returned.
338
     */
339
    private tokenize = new Callable("Tokenize", {
226✔
340
        signature: {
341
            args: [new StdlibArgument("delim", ValueKind.String)],
342
            returns: ValueKind.Object,
343
        },
344
        impl: (_interpreter) => {
345
            _interpreter.stderr.write(
×
346
                "WARNING: tokenize not yet implemented, because it returns an RoList.  Returning `invalid`."
347
            );
348
            return BrsInvalid.Instance;
×
349
        },
350
    });
351

352
    /**
353
     * Splits the input string using the separator string as a delimiter, and returns an array of
354
     * the split token strings (not including the delimiter). An empty separator string indicates
355
     * to split the string by character.
356
     */
357
    private split = new Callable("Split", {
226✔
358
        signature: {
359
            args: [new StdlibArgument("separator", ValueKind.String)],
360
            returns: ValueKind.Object,
361
        },
362
        impl: (_interpreter, separator: BrsString) => {
363
            let parts;
364
            if (separator.value === "") {
5✔
365
                // split characters apart, preserving multi-character unicode structures
366
                parts = Array.from(this.intrinsic.value);
1✔
367
            } else {
368
                parts = this.intrinsic.value.split(separator.value);
4✔
369
            }
370

371
            return new RoArray(parts.map((part) => new BrsString(part)));
19✔
372
        },
373
    });
374

375
    /**
376
     * Returns the string with certain characters ("'<>&) replaced with the corresponding HTML
377
     * entity encoding.
378
     */
379
    private getEntityEncode = new Callable("GetEntityEncode", {
226✔
380
        signature: {
381
            args: [],
382
            returns: ValueKind.String,
383
        },
384
        impl: (_interpreter) => {
385
            return new BrsString(this.intrinsic.value.replace(/(['"<>&])/g, "\\$1"));
1✔
386
        },
387
    });
388

389
    /** URL encodes the specified string per RFC 3986 and returns the encoded string. */
390
    private escape = new Callable("Escape", {
226✔
391
        signature: {
392
            args: [],
393
            returns: ValueKind.String,
394
        },
395
        impl: (_interpreter) => {
396
            return new BrsString(
2✔
397
                // encoding courtesy of
398
                // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
399
                encodeURIComponent(this.intrinsic.value).replace(
400
                    /[!'()*]/g,
401
                    (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase()
2✔
402
                )
403
            );
404
        },
405
    });
406

407
    /** URL decodes the specified string per RFC 3986 and returns the decoded string. */
408
    private unescape = new Callable("Unescape", {
226✔
409
        signature: {
410
            args: [],
411
            returns: ValueKind.String,
412
        },
413
        impl: (_interpreter) => {
414
            return new BrsString(decodeURIComponent(this.intrinsic.value));
2✔
415
        },
416
    });
417

418
    /** returns whether string is empty or not */
419
    private isEmpty = new Callable("isEmpty", {
226✔
420
        signature: {
421
            args: [],
422
            returns: ValueKind.Boolean,
423
        },
424
        impl: (_interpreter) => {
425
            return BrsBoolean.from(this.intrinsic.value.length === 0);
4✔
426
        },
427
    });
428

429
    /**
430
     * Encode the specified string with escape sequences for reserved Uniform Resource Identifier
431
     * (URI) characters.
432
     */
433
    private encodeUri = new Callable("EncodeUri", {
226✔
434
        signature: {
435
            args: [],
436
            returns: ValueKind.String,
437
        },
438
        impl: (_interpreter) => {
439
            return new BrsString(encodeURI(this.intrinsic.value));
2✔
440
        },
441
    });
442

443
    /**
444
     * Decode the specified string with escape sequences for reserved Uniform Resource Identifier
445
     * (URI) characters.
446
     */
447
    private decodeUri = new Callable("DecodeUri", {
226✔
448
        signature: {
449
            args: [],
450
            returns: ValueKind.String,
451
        },
452
        impl: (_interpreter) => {
453
            return new BrsString(decodeURI(this.intrinsic.value));
2✔
454
        },
455
    });
456

457
    /**
458
     * Encode the specified string with escape sequences for reserved Uniform Resource Identifier
459
     * (URI) component characters.
460
     */
461
    private encodeUriComponent = new Callable("EncodeUriComponent", {
226✔
462
        signature: {
463
            args: [],
464
            returns: ValueKind.String,
465
        },
466
        impl: (_interpreter) => {
467
            return new BrsString(encodeURIComponent(this.intrinsic.value));
3✔
468
        },
469
    });
470

471
    private decodeUriComponent = new Callable("DecodeUriCOmponent", {
226✔
472
        signature: {
473
            args: [],
474
            returns: ValueKind.String,
475
        },
476
        impl: (_interpreter) => {
477
            return new BrsString(decodeURIComponent(this.intrinsic.value));
3✔
478
        },
479
    });
480

481
    private toStr = new Callable("toStr", {
226✔
482
        signature: {
483
            args: [],
484
            returns: ValueKind.String,
485
        },
486
        impl: (_interpreter) => this.intrinsic,
62✔
487
    });
488
}
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