Repository: sjbarag/brs (js)

Issue:
Calling type() on a roSGNode instance returns a different type name
The following code has a different output when compared to the one of a Roku device:

```
node =  CreateObject("roSGNode", "SomeNode")
?type(node)
```

**Roku:**
```
roSGNode
```

**brs:**
```
Node
```



Pull request description:
fix(stdlib): fix uninitialized type casing
This MR fixes the casing of the `<uninitialized>` brightscript type, and is related to this issue #482 

Apparently, when we try to compare the type of some value to `"<UNINITIALIZED>"` in Brightscript running on Roku devices, it's causing the channel executing that code to crash, as it seems that this should be in all lowercase, which is working as expected.

The problem I'm having with this is that we have some tests which are running in `brs` and in those tests we're testing a util function which is checking if the value is valid and defined:

```vb
function isValidVal(val) as boolean
	if ((type(val) = "<uninitialized>") or (val = invalid) or (type(val) = "roInvalid"))
		return false
	end if
	return true
end function
```

If I test this function in brs like this:

```vb
result = isValidVal(not_defined_anywhere)
```

This returns `true`, which is not correct, but If I change the above function to check for `"<UNINITIALIZED>"` instead it returns the correct `result` which is `false`. The problem is that `"<UNINITIALIZED>"` is not the correct type and I can't refactor the function above to check for it as it's gonna cause a crash for my app.

That being said, I'm not sure if there is a particular reason why is this named `"<UNINITIALIZED>"`, first time contributing to this project.

fixes #482 

Patch:
diff --git a/src/brsTypes/BrsType.ts b/src/brsTypes/BrsType.ts
index c1a14e0..f5dc904 100644
--- a/src/brsTypes/BrsType.ts
+++ b/src/brsTypes/BrsType.ts
@@ -55,7 +55,7 @@ export namespace ValueKind {
             case ValueKind.Void:
                 return "Void";
             case ValueKind.Uninitialized:
-                return "<UNINITIALIZED>";
+                return "<uninitialized>";
             case ValueKind.Object:
                 return "Object";
         }
@@ -389,9 +389,9 @@ export class Uninitialized implements BrsValue, Comparable {
 
     equalTo(other: BrsType): BrsBoolean {
         if (other.kind === ValueKind.String) {
-            // Allow variables to be compared to the string "<UNINITIALIZED>" to test if they've
+            // Allow variables to be compared to the string "<uninitialized>" to test if they've
             // been initialized
-            return BrsBoolean.from(other.value === this.toString());
+            return BrsBoolean.from(other.value === this.toString().toLowerCase());
         }
 
         return BrsBoolean.False;
diff --git a/src/brsTypes/Callable.ts b/src/brsTypes/Callable.ts
index 72e488f..7d7a419 100644
--- a/src/brsTypes/Callable.ts
+++ b/src/brsTypes/Callable.ts
@@ -6,6 +6,7 @@ import { Location } from "../lexer";
 import { Int32 } from "./Int32";
 import { Float } from "./Float";
 import { Double } from "./Double";
+import { Int64 } from "./Int64";
 
 /** An argument to a BrightScript `function` or `sub`. */
 export interface Argument {
@@ -282,6 +283,14 @@ export class Callable implements Brs.BrsValue {
                 return;
             }
 
+            if (
+                expected.type.kind === Brs.ValueKind.Int64 &&
+                received.kind === Brs.ValueKind.Int32
+            ) {
+                args[index] = new Int64(received.getValue());
+                return;
+            }
+
             if (expected.type.kind !== received.kind) {
                 reasons.push({
                     reason: MismatchReason.ArgumentTypeMismatch,
diff --git a/src/brsTypes/Int64.ts b/src/brsTypes/Int64.ts
index 954e0dc..bae2d5f 100644
--- a/src/brsTypes/Int64.ts
+++ b/src/brsTypes/Int64.ts
@@ -4,8 +4,10 @@ import { BrsNumber, Numeric } from "./BrsNumber";
 import { ValueKind, Comparable } from "./BrsType";
 import { Float } from "./Float";
 import { Double } from "./Double";
+import { Boxable } from "./Boxing";
+import { roLongInteger } from "./components/RoLongInteger";
 
-export class Int64 implements Numeric, Comparable {
+export class Int64 implements Numeric, Comparable, Boxable {
     readonly kind = ValueKind.Int64;
     private readonly value: Long;
 
@@ -217,4 +219,8 @@ export class Int64 implements Numeric, Comparable {
     toString(parent?: BrsType): string {
         return this.value.toString();
     }
+
+    box() {
+        return new roLongInteger(this);
+    }
 }
diff --git a/src/brsTypes/components/BrsObjects.ts b/src/brsTypes/components/BrsObjects.ts
index 495f5d2..a9770c3 100644
--- a/src/brsTypes/components/BrsObjects.ts
+++ b/src/brsTypes/components/BrsObjects.ts
@@ -5,17 +5,21 @@ import { RoDateTime } from "./RoDateTime";
 import { Timespan } from "./Timespan";
 import { createNodeByType } from "./RoSGNode";
 import { RoRegex } from "./RoRegex";
+import { RoXMLElement } from "./RoXMLElement";
 import { BrsString, BrsBoolean } from "../BrsType";
 import { RoString } from "./RoString";
 import { roBoolean } from "./RoBoolean";
 import { roDouble } from "./RoDouble";
 import { roFloat } from "./RoFloat";
 import { roInt } from "./RoInt";
+import { roLongInteger } from "./RoLongInteger";
 import { Double } from "../Double";
 import { Float } from "../Float";
 import { Int32 } from "../Int32";
+import { Int64 } from "../Int64";
 import { Interpreter } from "../../interpreter";
 import { roInvalid } from "./RoInvalid";
+import { BrsComponent } from "./BrsComponent";
 
 /** Map containing a list of brightscript components that can be created. */
 export const BrsObjects = new Map<string, Function>([
@@ -32,10 +36,34 @@ export const BrsObjects = new Map<string, Function>([
         "roregex",
         (_: Interpreter, expression: BrsString, flags: BrsString) => new RoRegex(expression, flags),
     ],
+    ["roxmlelement", (_: Interpreter) => new RoXMLElement()],
     ["rostring", (_: Interpreter) => new RoString()],
     ["roboolean", (_: Interpreter, literal: BrsBoolean) => new roBoolean(literal)],
     ["rodouble", (_: Interpreter, literal: Double) => new roDouble(literal)],
     ["rofloat", (_: Interpreter, literal: Float) => new roFloat(literal)],
     ["roint", (_: Interpreter, literal: Int32) => new roInt(literal)],
+    ["rolonginteger", (_: Interpreter, literal: Int64) => new roLongInteger(literal)],
     ["roinvalid", (_: Interpreter) => new roInvalid()],
 ]);
+
+/**
+ * Lets another software using BRS as a library to add/overwrite an implementation of a BrsObject.
+ *
+ * This is useful, for example, if another piece of software wanted to implement video playback or Draw2d functionality.
+ *
+ * In each element of the objectList param, it is pair:
+ * [name of the BrsObject (e.g. "roScreen", etc.), function (interpreter, ...additionalArgs) that returns a new object]
+ *
+ * @example
+ *
+ * extendBrsObjects([
+ *   ["roScreen", (_interpreter) => {return new roScreen();}]
+ * ])
+ *
+ * @param objectList array of pairs: [name, constructor function]
+ */
+export function extendBrsObjects(objectList: [string, () => BrsComponent][]): void {
+    objectList.forEach(([name, ctor]) => {
+        BrsObjects.set(name.toLowerCase(), ctor);
+    });
+}
diff --git a/src/brsTypes/components/ComponentFactory.ts b/src/brsTypes/components/ComponentFactory.ts
index b5de5d9..29445a3 100644
--- a/src/brsTypes/components/ComponentFactory.ts
+++ b/src/brsTypes/components/ComponentFactory.ts
@@ -13,6 +13,7 @@ import {
     Scene,
     MiniKeyboard,
     TextEditBox,
+    BrsComponent,
 } from "..";
 
 export enum BrsComponentName {
@@ -34,11 +35,32 @@ export enum BrsComponentName {
 
 // TODO: update with more components as they're implemented.
 export class ComponentFactory {
+    private static additionalComponents = new Map<string, (name: string) => RoSGNode>();
+
+    /**
+     * Adds additional components types to the factory, so other software can extend brs if necessary.
+     * This would allow other software using this to add other node/component types at runtime
+     * For example, adding custom implementations of the built-in types, or
+     * adding additional types (PinPad, BusySpinner, etc) that aren't here yet
+     *
+     * @static
+     * @param types Array of pairs of [componentTypeName, construction function], such that when a given componentType is requested, the construction function is called and returns one of those components
+     */
+    public static addComponentTypes(types: [string, (name: string) => RoSGNode][]) {
+        types.forEach(([componentType, ctor]) => {
+            this.additionalComponents.set(componentType.toLowerCase(), ctor);
+        });
+    }
+
     public static createComponent(
-        componentType: BrsComponentName,
+        componentType: BrsComponentName | string,
         componentName?: string
     ): RoSGNode | undefined {
         let name = componentName || componentType;
+        const additionalCtor = this.additionalComponents.get(componentType?.toLowerCase());
+        if (additionalCtor) {
+            return additionalCtor(name);
+        }
         switch (componentType) {
             case BrsComponentName.Group:
                 return new Group([], name);
@@ -72,4 +94,19 @@ export class ComponentFactory {
                 return;
         }
     }
+
+    /**
+     * Checks to see if the given component type can be resolved by the Factory
+     * That is, if it is a built in type or has been added at run time.
+     *
+     * @static
+     * @param componentType The name of component to resolve
+     * @returns {boolean} true if that type is resolvable/constructable, false otherwise
+     */
+    public static canResolveComponentType(componentType: BrsComponentName | string): boolean {
+        return (
+            this.additionalComponents.has(componentType?.toLowerCase()) ||
+            componentType in BrsComponentName
+        );
+    }
 }
diff --git a/src/brsTypes/components/RoLongInteger.ts b/src/brsTypes/components/RoLongInteger.ts
new file mode 100644
index 0000000..ff55e5f
--- /dev/null
+++ b/src/brsTypes/components/RoLongInteger.ts
@@ -0,0 +1,79 @@
+import { BrsComponent } from "./BrsComponent";
+import { BrsValue, ValueKind, BrsString, BrsBoolean, BrsInvalid, Comparable } from "../BrsType";
+import { Callable, StdlibArgument } from "../Callable";
+import { Interpreter } from "../../interpreter";
+import { BrsType } from "..";
+import { Unboxable } from "../Boxing";
+import { Int64 } from "../Int64";
+
+export class roLongInteger extends BrsComponent implements BrsValue, Unboxable {
+    readonly kind = ValueKind.Object;
+    private intrinsic: Int64;
+
+    public getValue(): Int64 {
+        return this.intrinsic;
+    }
+
+    constructor(initialValue: Int64) {
+        super("roLongInteger");
+
+        this.intrinsic = initialValue;
+        this.registerMethods({
+            ifLongInt: [this.getLongInt, this.setLongInt],
+            ifToStr: [this.toStr],
+        });
+    }
+
+    unbox() {
+        return this.intrinsic;
+    }
+
+    equalTo(other: BrsType): BrsBoolean {
+        if (other instanceof roLongInteger) {
+            return BrsBoolean.from(
+                other.intrinsic.getValue().low === this.intrinsic.getValue().low
+            );
+        }
+
+        return BrsBoolean.False;
+    }
+
+    toString(_parent?: BrsType): string {
+        return this.intrinsic.toString();
+    }
+
+    // ---------- ifLongInt ----------
+
+    private getLongInt = new Callable("getLongInt", {
+        signature: {
+            args: [],
+            returns: ValueKind.Int64,
+        },
+        impl: (interpreter: Interpreter) => {
+            return this.intrinsic;
+        },
+    });
+
+    private setLongInt = new Callable("setLongInt", {
+        signature: {
+            args: [new StdlibArgument("value", ValueKind.Int64)],
+            returns: ValueKind.Void,
+        },
+        impl: (_interpreter, value: Int64) => {
+            this.intrinsic = value;
+            return BrsInvalid.Instance;
+        },
+    });
+
+    // ---------- ifToStr ----------
+
+    private toStr = new Callable("toStr", {
+        signature: {
+            args: [],
+            returns: ValueKind.String,
+        },
+        impl: (interpreter: Interpreter) => {
+            return new BrsString(this.intrinsic.toString());
+        },
+    });
+}
diff --git a/src/brsTypes/components/RoXMLElement.ts b/src/brsTypes/components/RoXMLElement.ts
new file mode 100644
index 0000000..a7e8f47
--- /dev/null
+++ b/src/brsTypes/components/RoXMLElement.ts
@@ -0,0 +1,90 @@
+import { RoAssociativeArray } from "./RoAssociativeArray";
+import { RoArray } from "./RoArray";
+import { BrsComponent } from "./BrsComponent";
+import { BrsBoolean, BrsString, BrsValue, ValueKind } from "../BrsType";
+import { BrsType } from "..";
+import { Callable, StdlibArgument } from "../Callable";
+import { Interpreter } from "../../interpreter";
+import { XmlDocument, XmlElement } from "xmldoc";
+
+export class RoXMLElement extends BrsComponent implements BrsValue {
+    readonly kind = ValueKind.Object;
+    private xmlNode?: XmlElement;
+
+    constructor() {
+        super("roXMLElement");
+
+        this.registerMethods({
+            ifXMLElement: [this.parse, this.getName, this.getNamedElementsCi, this.getAttributes],
+        });
+    }
+
+    toString(parent?: BrsType) {
+        return "<Component: roXMLElement>";
+    }
+
+    equalTo(other: BrsType) {
+        return BrsBoolean.False;
+    }
+
+    private parse = new Callable("parse", {
+        signature: {
+            args: [new StdlibArgument("str", ValueKind.String)],
+            returns: ValueKind.Boolean,
+        },
+        impl: (interpreter: Interpreter, str: BrsString) => {
+            try {
+                this.xmlNode = new XmlDocument(str.value);
+                return BrsBoolean.True;
+            } catch (err) {
+                this.xmlNode = undefined;
+                return BrsBoolean.False;
+            }
+        },
+    });
+
+    private getName = new Callable("getName", {
+        signature: {
+            args: [],
+            returns: ValueKind.String,
+        },
+        impl: (interpreter: Interpreter) => {
+            return new BrsString(this.xmlNode?.name ?? "");
+        },
+    });
+
+    private getNamedElementsCi = new Callable("getNamedElementsCi", {
+        signature: {
+            args: [new StdlibArgument("str", ValueKind.String)],
+            returns: ValueKind.Object,
+        },
+        impl: (interpreter: Interpreter, str: BrsString) => {
+            let neededKey = str.value.toLowerCase();
+            let arr = [];
+            for (let item of this.xmlNode?.children ?? []) {
+                if (item.type === "element" && item.name?.toLowerCase() === neededKey) {
+                    let childXmlElement = new RoXMLElement();
+                    childXmlElement.xmlNode = item;
+                    arr.push(childXmlElement);
+                }
+            }
+            return new RoArray(arr);
+        },
+    });
+
+    private getAttributes = new Callable("getAttributes", {
+        signature: {
+            args: [],
+            returns: ValueKind.Object,
+        },
+        impl: (interpreter: Interpreter) => {
+            let attrs = this.xmlNode?.attr ?? {};
+            return new RoAssociativeArray(
+                Object.entries(attrs).map(([name, value]) => ({
+                    name: new BrsString(name),
+                    value: new BrsString(value),
+                }))
+            );
+        },
+    });
+}
diff --git a/src/brsTypes/index.ts b/src/brsTypes/index.ts
index 5bdcf56..06bb165 100644
--- a/src/brsTypes/index.ts
+++ b/src/brsTypes/index.ts
@@ -32,6 +32,7 @@ export * from "./Float";
 export * from "./Double";
 export * from "./BrsInterface";
 export * from "./Callable";
+export * from "./components/BrsComponent";
 export * from "./components/RoDeviceInfo";
 export * from "./components/ComponentFactory";
 export * from "./components/RoArray";
@@ -40,11 +41,13 @@ export * from "./components/RoAssociativeArray";
 export * from "./components/Timespan";
 export * from "./components/BrsObjects";
 export * from "./components/RoRegex";
+export * from "./components/RoXMLElement";
 export * from "./components/RoString";
 export * from "./components/RoBoolean";
 export * from "./components/RoDouble";
 export * from "./components/RoFloat";
 export * from "./components/RoInt";
+export * from "./components/RoLongInteger";
 export * from "./components/RoInvalid";
 export * from "./components/RoSGNodeEvent";
 export * from "./components/RoSGNode";
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index 3499851..48beb14 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -10,6 +10,7 @@ import {
     BrsString,
     isBrsBoolean,
     Int32,
+    Int64,
     isBrsCallable,
     Uninitialized,
     RoArray,
@@ -19,6 +20,7 @@ import {
     Callable,
     BrsNumber,
     Float,
+    Double,
 } from "../brsTypes";
 
 import { Lexeme, Location } from "../lexer";
@@ -446,19 +448,25 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
         let requiredType = typeDesignators[name.charAt(name.length - 1)];
 
         if (requiredType && requiredType !== value.kind) {
-            return this.addError(
-                new TypeMismatch({
-                    message: `Attempting to assign incorrect value to statically-typed variable '${name}'`,
-                    left: {
-                        type: requiredType,
-                        location: statement.name.location,
-                    },
-                    right: {
-                        type: value,
-                        location: statement.value.location,
-                    },
-                })
-            );
+            if (requiredType === ValueKind.Int64 && value.kind === ValueKind.Int32) {
+                value = new Int64(value.getValue());
+            } else if (requiredType === ValueKind.Double && value.kind === ValueKind.Float) {
+                value = new Double(value.getValue());
+            } else {
+                return this.addError(
+                    new TypeMismatch({
+                        message: `Attempting to assign incorrect value to statically-typed variable '${name}'`,
+                        left: {
+                            type: requiredType,
+                            location: statement.name.location,
+                        },
+                        right: {
+                            type: value,
+                            location: statement.value.location,
+                        },
+                    })
+                );
+            }
         }
 
         this.environment.define(Scope.Function, statement.name.text, value);
diff --git a/src/parser/ComponentScopeResolver.ts b/src/parser/ComponentScopeResolver.ts
index a83fdde..d610706 100644
--- a/src/parser/ComponentScopeResolver.ts
+++ b/src/parser/ComponentScopeResolver.ts
@@ -1,8 +1,7 @@
 import { ComponentDefinition, ComponentScript } from "../componentprocessor";
 import * as Stmt from "./Statement";
-import { BrsComponentName } from "../brsTypes";
 import pSettle from "p-settle";
-import { ExitForReason } from "./BlockEndReason";
+import { ComponentFactory } from "../brsTypes";
 
 export class ComponentScopeResolver {
     private readonly excludedNames: string[] = ["init"];
@@ -78,7 +77,7 @@ export class ComponentScopeResolver {
         let currentComponent: ComponentDefinition | undefined = component;
         while (currentComponent.extends) {
             // If this is a built-in component, then no work is needed and we can return.
-            if (currentComponent.extends in BrsComponentName) {
+            if (ComponentFactory.canResolveComponentType(currentComponent.extends)) {
                 return Promise.resolve();
             }
 
diff --git a/src/stdlib/CreateObject.ts b/src/stdlib/CreateObject.ts
index 657f571..7b26275 100644
--- a/src/stdlib/CreateObject.ts
+++ b/src/stdlib/CreateObject.ts
@@ -39,7 +39,6 @@ export const CreateObject = new Callable("CreateObject", {
         if (possibleMock instanceof RoAssociativeArray) {
             return new MockNode(possibleMock, objToMock);
         }
-
         let ctor = BrsObjects.get(objName.value.toLowerCase());
 
         return ctor ? ctor(interpreter, ...additionalArgs) : BrsInvalid.Instance;


Test patch:
diff --git a/test/brsTypes/components/ComponentFactory.test.js b/test/brsTypes/components/ComponentFactory.test.js
new file mode 100644
index 0000000..09a885c
--- /dev/null
+++ b/test/brsTypes/components/ComponentFactory.test.js
@@ -0,0 +1,59 @@
+const brs = require("brs");
+const { ComponentFactory, RoSGNode, Callable, ValueKind } = brs.types;
+
+describe("ComponentFactory", () => {
+    describe("createComponent", () => {
+        it("returns a properly constructed built in Node with default name", () => {
+            const component = ComponentFactory.createComponent("Rectangle");
+            expect(component.nodeSubtype).toBe("Rectangle");
+            expect(component.name).toBe("Rectangle");
+            expect(component.constructor.name).toBe("Rectangle");
+        });
+        it("returns a properly constructed built in Node with custom name", () => {
+            const component = ComponentFactory.createComponent("Poster", "Foo");
+            expect(component.nodeSubtype).toBe("Foo");
+            expect(component.constructor.name).toBe("Poster");
+        });
+    });
+
+    describe("addComponentType", () => {
+        class Foo extends RoSGNode {
+            constructor(initializedFields = [], name = "Foo") {
+                super([], name);
+                this.name = name;
+
+                this.registerDefaultFields(this.defaultFields);
+                this.registerInitializedFields(initializedFields);
+
+                this.sayHello = new Callable("sayHello", {
+                    signature: {
+                        args: [],
+                        returns: ValueKind.String,
+                    },
+                    impl: (_interpreter) => {
+                        return "Hello world";
+                    },
+                });
+
+                this.registerMethods({
+                    ifHelloWorld: [this.sayHello],
+                });
+            }
+        }
+
+        it("adds a new Component to be constructed", () => {
+            ComponentFactory.addComponentTypes([
+                [
+                    "Foo",
+                    (name) => {
+                        return new Foo([], name);
+                    },
+                ],
+            ]);
+            const component = ComponentFactory.createComponent("Foo");
+            expect(component.nodeSubtype).toBe("Foo");
+            expect(component.name).toBe("Foo");
+            expect(component.constructor.name).toBe("Foo");
+        });
+    });
+});
diff --git a/test/brsTypes/components/RoLongInteger.test.js b/test/brsTypes/components/RoLongInteger.test.js
new file mode 100644
index 0000000..0c099c8
--- /dev/null
+++ b/test/brsTypes/components/RoLongInteger.test.js
@@ -0,0 +1,84 @@
+const brs = require("brs");
+const { roLongInteger, Int64, BrsBoolean, BrsString, Callable } = brs.types;
+const { Interpreter } = require("../../../lib/interpreter");
+
+describe("roLongInteger", () => {
+    describe("equality", () => {
+        it("compares to intrinsic Int64", () => {
+            let a = new roLongInteger(new Int64(40000111222));
+            let b = new roLongInteger(new Int64(0));
+            let c = new roLongInteger(new Int64(-40000111222));
+            let d = new roLongInteger(new Int64(40000111222));
+            let e = new roLongInteger(new Int64(-5));
+
+            expect(a.equalTo(b)).toBe(BrsBoolean.False);
+            expect(a.equalTo(c)).toBe(BrsBoolean.False);
+            expect(b.equalTo(c)).toBe(BrsBoolean.False);
+            expect(a.equalTo(d)).toBe(BrsBoolean.True);
+            expect(d.equalTo(a)).toBe(BrsBoolean.True);
+            expect(a.equalTo(e)).toBe(BrsBoolean.False);
+            expect(b.equalTo(e)).toBe(BrsBoolean.False);
+        });
+    });
+
+    test("toString", () => {
+        expect(new roLongInteger(new Int64(22)).toString()).toBe("22");
+    });
+
+    describe("ifLongInt, ifToStr", () => {
+        var a = new roLongInteger(new Int64(0));
+        var b = new roLongInteger(new Int64(0));
+        let interpreter;
+        let someNumberA = 305;
+        let someNumberB = 291;
+
+        beforeEach(() => {
+            interpreter = new Interpreter();
+        });
+
+        it("setLongInt", () => {
+            setLongIntA = a.getMethod("setLongInt");
+            setLongIntB = b.getMethod("setLongInt");
+
+            expect(setLongIntA).toBeInstanceOf(Callable);
+            expect(setLongIntB).toBeInstanceOf(Callable);
+
+            setLongIntA.call(interpreter, new Int64(someNumberA));
+            setLongIntB.call(interpreter, new Int64(someNumberB));
+
+            expect(a.equalTo(new roLongInteger(new Int64(someNumberA)))).toBe(BrsBoolean.True);
+            expect(b.equalTo(new roLongInteger(new Int64(someNumberB)))).toBe(BrsBoolean.True);
+        });
+
+        it("getLongInt", () => {
+            a = new roLongInteger(new Int64(someNumberA));
+            b = new roLongInteger(new Int64(someNumberB));
+
+            getLongIntA = a.getMethod("getLongInt");
+            getLongIntB = b.getMethod("getLongInt");
+
+            expect(getLongIntA).toBeInstanceOf(Callable);
+            expect(getLongIntB).toBeInstanceOf(Callable);
+
+            let resultA = getLongIntA.call(interpreter);
+            let resultB = getLongIntB.call(interpreter);
+
+            expect(resultA).toEqual(new Int64(someNumberA));
+            expect(resultB).toEqual(new Int64(someNumberB));
+        });
+
+        it("toStr", () => {
+            a = new roLongInteger(new Int64(someNumberA));
+            b = new roLongInteger(new Int64(someNumberB));
+
+            toStrA = a.getMethod("toStr");
+            toStrB = b.getMethod("toStr");
+
+            let resultA = toStrA.call(interpreter);
+            let resultB = toStrB.call(interpreter);
+
+            expect(resultA).toEqual(new BrsString(String(someNumberA)));
+            expect(resultB).toEqual(new BrsString(String(someNumberB)));
+        });
+    });
+});
diff --git a/test/brsTypes/components/RoXMLElement.test.js b/test/brsTypes/components/RoXMLElement.test.js
new file mode 100644
index 0000000..1286aad
--- /dev/null
+++ b/test/brsTypes/components/RoXMLElement.test.js
@@ -0,0 +1,100 @@
+const brs = require("brs");
+const { BrsString, RoXMLElement } = brs.types;
+const { Interpreter } = require("../../../lib/interpreter");
+
+describe("RoXMLElement", () => {
+    let xmlParser;
+    let interpreter;
+
+    beforeEach(() => {
+        xmlParser = new RoXMLElement();
+        interpreter = new Interpreter();
+    });
+
+    describe("test methods for object with successful parsed xml", () => {
+        beforeEach(() => {
+            let parse = xmlParser.getMethod("parse");
+            parse.call(interpreter, new BrsString(getXmlString()));
+        });
+
+        it("getName", () => {
+            let getName = xmlParser.getMethod("getName");
+            expect(getName.call(interpreter)).toEqual(new BrsString("tag1"));
+        });
+
+        it("getNamedElementsCi", () => {
+            let getNamedElementsCi = xmlParser.getMethod("getNamedElementsCi");
+            expect(getNamedElementsCi.call(interpreter, new BrsString("any")).elements).toEqual([]);
+
+            let children = getNamedElementsCi.call(interpreter, new BrsString("CHiLd1"));
+            expect(children.elements.length).toEqual(2);
+
+            let getName = children.elements[0].getMethod("getName");
+            expect(getName.call(interpreter)).toEqual(new BrsString("Child1"));
+
+            getName = children.elements[1].getMethod("getName");
+            expect(getName.call(interpreter)).toEqual(new BrsString("CHILD1"));
+        });
+
+        it("getAttributes", () => {
+            let getAttributes = xmlParser.getMethod("getAttributes");
+            expect(getAttributes.call(interpreter).elements).not.toEqual(new Map());
+            expect(getAttributes.call(interpreter).elements).toEqual(
+                new Map([
+                    ["id", new BrsString("someId")],
+                    ["attr1", new BrsString("0")],
+                ])
+            );
+        });
+    });
+
+    describe.each([
+        ["test methods for object with no parsed xml", () => {}],
+        [
+            "test methods for object with failed parsing of xml",
+            () => {
+                let parse = xmlParser.getMethod("parse");
+                parse.call(interpreter, new BrsString('>bad_tag id="12" <  some text >/bad_tag<'));
+            },
+        ],
+    ])("%s", (name, tryParse) => {
+        beforeEach(() => {
+            tryParse();
+        });
+
+        it("getName", () => {
+            let getName = xmlParser.getMethod("getName");
+            expect(getName).toBeTruthy();
+            expect(getName.call(interpreter)).toEqual(new BrsString(""));
+        });
+
+        it("getNamedElementsCi", () => {
+            let getNamedElementsCi = xmlParser.getMethod("getNamedElementsCi");
+            expect(getNamedElementsCi).toBeTruthy();
+            expect(getNamedElementsCi.call(interpreter, new BrsString("any")).elements).toEqual([]);
+        });
+
+        it("getAttributes", () => {
+            let getAttributes = xmlParser.getMethod("getAttributes");
+            expect(getAttributes).toBeTruthy();
+            expect(getAttributes.call(interpreter).elements).toEqual(new Map());
+        });
+    });
+
+    describe("test parse method with different xml strings", () => {
+        test.each([
+            ["<tag>some text<tag>", true],
+            ["<tag>some text <child1> child's text </child1> </tag>", true],
+            [getXmlString(), true],
+            ['>bad_tag id="12" <  some text >/bad_tag<', false],
+            ["", false],
+        ])("test parse with string %s", (xmlString, expected) => {
+            let parse = xmlParser.getMethod("parse");
+            expect(parse.call(interpreter, new BrsString(xmlString)).value).toBe(expected);
+        });
+    });
+});
+
+function getXmlString() {
+    return '<tag1 id="someId" attr1="0"> <Child1 id="id1"></Child1> <CHILD1 id="id2"></CHILD1> </tag1>';
+}
diff --git a/test/e2e/BrsComponents.test.js b/test/e2e/BrsComponents.test.js
index 6d0a955..35d5557 100644
--- a/test/e2e/BrsComponents.test.js
+++ b/test/e2e/BrsComponents.test.js
@@ -188,6 +188,35 @@ describe("end to end brightscript functions", () => {
         ]);
     });
 
+    test("components/roXMLElement.brs", () => {
+        return execute([resourceFile("components", "roXMLElement.brs")], outputStreams).then(() => {
+            expect(allArgs(outputStreams.stdout.write).filter((arg) => arg !== "\n")).toEqual([
+                "xmlParser = ",
+                "<Component: roXMLElement>",
+                "type(xmlParser) = ",
+                "roXMLElement",
+                "parse bad xml string, result = ",
+                "false",
+                "parse good xml string, result = ",
+                "true",
+                "getName() = ",
+                "tag1",
+                "getAttributes() = ",
+                `<Component: roAssociativeArray> =\n` +
+                    `{\n` +
+                    `    id: someId\n` +
+                    `    attr1: 0\n` +
+                    `}`,
+                'getNamedElementsCi("child1") count = ',
+                "2",
+                "name of first child  = ",
+                "Child1",
+                "mame of second child = ",
+                "CHILD1",
+            ]);
+        });
+    });
+
     test("components/customComponent.brs", async () => {
         await execute([resourceFile("components", "customComponent.brs")], outputStreams);
 
@@ -265,6 +294,10 @@ describe("end to end brightscript functions", () => {
             "789.012",
             "Integer to string ",
             "23",
+            "LongInteger object type",
+            "roLongInteger",
+            "LongInteger to string ",
+            "2000111222333",
         ]);
     });
 
diff --git a/test/e2e/CustomComponents.test.js b/test/e2e/CustomComponents.test.js
new file mode 100644
index 0000000..e24a20a
--- /dev/null
+++ b/test/e2e/CustomComponents.test.js
@@ -0,0 +1,65 @@
+const { execute } = require("../../lib");
+const { createMockStreams, allArgs } = require("./E2ETests");
+const lolex = require("lolex");
+const brs = require("brs");
+const { RoSGNode, BrsString, Callable, ValueKind } = brs.types;
+const path = require("path");
+
+class Foo extends RoSGNode {
+    constructor(initializedFields = [], name = "Foo") {
+        super([], name);
+        this.name = name;
+
+        this.registerDefaultFields(this.defaultFields);
+        this.registerInitializedFields(initializedFields);
+
+        this.sayHello = new Callable("sayHello", {
+            signature: {
+                args: [],
+                returns: ValueKind.String,
+            },
+            impl: (_interpreter) => {
+                return new BrsString("Foo Says Hello world");
+            },
+        });
+
+        this.registerMethods({
+            ifHelloWorld: [this.sayHello],
+        });
+    }
+}
+
+describe("Extending custom components", () => {
+    let outputStreams;
+
+    beforeAll(() => {
+        brs.types.ComponentFactory.addComponentTypes([["Foo", (name) => new Foo([], name)]]);
+        brs.types.extendBrsObjects([["Foo", (_interpreter) => new Foo()]]);
+
+        outputStreams = createMockStreams();
+    });
+
+    afterEach(() => {
+        jest.resetAllMocks();
+    });
+
+    afterAll(() => {
+        jest.restoreAllMocks();
+    });
+
+    test("components/ExtendCustom.brs", async () => {
+        outputStreams.root = path.join(__dirname, "customResources");
+        let customFile = path.join(
+            __dirname,
+            "customResources",
+            "components",
+            "extendCustomMain.brs"
+        );
+        await execute([customFile], outputStreams);
+
+        expect(allArgs(outputStreams.stdout.write).filter((arg) => arg !== "\n")).toEqual([
+            "ExtendCustom init",
+            "Foo Says Hello world",
+        ]);
+    });
+});
diff --git a/test/e2e/customResources/components/ExtendCustom.brs b/test/e2e/customResources/components/ExtendCustom.brs
new file mode 100644
index 0000000..1b6ef10
--- /dev/null
+++ b/test/e2e/customResources/components/ExtendCustom.brs
@@ -0,0 +1,4 @@
+sub init()
+  print "ExtendCustom init"
+  print m.top.sayHello()
+end sub
\ No newline at end of file
diff --git a/test/e2e/customResources/components/ExtendCustom.xml b/test/e2e/customResources/components/ExtendCustom.xml
new file mode 100644
index 0000000..050b52f
--- /dev/null
+++ b/test/e2e/customResources/components/ExtendCustom.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component name="ExtendCustom" extends="Foo">
+  <script type="text/brightscript" uri="ExtendCustom.brs" />
+  <interface>
+  </interface>
+  <children>
+  </children>
+</component>
diff --git a/test/e2e/customResources/components/extendCustomMain.brs b/test/e2e/customResources/components/extendCustomMain.brs
new file mode 100644
index 0000000..82e6924
--- /dev/null
+++ b/test/e2e/customResources/components/extendCustomMain.brs
@@ -0,0 +1,3 @@
+sub Main()
+  node = CreateObject("RoSgNode", "ExtendCustom")
+end sub
\ No newline at end of file
diff --git a/test/e2e/resources/components/roIntrinsics.brs b/test/e2e/resources/components/roIntrinsics.brs
index 2a1b6e6..701ba03 100644
--- a/test/e2e/resources/components/roIntrinsics.brs
+++ b/test/e2e/resources/components/roIntrinsics.brs
@@ -4,6 +4,7 @@ sub main()
     doubleObject = createObject("roDouble", 123.456)
     floatObject = createObject("roFloat", 789.012)
     integerObject = createObject("roInt", 23)
+    longIntegerObject = createObject("roLongInteger", 2000111222333)
 
     print "Boolean object A " booleanObjectA.toStr()
     print "Boolean object B " booleanObjectB
@@ -17,5 +18,7 @@ sub main()
     print "Double to string "doubleObject.toStr()
     print "Float to string "floatObject.toStr()
     print "Integer to string "integerObject.toStr()
+    print "LongInteger object type"type(longIntegerObject)
+    print "LongInteger to string "longIntegerObject.toStr()
 
 end sub
diff --git a/test/e2e/resources/components/roXMLElement.brs b/test/e2e/resources/components/roXMLElement.brs
new file mode 100644
index 0000000..92ed9b3
--- /dev/null
+++ b/test/e2e/resources/components/roXMLElement.brs
@@ -0,0 +1,13 @@
+sub main()
+    xmlParser = createObject("roXMLElement")
+    ?"xmlParser = "xmlParser 
+    ?"type(xmlParser) = "type(xmlParser) 
+    ?"parse bad xml string, result = "xmlParser.parse("some_xml_doc")
+    ?"parse good xml string, result = "xmlParser.parse("<tag1 id=""someId"" attr1=""0""> <Child1 id=""id1""></Child1> <CHILD1 id=""id2""></CHILD1> </tag1>")
+    ?"getName() = " xmlParser.getName()
+    ?"getAttributes() = " xmlParser.getAttributes()
+    children = xmlParser.getNamedElementsCi("child1")
+    ?"getNamedElementsCi(""child1"") count = " children.count()
+    ?"name of first child  = "children[0].getName()
+    ?"mame of second child = "children[1].getName()
+end sub
\ No newline at end of file
diff --git a/test/e2e/resources/function/scoping.brs b/test/e2e/resources/function/scoping.brs
index 43602e8..48f9a9b 100644
--- a/test/e2e/resources/function/scoping.brs
+++ b/test/e2e/resources/function/scoping.brs
@@ -2,9 +2,9 @@ sub Main()
     functionScoped = "I'm function scoped"
 
     _ = (sub()
-        print "Global: " type(RebootSystem) <> "<UNINITIALIZED>"
-        print "Module: " type(ModuleDefined) <> "<UNINITIALIZED>"
-        print "Function: " type(functionScoped) <> "<UNINITIALIZED>"
+        print "Global: " type(RebootSystem) <> "<uninitialized>"
+        print "Module: " type(ModuleDefined) <> "<uninitialized>"
+        print "Function: " type(functionScoped) <> "<uninitialized>"
     end sub)()
 end sub
 
diff --git a/test/e2e/resources/stdlib/runtime.brs b/test/e2e/resources/stdlib/runtime.brs
index 493c22f..0357597 100644
--- a/test/e2e/resources/stdlib/runtime.brs
+++ b/test/e2e/resources/stdlib/runtime.brs
@@ -1,4 +1,4 @@
 sub main()
-    aa = createObject("roAssociativeArray")
-    print type(aa) <> "<UNINITIALIZED>"
+	aa = createObject("roAssociativeArray")
+	print type(aa) <> "<uninitialized>"
 end sub
diff --git a/test/interpreter/Variables.test.js b/test/interpreter/Variables.test.js
index 22d096f..a8a2198 100644
--- a/test/interpreter/Variables.test.js
+++ b/test/interpreter/Variables.test.js
@@ -59,7 +59,9 @@ describe("interpreter variables", () => {
             new Stmt.Assignment(tokens, identifier("int32%"), new Expr.Literal(new Int32(1))),
             new Stmt.Assignment(tokens, identifier("float!"), new Expr.Literal(new Float(2))),
             new Stmt.Assignment(tokens, identifier("double#"), new Expr.Literal(new Double(3))),
+            new Stmt.Assignment(tokens, identifier("double#"), new Expr.Literal(new Float(3))),
             new Stmt.Assignment(tokens, identifier("int64&"), new Expr.Literal(new Int64(4))),
+            new Stmt.Assignment(tokens, identifier("int64&"), new Expr.Literal(new Int32(4))),
         ];
 
         expect(() => interpreter.exec(assign)).not.toThrow();
@@ -100,11 +102,11 @@ describe("interpreter variables", () => {
             },
             {
                 lhs: "double#",
-                values: [str, int32, float, int64],
+                values: [str, int32, int64],
             },
             {
                 lhs: "int64&",
-                values: [str, int32, float, double],
+                values: [str, float, double],
             },
         ].forEach(({ lhs, values }) => {
             test(lhs, () => {
diff --git a/test/stdlib/Runtime.test.js b/test/stdlib/Runtime.test.js
index 6f4b119..dd38f01 100644
--- a/test/stdlib/Runtime.test.js
+++ b/test/stdlib/Runtime.test.js
@@ -11,6 +11,10 @@ const {
     Float,
     Double,
     Uninitialized,
+    ValueKind,
+    Callable,
+    BrsComponent,
+    extendBrsObjects,
 } = brs.types;
 const { CreateObject, Type } = require("../../lib/stdlib");
 const { Interpreter } = require("../../lib/interpreter");
@@ -28,6 +32,34 @@ describe("global runtime functions", () => {
             let obj = CreateObject.call(interpreter, new BrsString("notAnObject"));
             expect(obj).toEqual(BrsInvalid.Instance);
         });
+
+        describe("extendBrsObjects", () => {
+            class HelloWorld extends BrsComponent {
+                constructor() {
+                    super("HelloWorld");
+
+                    this.sayHello = new Callable("sayHello", {
+                        signature: {
+                            args: [],
+                            returns: ValueKind.String,
+                        },
+                        impl: (_interpreter) => {
+                            return "Hello world";
+                        },
+                    });
+
+                    this.registerMethods({
+                        ifHelloWorld: [this.sayHello],
+                    });
+                }
+            }
+
+            it("can return a new object defined at run time", () => {
+                extendBrsObjects([["HelloWorld", (_interpreter) => new HelloWorld()]]);
+                let obj = CreateObject.call(interpreter, new BrsString("HelloWorld"));
+                expect(obj.sayHello.call(interpreter)).toEqual("Hello world");
+            });
+        });
     });
 
     describe("Type", () => {
@@ -44,7 +76,7 @@ describe("global runtime functions", () => {
                 { value: CreateObject, type: "Function" },
                 { value: new RoArray([]), type: "roArray" },
                 { value: new RoAssociativeArray([]), type: "roAssociativeArray" },
-                { value: Uninitialized.Instance, type: "<UNINITIALIZED>" },
+                { value: Uninitialized.Instance, type: "<uninitialized>" },
             ].forEach((testCase) =>
                 test(testCase.type, () => {
                     expect(Type.call(interpreter, testCase.value, new Int32(3))).toEqual(
@@ -67,7 +99,7 @@ describe("global runtime functions", () => {
                 { value: CreateObject, type: "Function" },
                 { value: new RoArray([]), type: "roArray" },
                 { value: new RoAssociativeArray([]), type: "roAssociativeArray" },
-                { value: Uninitialized.Instance, type: "<UNINITIALIZED>" },
+                { value: Uninitialized.Instance, type: "<uninitialized>" },
             ].forEach((testCase) =>
                 test(testCase.type, () => {
                     expect(Type.call(interpreter, testCase.value)).toEqual(