Repository: lvcabral/brs-engine
Pull request #381 (2024-08-31 03:56:20)

build(deps-dev): bump webpack from 5.91.0 to 5.94.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.91.0 to 5.94.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/webpack/webpack/releases">webpack's releases</a>.</em></p>
<blockquote>
<h2>v5.94.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Added runtime condition for harmony reexport checked</li>
<li>Handle properly <code>data</code>/<code>http</code>/<code>https</code> protocols in source maps</li>
<li>Make <code>bigint</code> optimistic when browserslist not found</li>
<li>Move <code>@​types/eslint-scope</code> to dev deps</li>
<li>Related in asset stats is now always an array when no related found</li>
<li>Handle ASI for export declarations</li>
<li>Mangle destruction incorrect with export named default properly</li>
<li>Fixed unexpected asi generation with sequence expression</li>
<li>Fixed a lot of types</li>
</ul>
<h2>New Features</h2>
<ul>
<li>Added new external type &quot;module-import&quot;</li>
<li>Support <code>webpackIgnore</code> for <code>new URL()</code> construction</li>
<li>[CSS] <code>@import</code> pathinfo support</li>
</ul>
<h2>Security</h2>
<ul>
<li>Fixed DOM clobbering in auto public path</li>
</ul>
<h2>v5.93.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Generate correct relative path to runtime chunks</li>
<li>Makes <code>DefinePlugin</code> quieter under default log level</li>
<li>Fixed mangle destructuring default in namespace import</li>
<li>Fixed consumption of eager shared modules for module federation</li>
<li>Strip slash for pretty regexp</li>
<li>Calculate correct contenthash for CSS generator options</li>
</ul>
<h2>New Features</h2>
<ul>
<li>Added the <code>binary</code> generator option for asset modules to explicitly keep source maps produced by loaders</li>
<li>Added the <code>modern-module</code> library value for tree shakable output</li>
<li>Added the <code>overrideStrict</code> option to override strict or non-strict mode for javascript modules</li>
</ul>
<h2>v5.92.1</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Doesn't crash with an error when the css experiment is enabled and contenthash is used</li>
</ul>
<h2>v5.92.0</h2>
<h2>Bug Fixes</h2>
<ul>
<li>Correct tidle range's comutation for module federation</li>
<li>Consider runtime for pure expression dependency update hash</li>
<li>Return value in the <code>subtractRuntime</code> function for runtime logic</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/webpack/webpack/commit/eabf85d8580dfcb876b56957ba5488222a4f7873"><code>eabf85d</code></a> chore(release): 5.94.0</li>
<li><a href="https://github.com/webpack/webpack/commit/955e057abc6cc83cbc3fa1e1ef67a49758bf5a61"><code>955e057</code></a> security: fix DOM clobbering in auto public path</li>
<li><a href="https://github.com/webpack/webpack/commit/98223873625a029b9903d5ec6c0235b8f9fb5a66"><code>9822387</code></a> test: fix</li>
<li><a href="https://github.com/webpack/webpack/commit/cbb86ede32ab53d8eade6efee30da2463f0082ec"><code>cbb86ed</code></a> test: fix</li>
<li><a href="https://github.com/webpack/webpack/commit/5ac3d7f2cdce6da63a8dfb07e9dbbd95756bf7a2"><code>5ac3d7f</code></a> fix: unexpected asi generation with sequence expression</li>
<li><a href="https://github.com/webpack/webpack/commit/2411661bd1bedf1b2efc23c76d595c189425d39f"><code>2411661</code></a> security: fix DOM clobbering in auto public path</li>
<li><a href="https://github.com/webpack/webpack/commit/b8c03d47726a57e0dc6ba58b4f96f0e81b168268"><code>b8c03d4</code></a> fix: unexpected asi generation with sequence expression</li>
<li><a href="https://github.com/webpack/webpack/commit/f46a03ccbc2b96ff3552642fea0d7402f7880865"><code>f46a03c</code></a> revert: do not use heuristic fallback for &quot;module-import&quot;</li>
<li><a href="https://github.com/webpack/webpack/commit/60f189871a4cdc5d595663d6babadac74f2f6a7d"><code>60f1898</code></a> fix: do not use heuristic fallback for &quot;module-import&quot;</li>
<li><a href="https://github.com/webpack/webpack/commit/66306aa45659ef4e8dac8226798931c228fdb204"><code>66306aa</code></a> Revert &quot;fix: module-import get fallback from externalsPresets&quot;</li>
<li>Additional commits viewable in <a href="https://github.com/webpack/webpack/compare/v5.91.0...v5.94.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack&package-manager=npm_and_yarn&previous-version=5.91.0&new-version=5.94.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/lvcabral/brs-engine/network/alerts).

</details>

sonarcloud[bot]: ## [![Quality Gate Passed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-passed-20px.png 'Quality Gate Passed')](https://sonarcloud.io/dashboard?id=lvcabral_brs-emu&pullRequest=381) **Quality Gate passed**  
Issues  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 New issues](https://sonarcloud.io/project/issues?id=lvcabral_brs-emu&pullRequest=381&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/accepted-16px.png '') [0 Accepted issues](https://sonarcloud.io/project/issues?id=lvcabral_brs-emu&pullRequest=381&issueStatuses=ACCEPTED)

Measures  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=lvcabral_brs-emu&pullRequest=381&issueStatuses=OPEN,CONFIRMED&sinceLeakPeriod=true)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Coverage on New Code](https://sonarcloud.io/component_measures?id=lvcabral_brs-emu&pullRequest=381&metric=new_coverage&view=list)  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/passed-16px.png '') [0.0% Duplication on New Code](https://sonarcloud.io/component_measures?id=lvcabral_brs-emu&pullRequest=381&metric=new_duplicated_lines_density&view=list)  
  
[See analysis details on SonarCloud](https://sonarcloud.io/dashboard?id=lvcabral_brs-emu&pullRequest=381)



Patch:
diff --git a/package.json b/package.json
index 35518781..e2d47b99 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,7 @@
 {
   "name": "brs-engine",
   "version": "1.6.1",
+  "title": "BrightScript Simulation Engine",
   "description": "BrightScript Simulation Engine - Run Roku apps on Browsers and Node.js",
   "author": "Marcelo Lv Cabral <marcelo@lvcabral.com> (https://lvcabral.com/)",
   "license": "MIT",
@@ -132,7 +133,7 @@
     "rimraf": "^2.6.2",
     "ts-loader": "^9.5.1",
     "typescript": "^5.3.3",
-    "webpack": "^5.75.0",
+    "webpack": "^5.94.0",
     "webpack-bundle-analyzer": "^4.10.1",
     "webpack-cli": "^5.0.1",
     "webpack-dev-server": "^4.15.1",
diff --git a/src/api/index.ts b/src/api/index.ts
index d7c1f708..51440b8f 100644
--- a/src/api/index.ts
+++ b/src/api/index.ts
@@ -115,7 +115,7 @@ export function initialize(customDeviceInfo?: Partial<DeviceInfo>, options: any
         Object.assign(deviceData, customDeviceInfo);
     }
     const storage: Storage = window.localStorage;
-    let initMsg = `${packageInfo.description} - v${packageInfo.version}`;
+    let initMsg = `${packageInfo.title} - v${packageInfo.version}`;
     /// #if DEBUG
     initMsg += " - dev";
     /// #endif
diff --git a/src/cli/ecp.ts b/src/cli/ecp.ts
index 6d89accb..43824193 100644
--- a/src/cli/ecp.ts
+++ b/src/cli/ecp.ts
@@ -308,7 +308,7 @@ function genDeviceRootXml() {
     xmlDevice.ele("friendlyName", {}, device.friendlyName);
     xmlDevice.ele("manufacturer", {}, "Roku");
     xmlDevice.ele("manufacturerURL", {}, "https://www.roku.com/");
-    xmlDevice.ele("modelDescription", {}, packageInfo.description);
+    xmlDevice.ele("modelDescription", {}, packageInfo.title);
     xmlDevice.ele("modelName", {}, getModelName(device.deviceModel));
     xmlDevice.ele("modelNumber", {}, device.deviceModel);
     xmlDevice.ele("modelURL", {}, "https://www.lvcabral.com/brs/");
diff --git a/src/cli/index.ts b/src/cli/index.ts
index 9f875b00..2454494f 100644
--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -55,7 +55,7 @@ sharedArray.fill(-1);
  *
  */
 program
-    .description(`${packageInfo.description} CLI`)
+    .description(`${packageInfo.title} CLI`)
     .arguments(`brs-cli [brsFiles...]`)
     .option("-a, --ascii <columns>", "Enable ASCII screen mode with # of columns.")
     .option("-c, --colors <level>", "Define the console color level (0 to disable).", defaultLevel)
@@ -142,7 +142,7 @@ program
  *
  */
 function showAppTitle() {
-    const appTitle = `${packageInfo.description} CLI`;
+    const appTitle = `${packageInfo.title} CLI`;
     const appVersion = `v${packageInfo.version}`;
     /// #if DEBUG
     console.log(`\n${appTitle} [${chalk.cyanBright(appVersion)} ${chalk.gray("dev")}]\n`);
diff --git a/src/worker/brsTypes/components/BrsComponent.ts b/src/worker/brsTypes/components/BrsComponent.ts
index ff29d658..1bb59c8c 100644
--- a/src/worker/brsTypes/components/BrsComponent.ts
+++ b/src/worker/brsTypes/components/BrsComponent.ts
@@ -1,3 +1,4 @@
+import { bscs } from "../..";
 import { BrsType } from "..";
 import { BrsInvalid } from "../BrsType";
 import { Callable } from "../Callable";
@@ -66,11 +67,19 @@ export class BrsComponent {
 
     addReference() {
         this.references++;
+        if (this.references === 1) {
+            const count = bscs.get(this.componentName) ?? 0;
+            bscs.set(this.componentName, count + 1);
+        }
     }
 
     removeReference() {
         this.references--;
         if (this.references === 0 && !this.returnFlag) {
+            const count = bscs.get(this.componentName);
+            if (count) {
+                bscs.set(this.componentName, count - 1);
+            }
             this.dispose();
         }
     }
diff --git a/src/worker/brsTypes/components/BrsObjects.ts b/src/worker/brsTypes/components/BrsObjects.ts
index be9e0466..11258f06 100644
--- a/src/worker/brsTypes/components/BrsObjects.ts
+++ b/src/worker/brsTypes/components/BrsObjects.ts
@@ -50,66 +50,104 @@ import { RoInvalid } from "./RoInvalid";
 import { RoFunction } from "./RoFunction";
 import { Callable } from "../Callable";
 
-/** Map containing a list of BrightScript components that can be created. */
-export const BrsObjects = new Map<string, Function>([
-    ["roappmanager", (interpreter: Interpreter) => new RoAppManager()],
-    ["roassociativearray", (interpreter: Interpreter) => new RoAssociativeArray([])],
+// Class to define a case-insensitive map of BrightScript objects.
+class BrsObjectsMap {
+    private readonly map = new Map<string, { originalKey: string; value: Function }>();
+
+    constructor(entries: [string, Function][]) {
+        entries.forEach(([key, value]) => this.set(key, value));
+    }
+
+    get(key: string) {
+        const entry = this.map.get(key.toLowerCase());
+        return entry ? entry.value : undefined;
+    }
+
+    set(key: string, value: Function) {
+        return this.map.set(key.toLowerCase(), { originalKey: key, value: value });
+    }
+
+    has(key: string) {
+        return this.map.has(key.toLowerCase());
+    }
+
+    delete(key: string) {
+        return this.map.delete(key.toLowerCase());
+    }
+
+    clear() {
+        return this.map.clear();
+    }
+
+    values() {
+        return Array.from(this.map.values()).map((entry) => entry.value);
+    }
+
+    keys() {
+        return Array.from(this.map.values()).map((entry) => entry.originalKey);
+    }
+}
+
+/** Map containing a list of BrightScript components that can be created with CreateObject(). */
+export const BrsObjects = new BrsObjectsMap([
+    ["roAppManager", (interpreter: Interpreter) => new RoAppManager()],
+    ["roAssociativeArray", (interpreter: Interpreter) => new RoAssociativeArray([])],
     [
-        "roarray",
+        "roArray",
         (interpreter: Interpreter, capacity: Int32 | Float, resizable: BrsBoolean) =>
             new RoArray(capacity, resizable),
     ],
-    ["robytearray", (interpreter: Interpreter) => new RoByteArray()],
-    ["roevpcipher", (interpreter: Interpreter) => new RoEVPCipher()],
-    ["roevpdigest", (interpreter: Interpreter) => new RoEVPDigest()],
-    ["rohmac", (interpreter: Interpreter) => new RoHMAC()],
-    ["rodevicecrypto", (interpreter: Interpreter) => new RoDeviceCrypto(interpreter)],
-    ["rochannelstore", (interpreter: Interpreter) => new RoChannelStore()],
-    ["rodatetime", (interpreter: Interpreter) => new RoDateTime()],
-    ["rolist", (interpreter: Interpreter) => new RoList()],
-    ["rotimespan", (interpreter: Interpreter) => new RoTimespan()],
+    ["roByteArray", (interpreter: Interpreter) => new RoByteArray()],
+    ["roEVPCipher", (interpreter: Interpreter) => new RoEVPCipher()],
+    ["roEVPDigest", (interpreter: Interpreter) => new RoEVPDigest()],
+    ["roHMAC", (interpreter: Interpreter) => new RoHMAC()],
+    ["roDeviceCrypto", (interpreter: Interpreter) => new RoDeviceCrypto(interpreter)],
+    ["roChannelStore", (interpreter: Interpreter) => new RoChannelStore()],
+    ["roDateTime", (interpreter: Interpreter) => new RoDateTime()],
+    ["roList", (interpreter: Interpreter) => new RoList()],
+    ["roTimespan", (interpreter: Interpreter) => new RoTimespan()],
     [
-        "roregex",
+        "roRegex",
         (interpreter: Interpreter, expression: BrsString, flags: BrsString) =>
             new RoRegex(expression, flags),
     ],
-    ["rostring", (interpreter: Interpreter) => new RoString()],
-    ["roboolean", (interpreter: Interpreter, literal: BrsBoolean) => new RoBoolean(literal)],
-    ["rodouble", (interpreter: Interpreter, literal: Double) => new RoDouble(literal)],
-    ["rofloat", (interpreter: Interpreter, literal: Float) => new RoFloat(literal)],
-    ["roint", (interpreter: Interpreter, literal: Int32) => new RoInt(literal)],
-    ["rolonginteger", (interpreter: Interpreter, literal: Int64) => new RoLongInteger(literal)],
-    ["rofunction", (interpreter: Interpreter, sub: Callable) => new RoFunction(sub)],
-    ["ropath", (interpreter: Interpreter, path: BrsString) => new RoPath(path)],
+    ["roString", (interpreter: Interpreter) => new RoString()],
+    ["roBoolean", (interpreter: Interpreter, literal: BrsBoolean) => new RoBoolean(literal)],
+    ["roDouble", (interpreter: Interpreter, literal: Double) => new RoDouble(literal)],
+    ["roFloat", (interpreter: Interpreter, literal: Float) => new RoFloat(literal)],
+    ["roInt", (interpreter: Interpreter, literal: Int32) => new RoInt(literal)],
+    ["roLongInteger", (interpreter: Interpreter, literal: Int64) => new RoLongInteger(literal)],
+    ["roFunction", (interpreter: Interpreter, sub: Callable) => new RoFunction(sub)],
+    ["roPath", (interpreter: Interpreter, path: BrsString) => new RoPath(path)],
     [
-        "robitmap",
+        "roBitmap",
         (interpreter: Interpreter, param: BrsComponent) => createBitmap(interpreter, param),
     ],
-    ["roimagemetadata", (interpreter: Interpreter) => new RoImageMetadata()],
-    ["romessageport", (interpreter: Interpreter) => new RoMessagePort()],
-    ["roinput", (interpreter: Interpreter) => new RoInput()],
-    ["rofilesystem", (interpreter: Interpreter) => new RoFileSystem()],
-    ["rolocalization", (interpreter: Interpreter) => new RoLocalization(interpreter)],
-    ["rofontregistry", (interpreter: Interpreter) => new RoFontRegistry(interpreter)],
-    ["roregistry", (interpreter: Interpreter) => new RoRegistry()],
+    ["roImageMetadata", (interpreter: Interpreter) => new RoImageMetadata()],
+    ["roMessagePort", (interpreter: Interpreter) => new RoMessagePort()],
+    ["roInput", (interpreter: Interpreter) => new RoInput()],
+    ["roFileSystem", (interpreter: Interpreter) => new RoFileSystem()],
+    ["roLocalization", (interpreter: Interpreter) => new RoLocalization(interpreter)],
+    ["roFontRegistry", (interpreter: Interpreter) => new RoFontRegistry(interpreter)],
+    ["roRegistry", (interpreter: Interpreter) => new RoRegistry()],
     [
-        "roregistrysection",
+        "roRegistrySection",
         (interpreter: Interpreter, section: BrsString) =>
             new RoRegistrySection(interpreter, section),
     ],
-    ["roappinfo", (interpreter: Interpreter) => new RoAppInfo()],
-    ["rodeviceinfo", (interpreter: Interpreter) => new RoDeviceInfo()],
-    ["roappmemorymonitor", (interpreter: Interpreter) => new RoAppMemoryMonitor()],
-    ["roaudioplayer", (interpreter: Interpreter) => new RoAudioPlayer()],
+    ["roAppInfo", (interpreter: Interpreter) => new RoAppInfo()],
+    ["roDeviceInfo", (interpreter: Interpreter) => new RoDeviceInfo()],
+    ["roAppMemoryMonitor", (interpreter: Interpreter) => new RoAppMemoryMonitor()],
+    ["roAudioPlayer", (interpreter: Interpreter) => new RoAudioPlayer()],
     [
-        "roaudioresource",
+        "roAudioResource",
         (interpreter: Interpreter, name: BrsString) => createAudioResource(interpreter, name),
     ],
-    ["roaudiometadata", (interpreter: Interpreter) => new RoAudioMetadata()],
-    ["rovideoplayer", (interpreter: Interpreter) => new RoVideoPlayer()],
-    ["rocompositor", (interpreter: Interpreter) => new RoCompositor()],
+    ["roAudioMetadata", (interpreter: Interpreter) => new RoAudioMetadata()],
+    ["roVideoPlayer", (interpreter: Interpreter) => new RoVideoPlayer()],
+    ["roCompositor", (interpreter: Interpreter) => new RoCompositor()],
     [
-        "roregion",
+        "roRegion",
         (
             interpreter: Interpreter,
             bitmap: RoBitmap | RoScreen,
@@ -120,11 +158,11 @@ export const BrsObjects = new Map<string, Function>([
         ) => createRegion(bitmap, x, y, width, height),
     ],
     [
-        "roscreen",
+        "roScreen",
         (interpreter: Interpreter, dblbuffer?: BrsBoolean, width?: Int32, height?: Int32) =>
             new RoScreen(interpreter, dblbuffer, width, height),
     ],
-    ["roxmlelement", (interpreter: Interpreter) => new RoXMLElement()],
-    ["rourltransfer", (interpreter: Interpreter) => new RoURLTransfer(interpreter)],
-    ["roinvalid", (interpreter: Interpreter) => new RoInvalid()],
+    ["roXMLElement", (interpreter: Interpreter) => new RoXMLElement()],
+    ["roURLTransfer", (interpreter: Interpreter) => new RoURLTransfer(interpreter)],
+    ["roInvalid", (interpreter: Interpreter) => new RoInvalid()],
 ]);
diff --git a/src/worker/brsTypes/components/RoAssociativeArray.ts b/src/worker/brsTypes/components/RoAssociativeArray.ts
index f8f97b30..2e6706c8 100644
--- a/src/worker/brsTypes/components/RoAssociativeArray.ts
+++ b/src/worker/brsTypes/components/RoAssociativeArray.ts
@@ -188,7 +188,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     }
 
     /** Removes all elements from the associative array */
-    private clear = new Callable("clear", {
+    private readonly clear = new Callable("clear", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -205,7 +205,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Removes a given item from the associative array */
-    private delete = new Callable("delete", {
+    private readonly delete = new Callable("delete", {
         signature: {
             args: [new StdlibArgument("str", ValueKind.String)],
             returns: ValueKind.Boolean,
@@ -220,7 +220,9 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
             let lKey = str.value.toLowerCase();
             if (this.modeCaseSensitive) {
                 let keySet = this.keyMap.get(lKey);
-                keySet?.delete(key);
+                if (keySet && key) {
+                    keySet?.delete(key);
+                }
                 if (keySet?.size === 0) {
                     this.keyMap.delete(lKey);
                 }
@@ -235,7 +237,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     /** Given a key and value, adds an item to the associative array if it doesn't exist
      * Or replaces the value of a key that already exists in the associative array
      */
-    private addreplace = new Callable("addreplace", {
+    private readonly addreplace = new Callable("addreplace", {
         signature: {
             args: [
                 new StdlibArgument("key", ValueKind.String),
@@ -251,7 +253,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Returns the number of items in the associative array */
-    private count = new Callable("count", {
+    private readonly count = new Callable("count", {
         signature: {
             args: [],
             returns: ValueKind.Int32,
@@ -262,7 +264,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Returns a boolean indicating whether or not a given key exists in the associative array */
-    private doesexist = new Callable("doesexist", {
+    private readonly doesexist = new Callable("doesexist", {
         signature: {
             args: [new StdlibArgument("str", ValueKind.String)],
             returns: ValueKind.Boolean,
@@ -274,7 +276,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Appends a new associative array to another. If two keys are the same, the value of the original AA is replaced with the new one. */
-    private append = new Callable("append", {
+    private readonly append = new Callable("append", {
         signature: {
             args: [new StdlibArgument("obj", ValueKind.Object)],
             returns: ValueKind.Void,
@@ -297,7 +299,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Returns an array of keys from the associative array in lexicographical order */
-    private keys = new Callable("keys", {
+    private readonly keys = new Callable("keys", {
         signature: {
             args: [],
             returns: ValueKind.Object,
@@ -308,7 +310,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Returns an array of values from the associative array in lexicographical order */
-    private items = new Callable("items", {
+    private readonly items = new Callable("items", {
         signature: {
             args: [],
             returns: ValueKind.Object,
@@ -334,7 +336,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     /** Given a key, returns the value associated with that key.
      * This method is case insensitive either-or case sensitive, depends on whether `setModeCasesensitive` was called or not.
      */
-    private lookup = new Callable("lookup", {
+    private readonly lookup = new Callable("lookup", {
         signature: {
             args: [new StdlibArgument("key", ValueKind.String)],
             returns: ValueKind.Dynamic,
@@ -345,7 +347,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Given a key, returns the value associated with that key. This method always is case insensitive. */
-    private lookupCI = new Callable("lookupCI", {
+    private readonly lookupCI = new Callable("lookupCI", {
         signature: {
             args: [new StdlibArgument("key", ValueKind.String)],
             returns: ValueKind.Dynamic,
@@ -356,7 +358,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Changes the sensitive case method for lookups */
-    private setmodecasesensitive = new Callable("setModeCaseSensitive", {
+    private readonly setmodecasesensitive = new Callable("setModeCaseSensitive", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -373,7 +375,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     // not possible to reproduce for now, this implementation follows the same logic used in roArray.
 
     /** Returns true if enumeration contains no elements, false otherwise	 */
-    private isEmpty = new Callable("isEmpty", {
+    private readonly isEmpty = new Callable("isEmpty", {
         signature: {
             args: [],
             returns: ValueKind.Boolean,
@@ -384,7 +386,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Checks whether the current position is not past the end of the enumeration. */
-    private isNext = new Callable("isNext", {
+    private readonly isNext = new Callable("isNext", {
         signature: {
             args: [],
             returns: ValueKind.Boolean,
@@ -395,7 +397,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Resets the current position to the first element of the enumeration. */
-    private reset = new Callable("reset", {
+    private readonly reset = new Callable("reset", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -407,7 +409,7 @@ export class RoAssociativeArray extends BrsComponent implements BrsValue, BrsIte
     });
 
     /** Increments the position of an enumeration. */
-    private next = new Callable("next", {
+    private readonly next = new Callable("next", {
         signature: {
             args: [],
             returns: ValueKind.Dynamic,
diff --git a/src/worker/brsTypes/components/RoCompositor.ts b/src/worker/brsTypes/components/RoCompositor.ts
index 7b79b0d8..a88a076c 100644
--- a/src/worker/brsTypes/components/RoCompositor.ts
+++ b/src/worker/brsTypes/components/RoCompositor.ts
@@ -20,8 +20,8 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     readonly kind = ValueKind.Object;
     readonly sprites = new Map<number, RoSprite[]>();
     readonly animations = new Array<RoSprite>();
-    private canvas: BrsCanvas;
-    private context: BrsCanvasContext2D;
+    private readonly canvas: BrsCanvas;
+    private readonly context: BrsCanvasContext2D;
     private destBitmap?: RoBitmap | RoScreen | RoRegion;
     private rgbaBackground?: number;
     private spriteId: number;
@@ -30,8 +30,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     constructor() {
         super("roCompositor");
         this.canvas = createNewCanvas(10, 10);
-        let context = this.canvas.getContext("2d") as BrsCanvasContext2D;
-        this.context = context;
+        this.context = this.canvas.getContext("2d") as BrsCanvasContext2D;
         this.spriteId = 0;
         this.registerMethods({
             ifCompositor: [
@@ -198,7 +197,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
         return BrsBoolean.False;
     }
 
-    dispose(): void {
+    dispose() {
         this.destBitmap?.removeReference();
         this.sprites.forEach((layer) => {
             layer.forEach((sprite) => {
@@ -212,7 +211,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     }
 
     /** Set the destBitmap (roBitmap or roScreen) and the background color */
-    private setDrawTo = new Callable("setDrawTo", {
+    private readonly setDrawTo = new Callable("setDrawTo", {
         signature: {
             args: [
                 new StdlibArgument("destBitmap", ValueKind.Object),
@@ -237,7 +236,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     });
 
     /** Create a new sprite, using an roRegion to define the sprite's bitmap. */
-    private newSprite = new Callable("newSprite", {
+    private readonly newSprite = new Callable("newSprite", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -263,7 +262,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     });
 
     /** Create a new sprite that consists of a sequence of frames to be animated. The frames are defined by the regionArray which is an roArray of roRegions. */
-    private newAnimatedSprite = new Callable("newAnimatedSprite", {
+    private readonly newAnimatedSprite = new Callable("newAnimatedSprite", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -304,7 +303,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     });
 
     /** Duration is the number of ms since the last call. Moves all animated sprites. Sprites will not animate unless you call this function regularly  */
-    private animationTick = new Callable("animationTick", {
+    private readonly animationTick = new Callable("animationTick", {
         signature: {
             args: [new StdlibArgument("duration", ValueKind.Int32)],
             returns: ValueKind.Void,
@@ -318,7 +317,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     });
 
     /** Draw any dirty sprites (that is, whatever is new or has changed since the last Draw). */
-    private draw = new Callable("draw", {
+    private readonly draw = new Callable("draw", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -330,7 +329,7 @@ export class RoCompositor extends BrsComponent implements BrsValue {
     });
 
     /** Redraw all sprites even if not dirty. */
-    private drawAll = new Callable("drawAll", {
+    private readonly drawAll = new Callable("drawAll", {
         signature: {
             args: [],
             returns: ValueKind.Void,
diff --git a/src/worker/brsTypes/components/RoScreen.ts b/src/worker/brsTypes/components/RoScreen.ts
index b5a06c03..2a787936 100644
--- a/src/worker/brsTypes/components/RoScreen.ts
+++ b/src/worker/brsTypes/components/RoScreen.ts
@@ -22,19 +22,19 @@ import UPNG from "upng-js";
 
 export class RoScreen extends BrsComponent implements BrsValue {
     readonly kind = ValueKind.Object;
+    private readonly doubleBuffer: boolean;
+    private readonly width: number;
+    private readonly height: number;
+    private readonly maxMs: number;
+    private readonly disposeCanvas: boolean;
     private alphaEnable: boolean;
-    private doubleBuffer: boolean;
     private currentBuffer: number;
     private lastBuffer: number;
-    private width: number;
-    private height: number;
     private canvas: BrsCanvas[];
     private context: BrsCanvasContext2D[];
     private port?: RoMessagePort;
     private isDirty: boolean;
     private lastMessage: number;
-    private maxMs: number;
-    private disposeCanvas: boolean;
 
     // TODO: Check the Roku behavior on 4:3 resolutions in HD/FHD devices
     constructor(
@@ -178,7 +178,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
         this.isDirty = true;
     }
 
-    dispose(): void {
+    dispose() {
         this.port?.removeReference();
         if (this.disposeCanvas) {
             this.canvas.forEach((c) => releaseCanvas(c));
@@ -187,7 +187,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     // ifScreen ------------------------------------------------------------------------------------
 
     /** If the screen is double buffered, SwapBuffers swaps the back buffer with the front buffer */
-    private swapBuffers = new Callable("swapBuffers", {
+    private readonly swapBuffers = new Callable("swapBuffers", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -219,7 +219,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     // ifDraw2D  -----------------------------------------------------------------------------------
 
     /** Clear the bitmap, and fill with the specified RGBA color */
-    private clear = new Callable("clear", {
+    private readonly clear = new Callable("clear", {
         signature: {
             args: [new StdlibArgument("rgba", ValueKind.Int32)],
             returns: ValueKind.Void,
@@ -230,7 +230,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draw the source object, where src is an roBitmap or an roRegion object, at position x,y */
-    private drawObject = new Callable("drawObject", {
+    private readonly drawObject = new Callable("drawObject", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -255,7 +255,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draw the source object at position x,y rotated by angle theta degrees. */
-    private drawRotatedObject = new Callable("drawRotatedObject", {
+    private readonly drawRotatedObject = new Callable("drawRotatedObject", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -280,7 +280,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draw the source object, at position x,y, scaled horizontally by scaleX and vertically by scaleY. */
-    private drawScaledObject = new Callable("drawScaledObject", {
+    private readonly drawScaledObject = new Callable("drawScaledObject", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -316,7 +316,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draw the source object, at position x,y, rotated by theta and scaled horizontally by scaleX and vertically by scaleY. */
-    private drawTransformedObject = new Callable("drawTransformedObject", {
+    private readonly drawTransformedObject = new Callable("drawTransformedObject", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -354,7 +354,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draw a line from (xStart, yStart) to (xEnd, yEnd) with RGBA color */
-    private drawLine = new Callable("drawLine", {
+    private readonly drawLine = new Callable("drawLine", {
         signature: {
             args: [
                 new StdlibArgument("xStart", ValueKind.Int32),
@@ -385,7 +385,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draws a point at (x,y) with the given size and RGBA color */
-    private drawPoint = new Callable("drawPoint", {
+    private readonly drawPoint = new Callable("drawPoint", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -405,7 +405,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Fill the specified rectangle from left (x), top (y) to right (x + width), bottom (y + height) with the RGBA color */
-    private drawRect = new Callable("drawRect", {
+    private readonly drawRect = new Callable("drawRect", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -429,7 +429,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Draws the text at position (x,y) using the specified RGBA color and roFont font object. */
-    private drawText = new Callable("drawText", {
+    private readonly drawText = new Callable("drawText", {
         signature: {
             args: [
                 new StdlibArgument("text", ValueKind.String),
@@ -452,7 +452,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Realize the bitmap by finishing all queued draw calls. */
-    private finish = new Callable("finish", {
+    private readonly finish = new Callable("finish", {
         signature: {
             args: [],
             returns: ValueKind.Void,
@@ -467,7 +467,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Returns true if alpha blending is enabled */
-    private getAlphaEnable = new Callable("getAlphaEnable", {
+    private readonly getAlphaEnable = new Callable("getAlphaEnable", {
         signature: {
             args: [],
             returns: ValueKind.Boolean,
@@ -478,7 +478,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** If enable is true, do alpha blending when this bitmap is the destination */
-    private setAlphaEnable = new Callable("setAlphaEnable", {
+    private readonly setAlphaEnable = new Callable("setAlphaEnable", {
         signature: {
             args: [new StdlibArgument("alphaEnable", ValueKind.Boolean)],
             returns: ValueKind.Void,
@@ -489,7 +489,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Return the width of the screen/bitmap/region. */
-    private getWidth = new Callable("getWidth", {
+    private readonly getWidth = new Callable("getWidth", {
         signature: {
             args: [],
             returns: ValueKind.Int32,
@@ -500,7 +500,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Return the height of the screen/bitmap/region. */
-    private getHeight = new Callable("getHeight", {
+    private readonly getHeight = new Callable("getHeight", {
         signature: {
             args: [],
             returns: ValueKind.Int32,
@@ -511,7 +511,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Returns an roByteArray representing the RGBA pixel values for the rectangle described by the parameters. */
-    private getByteArray = new Callable("getByteArray", {
+    private readonly getByteArray = new Callable("getByteArray", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -534,7 +534,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Returns an roByteArray object containing PNG image data for the specified area of the bitmap. */
-    private getPng = new Callable("getPng", {
+    private readonly getPng = new Callable("getPng", {
         signature: {
             args: [
                 new StdlibArgument("x", ValueKind.Int32),
@@ -560,7 +560,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     // ifGetMessagePort ----------------------------------------------------------------------------------
 
     /** Returns the message port (if any) currently associated with the object */
-    private getMessagePort = new Callable("getMessagePort", {
+    private readonly getMessagePort = new Callable("getMessagePort", {
         signature: {
             args: [],
             returns: ValueKind.Object,
@@ -573,7 +573,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     // ifSetMessagePort ----------------------------------------------------------------------------------
 
     /** Sets the roMessagePort to be used for all events from the screen */
-    private setMessagePort = new Callable("setMessagePort", {
+    private readonly setMessagePort = new Callable("setMessagePort", {
         signature: {
             args: [new StdlibArgument("port", ValueKind.Dynamic)],
             returns: ValueKind.Void,
@@ -588,7 +588,7 @@ export class RoScreen extends BrsComponent implements BrsValue {
     });
 
     /** Sets the roMessagePort to be used for all events from the screen */
-    private setPort = new Callable("setPort", {
+    private readonly setPort = new Callable("setPort", {
         signature: {
             args: [new StdlibArgument("port", ValueKind.Dynamic)],
             returns: ValueKind.Void,
diff --git a/src/worker/common.ts b/src/worker/common.ts
index 780a1166..1ed0b666 100644
--- a/src/worker/common.ts
+++ b/src/worker/common.ts
@@ -158,29 +158,32 @@ export enum RemoteType {
     ECP = 40, // External Control Protocol
     RMOB = 50, // Roku Mobile App (ECP2)
 }
-// Other valid remote codes:
+// Other RBI valid remote codes:
 // BT - Bluetooth
 // CEC - Consumer Electronics Control
 // MHL - Mobile High-Definition Link
-// FP - Front Panel (for on device controls)
+// FP - Front Panel (for on-device controls)
 
 // Debug prompt
 export const debugPrompt = "Brightscript Debugger> ";
 
 // Debug commands enumerator
 export enum DebugCommand {
+    BSCS,
     BT,
+    CLASSES,
     CONT,
     EXIT,
+    EXPR,
     HELP,
     LAST,
     LIST,
     NEXT,
+    STATS,
     STEP,
     THREAD,
     THREADS,
     VAR,
-    EXPR,
     BREAK,
     PAUSE,
 }
@@ -267,3 +270,12 @@ export function getExitReason(value: string): AppExitReason {
 export function numberToHex(value: number, pad: string = ""): string {
     return (value >>> 0).toString(16).padStart(8, pad);
 }
+
+// This function takes a text file content as a string and returns an array of lines
+export function parseTextFile(content?: string): string[] {
+    let lines: string[] = [];
+    if (content) {
+        lines = content.trimEnd().split("\n");
+    }
+    return lines;
+}
diff --git a/src/worker/index.ts b/src/worker/index.ts
index d3a14089..29cd4440 100644
--- a/src/worker/index.ts
+++ b/src/worker/index.ts
@@ -18,7 +18,7 @@ import {
     parseManifest,
 } from "./common";
 import { FileSystem } from "./interpreter/FileSystem";
-import { Lexer, Token } from "./lexer";
+import { Lexeme, Lexer, Token } from "./lexer";
 import { Parser, Stmt } from "./parser";
 import * as PP from "./preprocessor";
 import * as BrsError from "./Error";
@@ -43,6 +43,8 @@ export { Preprocessor } from "./preprocessor/Preprocessor";
 export { Interpreter } from "./interpreter";
 export { Environment, Scope } from "./interpreter/Environment";
 export const shared = new Map<string, Int32Array>();
+export const bscs = new Map<string, number>();
+export const stats = new Map<Lexeme, number>();
 
 const algorithm = "aes-256-ctr";
 
@@ -143,7 +145,7 @@ export function executeLine(contents: string, interpreter: Interpreter) {
     }
     try {
         const results = interpreter.exec(parseResults.statements);
-        results.map((result) => {
+        results.forEach((result) => {
             if (result !== BrsTypes.BrsInvalid.Instance) {
                 postMessage(`print,${result.toString()}`);
             }
@@ -169,7 +171,7 @@ export function createPayload(files: string[], customDeviceData?: Partial<Device
     let manifest: Map<string, string> | undefined;
 
     let id = 0;
-    files.map((filePath) => {
+    files.forEach((filePath) => {
         const fileName = path.basename(filePath) ?? filePath;
         const fileExt = fileName.split(".").pop();
         if (fileExt?.toLowerCase() === "brs") {
@@ -270,6 +272,8 @@ export function executeFile(
         },
         ...customOptions,
     };
+    bscs.clear();
+    stats.clear();
     const interpreter = new Interpreter(options);
     // Input Parameters / Deep Link
     const inputArray = setupInputArray(payload.input);
@@ -594,12 +598,16 @@ export function lexParseSync(
     if (password.length === 0) {
         lib.forEach((value: string, key: string) => {
             if (value !== "") {
+                sourceMap.set(key, value);
                 const libScan = lexer.scan(value, key);
                 const libParse = parser.parse(libScan.tokens);
                 allStatements.push(...libParse.statements);
             }
         });
     }
+    parser.stats.forEach((count, lexeme) => {
+        stats.set(lexeme, count.size);
+    });
     return {
         exitReason: exitReason,
         tokens: tokens,
diff --git a/src/worker/interpreter/Environment.ts b/src/worker/interpreter/Environment.ts
index 928ff050..53d96596 100644
--- a/src/worker/interpreter/Environment.ts
+++ b/src/worker/interpreter/Environment.ts
@@ -20,13 +20,14 @@ export class NotFound extends Error {
 
 /** Holds a set of values in multiple scopes and provides access operations to them. */
 export class Environment {
-    constructor(rootM?: RoAssociativeArray) {
+    constructor(mPointer: RoAssociativeArray, rootM?: RoAssociativeArray) {
         if (!rootM) {
-            this.rootM = this.mPointer;
+            this.rootM = mPointer;
         } else {
             this.rootM = rootM;
         }
-        this.mPointer.addReference();
+        mPointer.addReference();
+        this.mPointer = mPointer;
     }
     /**
      * Functions that are always accessible.
@@ -44,7 +45,7 @@ export class Environment {
      */
     private function = new Map<string, BrsType>();
     /** The BrightScript `m` pointer, analogous to JavaScript's `this` pointer. */
-    private mPointer = new RoAssociativeArray([]);
+    private mPointer: RoAssociativeArray;
     private rootM: RoAssociativeArray;
 
     /** Properties to support GOTO statement */
@@ -241,10 +242,9 @@ export class Environment {
      * @returns a copy of this environment but with no function-scoped values.
      */
     public createSubEnvironment(): Environment {
-        let newEnvironment = new Environment(this.rootM);
+        let newEnvironment = new Environment(this.mPointer, this.rootM);
         newEnvironment.global = this.global;
         newEnvironment.module = this.module;
-        newEnvironment.setM(this.mPointer);
 
         return newEnvironment;
     }
diff --git a/src/worker/interpreter/MicroDebugger.ts b/src/worker/interpreter/MicroDebugger.ts
index d054ab1a..55730c87 100644
--- a/src/worker/interpreter/MicroDebugger.ts
+++ b/src/worker/interpreter/MicroDebugger.ts
@@ -1,7 +1,10 @@
+import { bscs } from "..";
 import { Interpreter, TracePoint } from ".";
 import { Lexer, Location } from "../lexer";
 import { Parser } from "../parser";
 import { BrsError } from "../Error";
+import { BrsObjects } from "../brsTypes";
+
 import {
     Statement,
     Assignment,
@@ -16,7 +19,7 @@ import {
     For,
     While,
 } from "../parser/Statement";
-import { DataType, DebugCommand, debugPrompt, numberToHex } from "../common";
+import { DataType, DebugCommand, debugPrompt, numberToHex, parseTextFile } from "../common";
 /// #if !BROWSER
 import readline from "readline-sync";
 readline.setDefaultOptions({ prompt: debugPrompt });
@@ -32,9 +35,6 @@ export function runDebugger(
     errMessage?: string,
     errNumber?: number
 ) {
-    // TODO:
-    // - Implement step over and step out
-    // - Implement classes, bsc(s) and stats
     const lastLines = parseTextFile(interpreter.sourceMap.get(lastLoc.file));
     let debugMsg = "BrightScript Micro Debugger.\r\n";
     let lastLine: number = lastLoc.start.line;
@@ -213,6 +213,30 @@ function debugHandleCommand(
             debugMsg += ` 0*   ${interpreter.formatLocation(currLoc).padEnd(40)}${lineText}\r\n`;
             debugMsg += "  *selected\r\n";
             break;
+        case DebugCommand.BSCS: {
+            const objList = Array.from(bscs.keys()).sort();
+            let total = 0;
+            debugMsg = "print,";
+            for (const obj of objList) {
+                const value = bscs.get(obj);
+                if (value) {
+                    debugMsg += `${obj}: `.padStart(27);
+                    debugMsg += `count=${value}\r\n`;
+                    total += value;
+                }
+            }
+            debugMsg += `Total # components: `.padStart(27);
+            debugMsg += `${total}\r\n`;
+            break;
+        }
+        case DebugCommand.STATS:
+            debugMsg = `print,${interpreter.formatStats()}`;
+            break;
+        case DebugCommand.CLASSES: {
+            const classList = BrsObjects.keys().sort();
+            debugMsg = `print,${classList.join("\r\n")}\r\n`;
+            break;
+        }
         case DebugCommand.VAR:
             debugMsg = `print,${interpreter.formatLocalVariables()}`;
             break;
@@ -251,7 +275,7 @@ function debugHelp(): string {
     debugMsg += "Command List:\r\n";
     debugMsg += "   bt              Print backtrace of call function context frames\r\n";
     // debugMsg += "   brkd            Break on BrightScript diagnostics\r\n"
-    // debugMsg += "   classes         List public classes\r\n"
+    debugMsg += "   classes         List public classes\r\n";
     debugMsg += "   cont|c          Continue script execution\r\n";
     // debugMsg += "   down|d          Move down the function context chain one\r\n"
     debugMsg += "   exit|quit|q     Exit shell\r\n";
@@ -260,8 +284,8 @@ function debugHelp(): string {
     debugMsg += "   next|n          Show the next line to execute\r\n";
     debugMsg += "   list            List current function\r\n";
     // debugMsg += "   bsc             List BrightScript Component instances\r\n"
-    // debugMsg += "   bscs            Summarize BrightScript Component instances\r\n"
-    // debugMsg += "   stats           Shows statistics\r\n"
+    debugMsg += "   bscs            Summarize BrightScript Component instances\r\n";
+    debugMsg += "   stats           Shows statistics\r\n";
     debugMsg += "   step|s|t        Step one program statement\r\n";
     debugMsg += "   thread|th       Show selected thread\r\n";
     // debugMsg += "   thread|th <id>  Select one thread for inspection\r\n"
@@ -277,20 +301,13 @@ function debugHelp(): string {
     return debugMsg;
 }
 
-// This function takes a text file content as a string and returns an array of lines
-function parseTextFile(content?: string): string[] {
-    let lines: string[] = [];
-    if (content) {
-        lines = content.split("\n");
-    }
-    return lines;
-}
-
 function parseCommand(command: string): any {
     let result = { cmd: DebugCommand.EXPR, expr: "" };
     if (command?.length) {
         const commandsMap = new Map([
+            ["bscs", DebugCommand.BSCS],
             ["bt", DebugCommand.BT],
+            ["classes", DebugCommand.CLASSES],
             ["cont", DebugCommand.CONT],
             ["c", DebugCommand.CONT],
             ["exit", DebugCommand.EXIT],
@@ -305,6 +322,7 @@ function parseCommand(command: string): any {
             ["over", DebugCommand.STEP],
             ["out", DebugCommand.STEP],
             ["step", DebugCommand.STEP],
+            ["stats", DebugCommand.STATS],
             ["s", DebugCommand.STEP],
             ["t", DebugCommand.STEP],
             ["thread", DebugCommand.THREAD],
diff --git a/src/worker/interpreter/index.ts b/src/worker/interpreter/index.ts
index 13e1081c..21fc2ac5 100644
--- a/src/worker/interpreter/index.ts
+++ b/src/worker/interpreter/index.ts
@@ -35,7 +35,7 @@ import {
     Signature,
 } from "../brsTypes";
 import { tryCoerce } from "../brsTypes/coercion";
-import { shared } from "..";
+import { shared, stats } from "..";
 import { Lexeme } from "../lexer";
 import { isToken, Location } from "../lexer/Token";
 import { Expr, Stmt } from "../parser";
@@ -51,7 +51,14 @@ import { toCallable } from "./BrsFunction";
 import { BlockEnd, GotoLabel } from "../parser/Statement";
 import { FileSystem } from "./FileSystem";
 import { runDebugger } from "./MicroDebugger";
-import { DataType, DebugCommand, dataBufferIndex, defaultDeviceInfo, numberToHex } from "../common";
+import {
+    DataType,
+    DebugCommand,
+    dataBufferIndex,
+    defaultDeviceInfo,
+    numberToHex,
+    parseTextFile,
+} from "../common";
 
 /** The set of options used to configure an interpreter's execution. */
 export interface ExecutionOptions {
@@ -85,11 +92,11 @@ export interface TracePoint {
 }
 
 export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType> {
-    private _environment = new Environment();
+    private readonly _startTime = Date.now();
+    private readonly _stack = new Array<TracePoint>();
+    private _environment: Environment;
     private _sourceMap = new Map<string, string>();
-    private _stack = new Array<TracePoint>();
     private _tryMode = false;
-    private _startTime = Date.now();
     private _dotLevel = 0;
     private _singleKeyEvents = true; // Default Roku behavior is `true`
 
@@ -203,6 +210,7 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
      * @param options configuration for the execution
      */
     constructor(options?: Partial<ExecutionOptions>) {
+        this._environment = new Environment(new RoAssociativeArray([]));
         Object.assign(this.options, options);
         this.stdout = new OutputProxy(this.options.stdout, this.options.post);
         this.stderr = new OutputProxy(this.options.stderr, this.options.post);
@@ -1946,7 +1954,10 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
                             name: new BrsString("filename"),
                             value: new BrsString(loc?.file ?? "()"),
                         },
-                        { name: new BrsString("function"), value: new BrsString(funcSignature) },
+                        {
+                            name: new BrsString("function"),
+                            value: new BrsString(funcSignature),
+                        },
                         { name: new BrsString("line_number"), value: new Int32(line) },
                     ])
                 );
@@ -2020,6 +2031,27 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
         return formattedLocation;
     }
 
+    /**
+     * Method to return the statistics of the interpreter for the REPL and Micro Debugger
+     * @returns a string representation of the interpreter statistics
+     */
+    formatStats(): string {
+        let debugMsg = `Sub Context Data:\r\n`;
+        let varCount = this.environment.getList(Scope.Function).size + 2;
+        debugMsg += `  Variables:      ${varCount}\r\n`;
+        let lineCount = 0;
+        this.sourceMap.forEach((lines) => {
+            lineCount += parseTextFile(lines).length;
+        });
+        debugMsg += "Module Constant Table Sizes:\r\n";
+        debugMsg += `  Source Lns:     ${lineCount}\r\n`;
+        stats.forEach((count, lexeme) => {
+            const name = Lexeme[lexeme] + ":";
+            debugMsg += `  ${name.padEnd(15)} ${count}\r\n`;
+        });
+        return debugMsg;
+    }
+
     /**
      * Method to return the current channel formatted version
      * @returns the current channel version
diff --git a/src/worker/parser/Parser.ts b/src/worker/parser/Parser.ts
index bde86108..fe788c55 100644
--- a/src/worker/parser/Parser.ts
+++ b/src/worker/parser/Parser.ts
@@ -15,6 +15,7 @@ import {
     ValueKind,
     Argument,
     StdlibArgument,
+    BrsType,
 } from "../brsTypes";
 import { RuntimeErrorDetail } from "../Error";
 
@@ -166,12 +167,21 @@ interface ParseResults {
 export class Parser {
     /** Allows consumers to observe errors as they're detected. */
     readonly events = new EventEmitter();
+    readonly stats = new Map<Lexeme, Set<any>>();
 
     constructor() {
         bsLibraries.set("v30/bslCore.brs", false);
         bsLibraries.set("v30/bslDefender.brs", false);
         bsLibraries.set("Roku_Ads.brs", false);
+        this.stats.set(Lexeme.Integer, new Set());
+        this.stats.set(Lexeme.String, new Set());
+        this.stats.set(Lexeme.Float, new Set());
+        this.stats.set(Lexeme.Double, new Set());
+        this.stats.set(Lexeme.LongInteger, new Set());
+        this.stats.set(Lexeme.Function, new Set());
+        this.stats.set(Lexeme.Identifier, new Set());
     }
+
     /**
      * A convenience function, equivalent to `new Parser().parse(toParse)`, that parses an array of
      * `Token`s into an abstract syntax tree that can be executed with the `Interpreter`.
@@ -212,6 +222,7 @@ export class Parser {
      *          program
      */
     parse(toParse: readonly Token[]): ParseResults {
+        const self = this;
         let current = 0;
         let tokens = toParse;
         let parsingTryBlock = false;
@@ -363,6 +374,7 @@ export class Parser {
                     returnType = ValueKind.Dynamic;
                 }
 
+                let funcName = "(anonymous)";
                 if (isAnonymous) {
                     leftParen = consume(
                         `Expected '(' after ${functionType.text}`,
@@ -387,6 +399,7 @@ export class Parser {
                             `Function name '${name.text}' cannot end with type designator '${lastChar}'`
                         );
                     }
+                    funcName = name.text;
                 }
 
                 let args: Argument[] = [];
@@ -418,7 +431,8 @@ export class Parser {
                             `Function return type '${typeString}' is invalid`
                         );
                     }
-
+                    // RBI count the literal integer 0 when a function has a return type
+                    countLiteral(Lexeme.Integer, new Int32(0));
                     returnType = maybeReturnType;
                 }
 
@@ -470,6 +484,7 @@ export class Parser {
                     startingKeyword,
                     endingKeyword
                 );
+                countFunction(funcName);
 
                 if (isAnonymous) {
                     return func;
@@ -744,6 +759,10 @@ export class Parser {
                 // BrightScript for/to/step loops default to a step of 1 if no `step` is provided
                 increment = new Expr.Literal(new Int32(1), peek().location);
             }
+            // RBI count the step as literal integer even when omitted
+            if (increment instanceof Expr.Literal) {
+                countLiteral(Lexeme.Integer, increment.value);
+            }
 
             let maybeBody = block(Lexeme.EndFor, Lexeme.Next);
             if (!maybeBody) {
@@ -1577,6 +1596,7 @@ export class Parser {
 
                         // force it into an identifier so the AST makes some sense
                         name.kind = Lexeme.Identifier;
+                        countIdentifier(name.text);
                         expr = new Expr.DottedGet(expr, name as Identifier);
                     }
                 } else {
@@ -1611,6 +1631,19 @@ export class Parser {
                 Lexeme.RightParen
             );
 
+            if (
+                callee instanceof Expr.Variable &&
+                callee.name.text.toLowerCase() === "createobject"
+            ) {
+                // RBI doesn't count the object name, but only if a single argument is passed
+                if (args.length === 1 && args[0] instanceof Expr.Literal) {
+                    const name = args[0].value.toString();
+                    const strings = self.stats.get(Lexeme.String);
+                    if (strings && strings.has(name) && Array.from(strings).pop() === name) {
+                        strings.delete(name);
+                    }
+                }
+            }
             return new Expr.Call(callee, closingParen, args);
         }
 
@@ -1629,6 +1662,7 @@ export class Parser {
                     Lexeme.Double,
                     Lexeme.String
                 ):
+                    countLiteral(Lexeme[previous().kind], previous().literal);
                     return new Expr.Literal(previous().literal!, previous().location);
                 case match(Lexeme.Identifier):
                     return new Expr.Variable(previous() as Identifier);
@@ -1668,6 +1702,12 @@ export class Parser {
 
                     let closingSquare = previous();
 
+                    // When we initialize a literal array the capacity and object name are counted
+                    countLiteral(Lexeme.Integer, new Int32(10));
+                    countLiteral(Lexeme.String, new BrsString("roArray"));
+                    if (elements.length) {
+                        countIdentifier("");
+                    }
                     return new Expr.ArrayLiteral(elements, openingSquare, closingSquare);
                 }
                 case match(Lexeme.LeftBrace): {
@@ -1721,6 +1761,12 @@ export class Parser {
                             "Unmatched '{' - expected '}' after associative array literal",
                             Lexeme.RightBrace
                         );
+                        if (members.length) {
+                            countIdentifier("");
+                            members.forEach((member) => {
+                                countIdentifier(member.name.value);
+                            });
+                        }
                     }
 
                     let closingBrace = previous();
@@ -1886,5 +1932,26 @@ export class Parser {
                 advance();
             }
         }
+
+        function countFunction(name: string) {
+            const funcs = self.stats.get(Lexeme.Function);
+            if (funcs) {
+                funcs.add(`${name.toLowerCase()}(${funcs.size})`);
+            }
+        }
+
+        function countLiteral(lexeme: Lexeme, value?: BrsType) {
+            const literals = self.stats.get(lexeme);
+            if (literals && value && !literals.has(value.toString())) {
+                literals.add(value.toString());
+            }
+        }
+
+        function countIdentifier(name: string) {
+            const identifiers = self.stats.get(Lexeme.Identifier);
+            if (identifiers && !identifiers?.has(name.toLowerCase())) {
+                identifiers.add(name.toLowerCase());
+            }
+        }
     }
 }
diff --git a/src/worker/stdlib/Json.ts b/src/worker/stdlib/Json.ts
index 5595dc98..5d01cea8 100644
--- a/src/worker/stdlib/Json.ts
+++ b/src/worker/stdlib/Json.ts
@@ -28,6 +28,7 @@ function brsValueOf(x: any): BrsType {
     if (x === null) {
         return BrsInvalid.Instance;
     }
+    const maxInt = 0x80000000;
     let t: string = typeof x;
     switch (t) {
         case "boolean":
@@ -36,7 +37,7 @@ function brsValueOf(x: any): BrsType {
             return new BrsString(x);
         case "number":
             if (Number.isInteger(x)) {
-                return x >= -2_147_483_648 && x <= 2_147_483_647 ? new Int32(x) : new Int64(x);
+                return x >= -maxInt && x < maxInt ? new Int32(x) : new Int64(x);
             }
             return new Float(x);
         case "object":
@@ -175,15 +176,11 @@ export const ParseJson = new Callable("ParseJson", {
     impl: (interpreter: Interpreter, jsonString: BrsString) => {
         try {
             let s: string = jsonString.toString().trim();
-
             if (s === "") {
                 throw new Error("Data is empty");
             }
-
             return brsValueOf(JSON.parse(s));
         } catch (err: any) {
-            // example RBI error:
-            // "BRIGHTSCRIPT: ERROR: ParseJSON: Unknown identifier 'x': pkg:/source/main.brs(25)"
             logBrsErr(interpreter, "ParseJSON", err);
             return BrsInvalid.Instance;
         }
diff --git a/yarn.lock b/yarn.lock
index 81c36dfe..050c4c14 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -777,23 +777,7 @@
   dependencies:
     "@types/node" "*"
 
-"@types/eslint-scope@^3.7.3":
-  version "3.7.7"
-  resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5"
-  integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==
-  dependencies:
-    "@types/eslint" "*"
-    "@types/estree" "*"
-
-"@types/eslint@*":
-  version "8.56.10"
-  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.10.tgz#eb2370a73bf04a901eeba8f22595c7ee0f7eb58d"
-  integrity sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==
-  dependencies:
-    "@types/estree" "*"
-    "@types/json-schema" "*"
-
-"@types/estree@*", "@types/estree@^1.0.5":
+"@types/estree@^1.0.5":
   version "1.0.5"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
   integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
@@ -869,7 +853,7 @@
   dependencies:
     "@types/istanbul-lib-report" "*"
 
-"@types/json-schema@*", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
+"@types/json-schema@^7.0.15", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
   version "7.0.15"
   resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
   integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -1322,10 +1306,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
     mime-types "~2.1.34"
     negotiator "0.6.3"
 
-acorn-import-assertions@^1.9.0:
-  version "1.9.0"
-  resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
-  integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+acorn-import-attributes@^1.9.5:
+  version "1.9.5"
+  resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef"
+  integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==
 
 acorn-jsx@^5.3.2:
   version "5.3.2"
@@ -2690,10 +2674,10 @@ encodeurl@~1.0.2:
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
-enhanced-resolve@^5.0.0, enhanced-resolve@^5.16.0:
-  version "5.16.0"
-  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.0.tgz#65ec88778083056cb32487faa9aef82ed0864787"
-  integrity sha512-O+QWCviPNSSLAD9Ucn8Awv+poAkqn3T1XY5/N7kR7rQO9yfSGWkYZDwpJ+iKF7B8rxaQKWngSqACpgzeapSyoA==
+enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1:
+  version "5.17.1"
+  resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
+  integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
   dependencies:
     graceful-fs "^4.2.4"
     tapable "^2.2.0"
@@ -7368,21 +7352,20 @@ webpack-stats-plugin@^1.1.3:
   resolved "https://registry.yarnpkg.com/webpack-stats-plugin/-/webpack-stats-plugin-1.1.3.tgz#ebcc36c8b468074ad737882e2043c1ce4b55d928"
   integrity sha512-yUKYyy+e0iF/w31QdfioRKY+h3jDBRpthexBOWGKda99iu2l/wxYsI/XqdlP5IU58/0KB9CsJZgWNAl+/MPkRw==
 
-webpack@^5.75.0:
-  version "5.91.0"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9"
-  integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==
+webpack@^5.94.0:
+  version "5.94.0"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f"
+  integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==
   dependencies:
-    "@types/eslint-scope" "^3.7.3"
     "@types/estree" "^1.0.5"
     "@webassemblyjs/ast" "^1.12.1"
     "@webassemblyjs/wasm-edit" "^1.12.1"
     "@webassemblyjs/wasm-parser" "^1.12.1"
     acorn "^8.7.1"
-    acorn-import-assertions "^1.9.0"
+    acorn-import-attributes "^1.9.5"
     browserslist "^4.21.10"
     chrome-trace-event "^1.0.2"
-    enhanced-resolve "^5.16.0"
+    enhanced-resolve "^5.17.1"
     es-module-lexer "^1.2.1"
     eslint-scope "5.1.1"
     events "^3.2.0"


Test patch:
diff --git a/test/interpreter/Environment.test.js b/test/interpreter/Environment.test.js
index 52bd517c..9365de58 100644
--- a/test/interpreter/Environment.test.js
+++ b/test/interpreter/Environment.test.js
@@ -1,7 +1,7 @@
 const brs = require("../../bin/brs.node");
 const { Environment, Scope } = brs;
 const { Lexeme } = brs.lexer;
-const { BrsString, RoAssociativeArray, Int32, BrsInvalid, Callable } = brs.types;
+const { BrsString, RoAssociativeArray, Int32 } = brs.types;
 
 describe("Environment", () => {
     let env;
@@ -11,7 +11,7 @@ describe("Environment", () => {
     let identifier = (text) => ({ kind: Lexeme.Identifier, text: text, line: lineNumber++ });
 
     beforeEach(() => {
-        env = new Environment();
+        env = new Environment(new RoAssociativeArray([]));
         lineNumber = 0;
     });
 
@@ -137,7 +137,7 @@ describe("Environment", () => {
 
     it("maintains root-m scope between subenvironments", () => {
         let rootM = new RoAssociativeArray([]);
-        env = new Environment(rootM);
+        env = new Environment(new RoAssociativeArray([]), rootM);
 
         expect(env.getRootM()).toBe(env.createSubEnvironment().getRootM());
     });
diff --git a/test/parser/statement/PrintStatement.test.js b/test/parser/statement/PrintStatement.test.js
index 011437ed..af25a78c 100644
--- a/test/parser/statement/PrintStatement.test.js
+++ b/test/parser/statement/PrintStatement.test.js
@@ -25,7 +25,7 @@ describe("parser print statements", () => {
     });
 
     it("supports empty print", () => {
-        let { statements, errors } = brs.parser.Parser.parse([token(Lexeme.Print), EOF]);
+        let { statements, errors } = parser.parse([token(Lexeme.Print), EOF]);
         expect(errors).toEqual([]);
         expect(statements).toBeDefined();
         expect(statements).not.toBeNull();