Repository: sjbarag/brs
Pull request #340 (2019-11-12 20:12:31)

Made interpreter available to any object constructor #306


Patch:
diff --git a/src/Error.ts b/src/Error.ts
index 0591503..a7e0865 100644
--- a/src/Error.ts
+++ b/src/Error.ts
@@ -76,6 +76,23 @@ export class TypeMismatch extends BrsError {
     }
 }
 
+/**
+ * Logs a detected BRS error to console.
+ * @param err the error to log to console
+ */
+export function logConsoleError(err: BrsError) {
+    console.error(err.format());
+}
+
+/**
+ * Produces a function that writes errors to the given error stream.
+ * @param errorStream write stream to write errors to.
+ * @returns function that writes to given write stream.
+ */
+export function getLoggerUsing(errorStream: NodeJS.WriteStream): (err: BrsError) => boolean {
+    return err => errorStream.write(err.format());
+}
+
 /**
  * Returns the `.kind` property of a `BrsType`, otherwise returns the provided `ValueKind`.
  * @param maybeType the `BrsType` to extract a `.kind` field from, or the `ValueKind` to return directly
diff --git a/src/LexerParser.ts b/src/LexerParser.ts
new file mode 100644
index 0000000..4e88304
--- /dev/null
+++ b/src/LexerParser.ts
@@ -0,0 +1,80 @@
+import * as fs from "fs";
+import { promisify } from "util";
+import pSettle from "p-settle";
+const readFile = promisify(fs.readFile);
+
+import { Lexer } from "./lexer";
+import { Parser, Stmt } from "./parser";
+import * as PP from "./preprocessor";
+
+import * as BrsTypes from "./brsTypes";
+export { BrsTypes as types };
+export { PP as preprocessor };
+import { ManifestValue } from "./preprocessor/Manifest";
+import * as BrsError from "./Error";
+import { defaultExecutionOptions, ExecutionOptions } from "./interpreter";
+
+export function getLexerParserFn(
+    manifest: Map<string, ManifestValue>,
+    options: Partial<ExecutionOptions>
+) {
+    const executionOptions = Object.assign(defaultExecutionOptions, options);
+    return async function parse(filenames: string[]): Promise<Stmt.Statement[]> {
+        let parsedFiles = await pSettle(
+            filenames.map(async filename => {
+                let contents;
+                try {
+                    contents = await readFile(filename, "utf-8");
+                } catch (err) {
+                    return Promise.reject({
+                        message: `brs: can't open file '${filename}': [Errno ${err.errno}]`,
+                    });
+                }
+
+                let lexer = new Lexer();
+                let preprocessor = new PP.Preprocessor();
+                let parser = new Parser();
+                [lexer, preprocessor, parser].forEach(emitter =>
+                    emitter.onError(BrsError.getLoggerUsing(executionOptions.stderr))
+                );
+
+                let scanResults = lexer.scan(contents, filename);
+                if (scanResults.errors.length > 0) {
+                    return Promise.reject({
+                        message: "Error occurred during lexing",
+                    });
+                }
+
+                let preprocessResults = preprocessor.preprocess(scanResults.tokens, manifest);
+                if (preprocessResults.errors.length > 0) {
+                    return Promise.reject({
+                        message: "Error occurred during pre-processing",
+                    });
+                }
+
+                let parseResults = parser.parse(preprocessResults.processedTokens);
+                if (parseResults.errors.length > 0) {
+                    return Promise.reject({
+                        message: "Error occurred parsing",
+                    });
+                }
+
+                return Promise.resolve(parseResults.statements);
+            })
+        );
+
+        // don't execute anything if there were reading, lexing, or parsing errors
+        if (parsedFiles.some(file => file.isRejected)) {
+            return Promise.reject({
+                messages: parsedFiles
+                    .filter(file => file.isRejected)
+                    .map(rejection => rejection.reason.message),
+            });
+        }
+
+        // combine statements from all files into one array
+        return parsedFiles
+            .map(file => file.value || [])
+            .reduce((allStatements, fileStatements) => [...allStatements, ...fileStatements], []);
+    };
+}
diff --git a/src/brsTypes/components/BrsObjects.ts b/src/brsTypes/components/BrsObjects.ts
index d263495..581f0de 100644
--- a/src/brsTypes/components/BrsObjects.ts
+++ b/src/brsTypes/components/BrsObjects.ts
@@ -17,18 +17,21 @@ import { Interpreter } from "../../interpreter";
 
 /** Map containing a list of brightscript components that can be created. */
 export const BrsObjects = new Map<string, Function>([
-    ["roassociativearray", () => new RoAssociativeArray([])],
-    ["roarray", () => new RoArray([])],
-    ["rodatetime", () => new RoDateTime()],
-    ["rotimespan", () => new Timespan()],
+    ["roassociativearray", (_: Interpreter) => new RoAssociativeArray([])],
+    ["roarray", (_: Interpreter) => new RoArray([])],
+    ["rodatetime", (_: Interpreter) => new RoDateTime()],
+    ["rotimespan", (_: Interpreter) => new Timespan()],
     [
         "rosgnode",
         (interpreter: Interpreter, nodeType: BrsString) => createNodeByType(interpreter, nodeType),
     ],
-    ["roregex", (expression: BrsString, flags: BrsString) => new RoRegex(expression, flags)],
-    ["rostring", (literal: BrsString) => new RoString(literal)],
-    ["roboolean", (literal: BrsBoolean) => new roBoolean(literal)],
-    ["rodouble", (literal: Double) => new roDouble(literal)],
-    ["rofloat", (literal: Float) => new roFloat(literal)],
-    ["roint", (literal: Int32) => new roInt(literal)],
+    [
+        "roregex",
+        (_: Interpreter, expression: BrsString, flags: BrsString) => new RoRegex(expression, flags),
+    ],
+    ["rostring", (_: Interpreter, literal: BrsString) => new RoString(literal)],
+    ["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)],
 ]);
diff --git a/src/componentprocessor/index.ts b/src/componentprocessor/index.ts
index 83fe08e..d50c6f8 100644
--- a/src/componentprocessor/index.ts
+++ b/src/componentprocessor/index.ts
@@ -5,6 +5,7 @@ import { XmlDocument, XmlElement } from "xmldoc";
 import pSettle = require("p-settle");
 const readFile = promisify(fs.readFile);
 import * as fg from "fast-glob";
+import { Environment } from "../interpreter/Environment";
 
 interface FieldAttributes {
     id: string;
@@ -29,6 +30,11 @@ interface ComponentNode {
     children: ComponentNode[];
 }
 
+export interface ComponentScript {
+    type: string;
+    uri: string;
+}
+
 export class ComponentDefinition {
     public contents?: string;
     public xmlNode?: XmlDocument;
@@ -38,6 +44,8 @@ export class ComponentDefinition {
     public processed: boolean = false;
     public fields: ComponentField = {};
     public children: ComponentNode[] = [];
+    public scripts: ComponentScript[] = [];
+    public environment: Environment | undefined;
 
     constructor(readonly xmlPath: string) {}
 
@@ -129,11 +137,13 @@ async function processXmlTree(
         let xmlNode = nodeDef.xmlNode;
         if (xmlNode) {
             nodeDef.children = getChildren(xmlNode);
+            nodeDef.scripts = getScripts(xmlNode);
             let baseNode = xmlNode.attr.extends;
             while (baseNode) {
                 let baseNodeDef = nodeDefMap.get(baseNode);
                 if (baseNodeDef) {
                     nodeDef.children = [...getChildren(baseNodeDef.xmlNode!), ...nodeDef.children];
+                    nodeDef.scripts = [...getScripts(baseNodeDef.xmlNode!), ...nodeDef.scripts];
                     baseNode = baseNodeDef.xmlNode!.attr.extends;
                 }
             }
@@ -213,3 +223,20 @@ function parseChildren(element: XmlElement, children: ComponentNode[]): void {
         children.push(childComponent);
     });
 }
+
+function getScripts(node: XmlDocument): ComponentScript[] {
+    let scripts = node.childrenNamed("script");
+    let componentScripts: ComponentScript[] = [];
+
+    // TODO: Verify if uri is valid
+    scripts.map(script => {
+        if (script.attr) {
+            componentScripts.push({
+                type: script.attr.type,
+                uri: script.attr.uri,
+            });
+        }
+    });
+
+    return componentScripts;
+}
diff --git a/src/index.ts b/src/index.ts
index fd8c38e..0b5a986 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,15 +1,17 @@
 import * as fs from "fs";
 import * as readline from "readline";
-import { promisify } from "util";
-import pSettle from "p-settle";
-const readFile = promisify(fs.readFile);
 
 import { Lexer } from "./lexer";
 import * as PP from "./preprocessor";
-import { getComponentDefinitionMap } from "./componentprocessor";
+import {
+    getComponentDefinitionMap,
+    ComponentDefinition,
+    ComponentScript,
+} from "./componentprocessor";
 import { Parser } from "./parser";
 import { Interpreter, ExecutionOptions, defaultExecutionOptions } from "./interpreter";
 import * as BrsError from "./Error";
+import * as LexerParser from "./LexerParser";
 
 import * as _lexer from "./lexer";
 export { _lexer as lexer };
@@ -18,6 +20,8 @@ export { BrsTypes as types };
 export { PP as preprocessor };
 import * as _parser from "./parser";
 export { _parser as parser };
+import { URL } from "url";
+import * as path from "path";
 
 /**
  * Executes a BrightScript file by path and writes its output to the streams
@@ -34,73 +38,36 @@ export async function execute(filenames: string[], options: Partial<ExecutionOpt
     const executionOptions = Object.assign(defaultExecutionOptions, options);
 
     let manifest = await PP.getManifest(executionOptions.root);
+    let componentDefinitions = await getComponentDefinitionMap(executionOptions.root);
+
+    componentDefinitions.forEach((component: ComponentDefinition) => {
+        if (component.scripts.length < 1) return;
+        try {
+            component.scripts = component.scripts.map((script: ComponentScript) => {
+                script.uri = path.join(
+                    options.root ? options.root : __dirname,
+                    new URL(script.uri).pathname
+                );
+                return script;
+            });
+        } catch (error) {
+            throw new Error(
+                `Encountered an error when parsing component ${component.name}: ${error}`
+            );
+        }
+    });
 
-    let nodeDefs = await getComponentDefinitionMap(executionOptions.root);
-
-    // wait for all files to be read, lexed, and parsed, but don't exit on the first error
-    let parsedFiles = await pSettle(
-        filenames.map(async filename => {
-            let contents;
-            try {
-                contents = await readFile(filename, "utf-8");
-            } catch (err) {
-                return Promise.reject({
-                    message: `brs: can't open file '${filename}': [Errno ${err.errno}]`,
-                });
-            }
-
-            let lexer = new Lexer();
-            let preprocessor = new PP.Preprocessor();
-            let parser = new Parser();
-            [lexer, preprocessor, parser].forEach(emitter => emitter.onError(logError));
-
-            let scanResults = lexer.scan(contents, filename);
-            if (scanResults.errors.length > 0) {
-                return Promise.reject({
-                    message: "Error occurred during lexing",
-                });
-            }
-
-            let preprocessResults = preprocessor.preprocess(scanResults.tokens, manifest);
-            if (preprocessResults.errors.length > 0) {
-                return Promise.reject({
-                    message: "Error occurred during pre-processing",
-                });
-            }
-
-            let parseResults = parser.parse(preprocessResults.processedTokens);
-            if (parseResults.errors.length > 0) {
-                return Promise.reject({
-                    message: "Error occurred parsing",
-                });
-            }
-
-            return Promise.resolve(parseResults.statements);
-        })
+    let lexerParserFn = LexerParser.getLexerParserFn(manifest, options);
+    const interpreter = await Interpreter.withSubEnvsFromComponents(
+        componentDefinitions,
+        lexerParserFn
     );
-
-    // don't execute anything if there were reading, lexing, or parsing errors
-    if (parsedFiles.some(file => file.isRejected)) {
-        return Promise.reject({
-            messages: parsedFiles
-                .filter(file => file.isRejected)
-                .map(rejection => rejection.reason.message),
-        });
+    if (!interpreter) {
+        throw new Error("Unable to build interpreter.");
     }
 
-    // combine statements from all files into one array
-    let statements = parsedFiles
-        .map(file => file.value || [])
-        .reduce((allStatements, fileStatements) => [...allStatements, ...fileStatements], []);
-
-    // execute them
-    const interpreter = new Interpreter(executionOptions);
-    interpreter.onError(logError);
-    // save each custom component def into a global map so we can access it
-    // at run time when we call `createObjectByType`
-    interpreter.environment.nodeDefMap = nodeDefs;
-
-    return interpreter.exec(statements);
+    let mainStatements = await lexerParserFn(filenames);
+    return interpreter.exec(mainStatements);
 }
 
 /**
@@ -136,7 +103,7 @@ export function lexParseSync(filenames: string[], options: Partial<ExecutionOpti
  */
 export function repl() {
     const replInterpreter = new Interpreter();
-    replInterpreter.onError(logError);
+    replInterpreter.onError(BrsError.getLoggerUsing(process.stderr));
 
     const rl = readline.createInterface({
         input: process.stdin,
@@ -179,9 +146,10 @@ function run(
 ) {
     const lexer = new Lexer();
     const parser = new Parser();
+    const logErrorFn = BrsError.getLoggerUsing(options.stderr);
 
-    lexer.onError(logError);
-    parser.onError(logError);
+    lexer.onError(logErrorFn);
+    parser.onError(logErrorFn);
 
     const scanResults = lexer.scan(contents, "REPL");
     if (scanResults.errors.length > 0) {
@@ -204,11 +172,3 @@ function run(
         return;
     }
 }
-
-/**
- * Logs a detected BRS error to stderr.
- * @param err the error to log to `stderr`
- */
-function logError(err: BrsError.BrsError) {
-    console.error(err.format());
-}
diff --git a/src/interpreter/Environment.ts b/src/interpreter/Environment.ts
index 14e4db1..c0c56da 100644
--- a/src/interpreter/Environment.ts
+++ b/src/interpreter/Environment.ts
@@ -21,6 +21,13 @@ 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) {
+        if (!rootM) {
+            this.rootM = this.mPointer;
+        } else {
+            this.rootM = rootM;
+        }
+    }
     /**
      * Functions that are always accessible.
      * @see Scope.Global
@@ -42,6 +49,7 @@ export class Environment {
     private mockObjects = new Map<string, RoAssociativeArray>();
     /** The BrightScript `m` pointer, analogous to JavaScript's `this` pointer. */
     private mPointer = new RoAssociativeArray([]);
+    private rootM: RoAssociativeArray;
     /**
      * The one true focus of the scenegraph app, only one component can have focus at a time.
      * Note: this focus is only meaningful if the node being set focus to
@@ -93,6 +101,14 @@ export class Environment {
         return this.mPointer;
     }
 
+    /**
+     * Retrieves the the special `m` variable from the root Environment.
+     * @returns the current value used for the root `m` pointer.
+     */
+    public getRootM(): RoAssociativeArray {
+        return this.rootM;
+    }
+
     /**
      * Removes a variable from this environment's function scope.
      * @param name the name of the variable to remove (in the form of an `Identifier`)
@@ -185,13 +201,17 @@ export class Environment {
      * 3. Parameters passed into the function
      * 4. The `m` pointer, defined by the way in which a function was called
      * 5. Currently focused node object that reacts to onKey button presses
+     * @param includeModuleScope whether or not to includer caller's module scope into
+     * the cloned environment.
      *
      * @returns a copy of this environment but with no function-scoped values.
      */
-    public createSubEnvironment(): Environment {
-        let newEnvironment = new Environment();
+    public createSubEnvironment(includeModuleScope: boolean = true): Environment {
+        let newEnvironment = new Environment(this.rootM);
         newEnvironment.global = new Map(this.global);
-        newEnvironment.module = new Map(this.module);
+        newEnvironment.module = includeModuleScope
+            ? new Map(this.module)
+            : new Map<string, BrsType>();
         newEnvironment.mPointer = this.mPointer;
         newEnvironment.mockObjects = this.mockObjects;
         newEnvironment.focusedNode = this.focusedNode;
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index 9f6e0bb..32e1fa2 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -18,14 +18,13 @@ import {
     MismatchReason,
     Callable,
     BrsNumber,
-    Comparable,
     Float,
 } from "../brsTypes";
 
 import { Lexeme } from "../lexer";
 import { isToken } from "../lexer/Token";
 import { Expr, Stmt } from "../parser";
-import { BrsError, TypeMismatch } from "../Error";
+import { BrsError, TypeMismatch, getLoggerUsing } from "../Error";
 
 import * as StdLib from "../stdlib";
 import { _brs_ } from "../extensions";
@@ -39,6 +38,10 @@ import MemoryFileSystem from "memory-fs";
 import { BrsComponent } from "../brsTypes/components/BrsComponent";
 import { isBoxable, isUnboxable } from "../brsTypes/Boxing";
 
+import { ManifestValue } from "../preprocessor/Manifest";
+import { ComponentDefinition } from "../componentprocessor";
+import pSettle from "p-settle";
+
 /** The set of options used to configure an interpreter's execution. */
 export interface ExecutionOptions {
     /** The base path for  */
@@ -94,6 +97,40 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
         this.events.once("err", errorHandler);
     }
 
+    /**
+     * Builds out all the sub-environments for the given components. Components are saved into the calling interpreter
+     * instance. This function will mutate the state of the calling interpreter.
+     * @param componentMap Map of all components to be assigned to this interpreter
+     * @param parseFn Function used to parse components into interpretable statements
+     * @param options
+     */
+    public static async withSubEnvsFromComponents(
+        componentMap: Map<string, ComponentDefinition>,
+        parseFn: (filenames: string[]) => Promise<Stmt.Statement[]>,
+        options: ExecutionOptions = defaultExecutionOptions
+    ) {
+        let interpreter = new Interpreter(options);
+        interpreter.onError(getLoggerUsing(options.stderr));
+
+        interpreter.environment.nodeDefMap = componentMap;
+        await pSettle(
+            Array.from(componentMap).map(async componentKV => {
+                let [_, component] = componentKV;
+                component.environment = interpreter.environment.createSubEnvironment(
+                    /* includeModuleScope */ false
+                );
+                let statements = await parseFn(component.scripts.map(c => c.uri));
+                interpreter.inSubEnv(subInterpreter => {
+                    subInterpreter.environment.setM(interpreter.environment.getM());
+                    subInterpreter.exec(statements);
+                    return BrsInvalid.Instance;
+                }, component.environment);
+            })
+        );
+
+        return interpreter;
+    }
+
     /**
      * Creates a new Interpreter, including any global properties and functions.
      * @param options configuration for the execution, including the streams to use for `stdout` and
@@ -126,10 +163,12 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
      * passes the sub-interpreter to the provided JavaScript function. Always
      * reverts the current interpreter's environment to its original value.
      * @param func the JavaScript function to execute with the sub interpreter.
+     * @param environment (Optional) the environment to run the interpreter in.
      */
-    inSubEnv(func: (interpreter: Interpreter) => BrsType): BrsType {
+    inSubEnv(func: (interpreter: Interpreter) => BrsType, environment?: Environment): BrsType {
         let originalEnvironment = this._environment;
-        let newEnv = this._environment.createSubEnvironment();
+        let newEnv = environment || this._environment.createSubEnvironment();
+
         try {
             this._environment = newEnv;
             return func(this);
diff --git a/src/lexer/Lexer.ts b/src/lexer/Lexer.ts
index c823beb..c302a35 100644
--- a/src/lexer/Lexer.ts
+++ b/src/lexer/Lexer.ts
@@ -641,17 +641,36 @@ export class Lexer {
 
             let tokenType = KeyWords[text.toLowerCase()] || Lexeme.Identifier;
             if (tokenType === KeyWords.rem) {
-                // The 'rem' keyword can be used to indicate comments as well, so
-                // consume the rest of the line, but don't add the token; it's not
-                // particularly useful.
-                while (peek() !== "\n" && !isAtEnd()) {
-                    advance();
+                //the rem keyword can be used as an identifier on objects,
+                //so do a quick look-behind to see if there's a preceeding dot
+                if (checkPrevious(Lexeme.Dot)) {
+                    addToken(Lexeme.Identifier);
+                } else {
+                    // The 'rem' keyword can be used to indicate comments as well, so
+                    // consume the rest of the line, but don't add the token; it's not
+                    // particularly useful.
+                    while (peek() !== "\n" && !isAtEnd()) {
+                        advance();
+                    }
                 }
             } else {
                 addToken(tokenType);
             }
         }
 
+        /**
+         * Check that the previous token was of the specified type
+         * @param kind
+         */
+        function checkPrevious(kind: Lexeme) {
+            let previous = tokens[tokens.length - 1];
+            if (previous && previous.kind === kind) {
+                return true;
+            } else {
+                return false;
+            }
+        }
+
         /**
          * Reads characters within an identifier with a leading '#', typically reserved for conditional
          * compilation. Adds the produced token to the `tokens` array.
diff --git a/src/parser/Parser.ts b/src/parser/Parser.ts
index 8879db5..ceda52a 100644
--- a/src/parser/Parser.ts
+++ b/src/parser/Parser.ts
@@ -911,7 +911,7 @@ export class Parser {
                 }
                 thenBranch = new Stmt.Block([thenStatement], peek().location);
 
-                while (match(Lexeme.ElseIf)) {
+                while (previous().kind !== Lexeme.Newline && match(Lexeme.ElseIf)) {
                     let elseIf = previous();
                     let elseIfCondition = expression();
                     if (checkThen()) {
@@ -933,7 +933,10 @@ export class Parser {
                     });
                 }
 
-                if (match(Lexeme.Else)) {
+                if (
+                    previous().kind !== Lexeme.Newline &&
+                    (previous().kind === Lexeme.Else || match(Lexeme.Else))
+                ) {
                     let elseStatement = declaration();
                     if (!elseStatement) {
                         throw addError(peek(), `Expected a statement to follow 'else'`);
@@ -1018,6 +1021,8 @@ export class Parser {
                     consume(
                         "Expected newline or ':' after indexed 'set' statement",
                         Lexeme.Newline,
+                        Lexeme.Else,
+                        Lexeme.ElseIf,
                         Lexeme.Colon,
                         Lexeme.Eof
                     );
@@ -1034,6 +1039,8 @@ export class Parser {
                     consume(
                         "Expected newline or ':' after dotted 'set' statement",
                         Lexeme.Newline,
+                        Lexeme.Else,
+                        Lexeme.ElseIf,
                         Lexeme.Colon,
                         Lexeme.Eof
                     );
diff --git a/src/stdlib/CreateObject.ts b/src/stdlib/CreateObject.ts
index 3d8f947..b9756b9 100644
--- a/src/stdlib/CreateObject.ts
+++ b/src/stdlib/CreateObject.ts
@@ -20,6 +20,7 @@ export const CreateObject = new Callable("CreateObject", {
             new StdlibArgument("arg2", ValueKind.Dynamic, BrsInvalid.Instance),
             new StdlibArgument("arg3", ValueKind.Dynamic, BrsInvalid.Instance),
             new StdlibArgument("arg4", ValueKind.Dynamic, BrsInvalid.Instance),
+            new StdlibArgument("arg5", ValueKind.Dynamic, BrsInvalid.Instance),
         ],
         returns: ValueKind.Dynamic,
     },
@@ -40,10 +41,7 @@ export const CreateObject = new Callable("CreateObject", {
         }
 
         let ctor = BrsObjects.get(objName.value.toLowerCase());
-        if (ctor && objName.value.toLowerCase() === "rosgnode") {
-            return ctor(interpreter, ...additionalArgs);
-        }
 
-        return ctor ? ctor(...additionalArgs) : BrsInvalid.Instance;
+        return ctor ? ctor(interpreter, ...additionalArgs) : BrsInvalid.Instance;
     },
 });
diff --git a/src/stdlib/index.ts b/src/stdlib/index.ts
index cdf76dd..7e88be4 100644
--- a/src/stdlib/index.ts
+++ b/src/stdlib/index.ts
@@ -1,4 +1,5 @@
-import { Callable, ValueKind, BrsInvalid } from "../brsTypes";
+import { Callable, ValueKind, BrsInvalid, RoAssociativeArray } from "../brsTypes";
+import { Interpreter } from "../interpreter";
 
 let warningShown = false;
 
@@ -17,6 +18,19 @@ export const RebootSystem = new Callable("RebootSystem", {
     },
 });
 
+/**
+ * Returns global M pointer (the m from the root Environment).
+ */
+export const GetGlobalAA = new Callable("GetGlobalAA", {
+    signature: {
+        args: [],
+        returns: ValueKind.Dynamic,
+    },
+    impl: (interpreter: Interpreter): RoAssociativeArray => {
+        return interpreter.environment.getRootM();
+    },
+});
+
 export * from "./CreateObject";
 export * from "./File";
 export * from "./Json";


Test patch:
diff --git a/test/componentprocessor/componentprocessor.test.js b/test/componentprocessor/componentprocessor.test.js
index bad7827..de92749 100644
--- a/test/componentprocessor/componentprocessor.test.js
+++ b/test/componentprocessor/componentprocessor.test.js
@@ -1,10 +1,13 @@
 const { getComponentDefinitionMap, ComponentDefinition } = require("../../lib/componentprocessor");
+const path = require("path");
 
 jest.mock("fast-glob");
 jest.mock("fs");
 const fg = require("fast-glob");
 const fs = require("fs");
 
+const realFs = jest.requireActual("fs");
+
 describe.only("component parsing support", () => {
     afterEach(() => {
         fg.sync.mockRestore();
@@ -53,42 +56,55 @@ describe.only("component parsing support", () => {
             expect(xmlNode.attr.extends).toEqual("BaseComponent");
         });
 
-        it("parses children nodes in correct order", async () => {
-            const baseComp = `
-<?xml version="1.0" encoding="utf-8" ?>
-<component name="BaseComponent">
-    <children>
-        <Label name="label_a" />
-    </children>
-</component>
-            `;
-            const extendedComp = `
-<?xml version="1.0" encoding="utf-8" ?>
-<component name="ExtendedComponent" extends="BaseComponent">
-    <children>
-            <Label name="label_b" />
-    </children>
-</component>
-            `;
-
-            fg.sync.mockImplementation(() => {
-                return ["base_component.xml", "extended_component.xml"];
+        describe("parsing expectations", () => {
+            beforeEach(() => {
+                fg.sync.mockImplementation(() => {
+                    return [
+                        "baseComponent.xml",
+                        "extendedComponent.xml",
+                        "scripts/baseComp.brs",
+                        "scripts/extendedComp.brs",
+                        "scripts/utility.brs",
+                    ];
+                });
+                fs.readFile.mockImplementation((filename, _, cb) => {
+                    resourcePath = path.join(__dirname, "resources", filename);
+                    realFs.readFile(resourcePath, (err, contents) => {
+                        cb(/* no error */ null, contents);
+                    });
+                });
             });
-            fs.readFile.mockImplementation((filename, _, cb) => {
-                selected = filename.includes("base_component.xml") ? baseComp : extendedComp;
-                cb(/* no error */ null, selected);
+
+            it("parses children nodes in correct order", async () => {
+                let map = await getComponentDefinitionMap("/doesnt/matter");
+                let parsedExtendedComp = map.get("ExtendedComponent");
+                expect(parsedExtendedComp).not.toBeUndefined();
+                expect(parsedExtendedComp.children).not.toBeUndefined();
+                expect(parsedExtendedComp.children.length).toBeGreaterThan(0);
+
+                let expectedChildOrder = ["label_a", "label_b"];
+                let childOrder = parsedExtendedComp.children.map(child => {
+                    return child.fields && child.fields.name;
+                });
+                expect(childOrder).toEqual(expectedChildOrder);
             });
 
-            let map = await getComponentDefinitionMap("/doesnt/matter");
-            let parsedExtendedComp = map.get("ExtendedComponent");
-            expect(parsedExtendedComp).not.toBeUndefined();
-            expect(parsedExtendedComp.children).not.toBeUndefined();
-            expect(parsedExtendedComp.children.length).toBeGreaterThan(0);
-            let expectedChildOrder = ["label_a", "label_b"];
-            let childOrder = parsedExtendedComp.children.map(child => {
-                return child.fields && child.fields.name;
+            it("adds all scripts into node in correct order", async () => {
+                let map = await getComponentDefinitionMap("/doesnt/matter");
+                let parsedExtendedComp = map.get("ExtendedComponent");
+                expect(parsedExtendedComp).not.toBeUndefined();
+                expect(parsedExtendedComp.scripts).not.toBeUndefined();
+                expect(parsedExtendedComp.scripts.length).toBeGreaterThan(0);
+
+                const expectedPrefix = "pkg:/test/componentprocessor/resources/scripts/";
+                let expectedScripts = [
+                    "baseComponent.brs",
+                    "extendedComponent.brs",
+                    "utility.brs",
+                ].map(scriptName => path.join(expectedPrefix, scriptName));
+                let actualScripts = parsedExtendedComp.scripts.map(script => script.uri);
+                expect(actualScripts).toEqual(expectedScripts);
             });
-            expect(childOrder).toEqual(expectedChildOrder);
         });
     });
 });
diff --git a/test/componentprocessor/resources/baseComponent.xml b/test/componentprocessor/resources/baseComponent.xml
new file mode 100644
index 0000000..7ca30a2
--- /dev/null
+++ b/test/componentprocessor/resources/baseComponent.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<component name="BaseComponent">
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/baseComponent.brs" />
+<children>
+    <Label name="label_a" />
+</children>
+</component>
diff --git a/test/componentprocessor/resources/extendedComponent.xml b/test/componentprocessor/resources/extendedComponent.xml
new file mode 100644
index 0000000..604769e
--- /dev/null
+++ b/test/componentprocessor/resources/extendedComponent.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<component name="ExtendedComponent" extends="BaseComponent">
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/extendedComponent.brs" />
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/utility.brs" />
+<children>
+        <Label name="label_b" />
+</children>
+</component>
diff --git a/test/componentprocessor/resources/scripts/baseComponent.brs b/test/componentprocessor/resources/scripts/baseComponent.brs
new file mode 100644
index 0000000..6cf230a
--- /dev/null
+++ b/test/componentprocessor/resources/scripts/baseComponent.brs
@@ -0,0 +1,4 @@
+sub init()
+    ?"base component"
+    x = 5
+end sub
\ No newline at end of file
diff --git a/test/componentprocessor/resources/scripts/extendedComponent.brs b/test/componentprocessor/resources/scripts/extendedComponent.brs
new file mode 100644
index 0000000..8db7136
--- /dev/null
+++ b/test/componentprocessor/resources/scripts/extendedComponent.brs
@@ -0,0 +1,4 @@
+sub init()
+    ?"extended component"
+    x = 10
+end sub
\ No newline at end of file
diff --git a/test/componentprocessor/resources/scripts/utility.brs b/test/componentprocessor/resources/scripts/utility.brs
new file mode 100644
index 0000000..3f31435
--- /dev/null
+++ b/test/componentprocessor/resources/scripts/utility.brs
@@ -0,0 +1,3 @@
+sub utility()
+    ?"utility"
+end sub
\ No newline at end of file
diff --git a/test/e2e/ConditionalCompilation.test.js b/test/e2e/ConditionalCompilation.test.js
index 2d56cfc..9ec8bd5 100644
--- a/test/e2e/ConditionalCompilation.test.js
+++ b/test/e2e/ConditionalCompilation.test.js
@@ -31,8 +31,7 @@ describe("end to end conditional compilation", () => {
 
     describe("(with sterr captured)", () => {
         test("conditional-compilation/compile-error.brs", async done => {
-            // make console.error empty so we don't clutter test output
-            let stderr = jest.spyOn(console, "error").mockImplementation(() => {});
+            let stderr = outputStreams.stderrSpy;
 
             try {
                 await execute(
diff --git a/test/e2e/E2ETests.js b/test/e2e/E2ETests.js
index 559172b..d80eabd 100644
--- a/test/e2e/E2ETests.js
+++ b/test/e2e/E2ETests.js
@@ -23,8 +23,11 @@ exports.allArgs = function(jestMock) {
 exports.createMockStreams = function() {
     const stdout = Object.assign(new stream.PassThrough(), process.stdout);
     const stderr = Object.assign(new stream.PassThrough(), process.stderr);
-    jest.spyOn(stdout, "write").mockImplementation(() => {});
-    jest.spyOn(stderr, "write").mockImplementation(() => {});
 
-    return { stdout, stderr };
+    return {
+        stdout,
+        stderr,
+        stdoutSpy: jest.spyOn(stdout, "write").mockImplementation(() => {}),
+        stderrSpy: jest.spyOn(stderr, "write").mockImplementation(() => {}),
+    };
 };
diff --git a/test/e2e/Functions.test.js b/test/e2e/Functions.test.js
index 6646fae..77d0c6d 100644
--- a/test/e2e/Functions.test.js
+++ b/test/e2e/Functions.test.js
@@ -76,6 +76,25 @@ describe("end to end functions", () => {
         ]);
     });
 
+    test("function/m-pointer-global.brs", async () => {
+        await execute([resourceFile("function", "m-pointer-global.brs")], outputStreams);
+
+        expect(allArgs(outputStreams.stdout.write).filter(arg => arg !== "\n")).toEqual([
+            "old: ",
+            "value 1",
+            "new: ",
+            "value 2",
+            "glb: ",
+            "value 2",
+            "old: ",
+            "invalid",
+            "new: ",
+            "value 4",
+            "glb: ",
+            "value 3",
+        ]);
+    });
+
     test("function/scoping.brs", async () => {
         await execute([resourceFile("function", "scoping.brs")], outputStreams);
 
diff --git a/test/e2e/Syntax.test.js b/test/e2e/Syntax.test.js
index ac9b87b..b5ac44f 100644
--- a/test/e2e/Syntax.test.js
+++ b/test/e2e/Syntax.test.js
@@ -101,6 +101,8 @@ describe("end to end syntax", () => {
             "4",
             "5",
             "6",
+            "7",
+            "8",
         ]);
     });
 
diff --git a/test/e2e/TypeChecking.test.js b/test/e2e/TypeChecking.test.js
index 2bdc7e7..71d6c6a 100644
--- a/test/e2e/TypeChecking.test.js
+++ b/test/e2e/TypeChecking.test.js
@@ -3,12 +3,11 @@ const { execute } = require("../../lib/");
 const { createMockStreams, resourceFile, allArgs } = require("./E2ETests");
 
 describe("function argument type checking", () => {
-    let outputStreams;
+    let outputStreams, stderr;
 
     beforeAll(() => {
-        // make console.error empty so we don't clutter test output
-        stderr = jest.spyOn(console, "error").mockImplementation(() => {});
         outputStreams = createMockStreams();
+        stderr = outputStreams.stderrSpy;
     });
 
     afterEach(() => {
diff --git a/test/e2e/resources/components/AdvancedWidget.xml b/test/e2e/resources/components/AdvancedWidget.xml
index c748bb5..2c0dd41 100644
--- a/test/e2e/resources/components/AdvancedWidget.xml
+++ b/test/e2e/resources/components/AdvancedWidget.xml
@@ -1,5 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <component name="AdvancedWidget" extends="NormalWidget">
+    <script type="text/brightscript" uri="pkg:/components/scripts/AdvancedWidget.brs" />
+    <script type="text/brightscript" uri="pkg:/components/scripts/SupportScript.brs" />
     <interface>
         <!-- output -->
         <field id="advancedStringField" type="string" value="advancedField!" alwaysNotify="true" />
diff --git a/test/e2e/resources/components/BaseWidget.xml b/test/e2e/resources/components/BaseWidget.xml
index 4edabec..b609aff 100644
--- a/test/e2e/resources/components/BaseWidget.xml
+++ b/test/e2e/resources/components/BaseWidget.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <component name="BaseWidget">
+    <script type="text/brightscript" uri="pkg:/components/scripts/BaseWidget.brs" />
     <interface>
         <field id="baseStringField" type="string" />
         <field id="baseIntField" type="integer" />
diff --git a/test/e2e/resources/components/NormalWidget.xml b/test/e2e/resources/components/NormalWidget.xml
index 020524d..fccc640 100644
--- a/test/e2e/resources/components/NormalWidget.xml
+++ b/test/e2e/resources/components/NormalWidget.xml
@@ -1,13 +1,11 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <component name="NormalWidget" extends="BaseWidget">
-    <script type="text/brightscript" uri="pkg:/source/hello.brs" />
+    <script type="text/brightscript" uri="pkg:/components/scripts/NormalWidget.brs" />
     <interface>
         <field id="normalStringField" type="string" onChange="onNormalStringFieldChange" />
         <field id="normalIntField" type="integer" value="-321" />
         <field id="normalBoolField" type="boolean" value="true" />
     </interface>
-    <script type="text/brightscript" uri="pkg:/components/player/liveGuide/scripts/BaseGuideView.brs" />
-    <script type="text/brightscript" uri="pkg:/components/player/liveGuide/scripts/LiveGuideSettings.brs" />
     <children>
         <Label id="normalLabel" />
         <SimpleLabel id="normalSimpleLabel" />
diff --git a/test/e2e/resources/components/scripts/AdvancedWidget.brs b/test/e2e/resources/components/scripts/AdvancedWidget.brs
new file mode 100644
index 0000000..7f3fb8d
--- /dev/null
+++ b/test/e2e/resources/components/scripts/AdvancedWidget.brs
@@ -0,0 +1,2 @@
+sub init()
+end sub
\ No newline at end of file
diff --git a/test/e2e/resources/components/scripts/BaseWidget.brs b/test/e2e/resources/components/scripts/BaseWidget.brs
new file mode 100644
index 0000000..b3d813c
--- /dev/null
+++ b/test/e2e/resources/components/scripts/BaseWidget.brs
@@ -0,0 +1,3 @@
+' TODO: Uncomment when init logic has been implemented
+' sub init()
+' end sub
\ No newline at end of file
diff --git a/test/e2e/resources/components/scripts/NormalWidget.brs b/test/e2e/resources/components/scripts/NormalWidget.brs
new file mode 100644
index 0000000..b3d813c
--- /dev/null
+++ b/test/e2e/resources/components/scripts/NormalWidget.brs
@@ -0,0 +1,3 @@
+' TODO: Uncomment when init logic has been implemented
+' sub init()
+' end sub
\ No newline at end of file
diff --git a/test/e2e/resources/components/scripts/SupportScript.brs b/test/e2e/resources/components/scripts/SupportScript.brs
new file mode 100644
index 0000000..d956fe9
--- /dev/null
+++ b/test/e2e/resources/components/scripts/SupportScript.brs
@@ -0,0 +1,2 @@
+sub supportFunction()
+end sub
\ No newline at end of file
diff --git a/test/e2e/resources/conditionals.brs b/test/e2e/resources/conditionals.brs
index cb45fd7..3ac1670 100644
--- a/test/e2e/resources/conditionals.brs
+++ b/test/e2e/resources/conditionals.brs
@@ -41,3 +41,16 @@ else if foo < 10 then
 else
     print "from else"
 endif
+
+' inline if inside if
+if foo < 5
+    if foo < 3 then print "foo < 3"
+else if foo = 5
+    print 7
+end if
+
+' inline if with dotted expression
+aa = {}
+aa.value = 0
+if aa.value = 0 then aa.value = 8 else aa.value++
+print aa.value
\ No newline at end of file
diff --git a/test/e2e/resources/function/m-pointer-global.brs b/test/e2e/resources/function/m-pointer-global.brs
new file mode 100644
index 0000000..6bdfd37
--- /dev/null
+++ b/test/e2e/resources/function/m-pointer-global.brs
@@ -0,0 +1,16 @@
+sub main()
+    m.value = "value 1"
+    subf("value 2")
+    m.subObj = {func: subf}
+    m.value = "value 3"
+    m.subObj.func("value 4")
+end sub
+
+function subf(newValue as string) as boolean
+    print "old: " m.value
+    m.value = newValue
+    print "new: " m.value
+    g = GetGlobalAA()
+    print "glb: " g.value
+    return true
+end function
diff --git a/test/interpreter/Environment.test.js b/test/interpreter/Environment.test.js
index a9eb588..e7dbd19 100644
--- a/test/interpreter/Environment.test.js
+++ b/test/interpreter/Environment.test.js
@@ -130,6 +130,13 @@ describe("Environment", () => {
         expect(subEnv.has(identifier("mockScoped"))).toBe(true);
     });
 
+    it("maintains root-m scope between subenvironments", () => {
+        let rootM = new RoAssociativeArray([]);
+        env = new Environment(rootM);
+
+        expect(env.getRootM()).toBe(env.createSubEnvironment().getRootM());
+    });
+
     it("gets & sets mock objects on the mock scope", () => {
         let obj = new RoAssociativeArray([]);
         env.setMock("mock1", obj);
diff --git a/test/interpreter/Interpreter.test.js b/test/interpreter/Interpreter.test.js
new file mode 100644
index 0000000..fcce151
--- /dev/null
+++ b/test/interpreter/Interpreter.test.js
@@ -0,0 +1,63 @@
+const { getComponentDefinitionMap } = require("../../lib/componentprocessor");
+const { Interpreter, defaultExecutionOptions } = require("../../lib/interpreter");
+const path = require("path");
+const LexerParser = require("../../lib/LexerParser");
+
+jest.mock("fast-glob");
+jest.mock("fs");
+const fg = require("fast-glob");
+const fs = require("fs");
+
+const realFs = jest.requireActual("fs");
+
+describe("integration tests", () => {
+    beforeEach(() => {
+        fg.sync.mockImplementation(() => {
+            return [
+                "baseComponent.xml",
+                "extendedComponent.xml",
+                "scripts/baseComp.brs",
+                "scripts/extendedComp.brs",
+                "scripts/utility.brs",
+            ];
+        });
+        fs.readFile.mockImplementation((filename, _, cb) => {
+            resourcePath = path.join(__dirname, "resources", filename);
+            realFs.readFile(resourcePath, "utf8", (err, contents) => {
+                cb(/* no error */ null, contents);
+            });
+        });
+    });
+
+    afterEach(() => {
+        fg.sync.mockRestore();
+        fs.readFile.mockRestore();
+    });
+
+    test("interpreter environments have the right functions per component", async () => {
+        let componentMap = await getComponentDefinitionMap("/doesnt/matter");
+        componentMap.forEach(comp => {
+            comp.scripts = comp.scripts.map(script => {
+                script.uri = path.join("scripts/", path.parse(script.uri).base);
+                return script;
+            });
+        });
+        let interpreter = await Interpreter.withSubEnvsFromComponents(
+            componentMap,
+            LexerParser.getLexerParserFn(new Map(), defaultExecutionOptions)
+        );
+
+        let baseComp = interpreter.environment.nodeDefMap.get("BaseComponent");
+        expect(baseComp).not.toBeUndefined();
+        expect(baseComp.environment.module.size).toEqual(0);
+
+        let extendedComp = interpreter.environment.nodeDefMap.get("ExtendedComponent");
+        expect(extendedComp).not.toBeUndefined();
+        let actualFns = Array.from(extendedComp.environment.module).map(methodKV => {
+            let [fnName] = methodKV;
+            return fnName;
+        });
+
+        expect(actualFns).toEqual(["init", "test1", "utility"]);
+    });
+});
diff --git a/test/interpreter/resources/baseComponent.xml b/test/interpreter/resources/baseComponent.xml
new file mode 100644
index 0000000..7ca30a2
--- /dev/null
+++ b/test/interpreter/resources/baseComponent.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<component name="BaseComponent">
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/baseComponent.brs" />
+<children>
+    <Label name="label_a" />
+</children>
+</component>
diff --git a/test/interpreter/resources/extendedComponent.xml b/test/interpreter/resources/extendedComponent.xml
new file mode 100644
index 0000000..604769e
--- /dev/null
+++ b/test/interpreter/resources/extendedComponent.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<component name="ExtendedComponent" extends="BaseComponent">
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/extendedComponent.brs" />
+<script type="text/brightscript" uri="pkg:/test/componentprocessor/resources/scripts/utility.brs" />
+<children>
+        <Label name="label_b" />
+</children>
+</component>
diff --git a/test/interpreter/resources/scripts/baseComponent.brs b/test/interpreter/resources/scripts/baseComponent.brs
new file mode 100644
index 0000000..539c3b7
--- /dev/null
+++ b/test/interpreter/resources/scripts/baseComponent.brs
@@ -0,0 +1,5 @@
+' TODO: Uncomment when init logic has been implemented
+' sub init()
+'     ?"base component"
+'     x = 5
+' end sub
diff --git a/test/interpreter/resources/scripts/extendedComponent.brs b/test/interpreter/resources/scripts/extendedComponent.brs
new file mode 100644
index 0000000..f08d963
--- /dev/null
+++ b/test/interpreter/resources/scripts/extendedComponent.brs
@@ -0,0 +1,8 @@
+sub init()
+    ?"extended component"
+    x = 10
+end sub
+
+sub test1()
+    y = 100
+end sub
diff --git a/test/interpreter/resources/scripts/utility.brs b/test/interpreter/resources/scripts/utility.brs
new file mode 100644
index 0000000..4d553a5
--- /dev/null
+++ b/test/interpreter/resources/scripts/utility.brs
@@ -0,0 +1,3 @@
+sub utility()
+    ?"utility"
+end sub
diff --git a/test/lexer/Lexer.test.js b/test/lexer/Lexer.test.js
index 6b80b8e..b4d290c 100644
--- a/test/lexer/Lexer.test.js
+++ b/test/lexer/Lexer.test.js
@@ -423,6 +423,28 @@ describe("lexer", () => {
         });
     });
 
+    it("detects rem when used as keyword", () => {
+        let { tokens } = Lexer.scan("person.rem=true");
+        expect(tokens.map(t => t.kind)).toEqual([
+            Lexeme.Identifier,
+            Lexeme.Dot,
+            Lexeme.Identifier,
+            Lexeme.Equal,
+            Lexeme.True,
+            Lexeme.Eof,
+        ]);
+
+        //verify the location of `rem`
+        expect(tokens.map(t => [t.location.start.column, t.location.end.column])).toEqual([
+            [0, 6], // person
+            [6, 7], // .
+            [7, 10], // rem
+            [10, 11], // =
+            [11, 15], // true
+            [15, 16], // EOF
+        ]);
+    });
+
     describe("isToken", () => {
         it("returns booleans", () => {
             let location = {
diff --git a/test/parser/statement/__snapshots__/Misc.test.js.snap b/test/parser/statement/__snapshots__/Misc.test.js.snap
index ebbdc1f..f04585e 100644
--- a/test/parser/statement/__snapshots__/Misc.test.js.snap
+++ b/test/parser/statement/__snapshots__/Misc.test.js.snap
@@ -2643,6 +2643,61 @@ Array [
               },
             },
           },
+          DottedSet {
+            "name": Object {
+              "isReserved": true,
+              "kind": "Identifier",
+              "literal": undefined,
+              "location": Object {
+                "end": Object {
+                  "column": 26,
+                  "line": 43,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 23,
+                  "line": 43,
+                },
+              },
+              "text": "rem",
+            },
+            "obj": Variable {
+              "name": Object {
+                "isReserved": false,
+                "kind": "Identifier",
+                "literal": undefined,
+                "location": Object {
+                  "end": Object {
+                    "column": 22,
+                    "line": 43,
+                  },
+                  "file": "",
+                  "start": Object {
+                    "column": 16,
+                    "line": 43,
+                  },
+                },
+                "text": "person",
+              },
+            },
+            "value": Literal {
+              "_location": Object {
+                "end": Object {
+                  "column": 33,
+                  "line": 43,
+                },
+                "file": "",
+                "start": Object {
+                  "column": 29,
+                  "line": 43,
+                },
+              },
+              "value": BrsBoolean {
+                "kind": 1,
+                "value": true,
+              },
+            },
+          },
           DottedSet {
             "name": Object {
               "isReserved": true,
@@ -2661,7 +2716,7 @@ Array [
               },
               "text": "return",
             },
-            "obj": DottedGet {
+            "obj": Variable {
               "name": Object {
                 "isReserved": false,
                 "kind": "Identifier",
@@ -2679,25 +2734,6 @@ Array [
                 },
                 "text": "person",
               },
-              "obj": Variable {
-                "name": Object {
-                  "isReserved": false,
-                  "kind": "Identifier",
-                  "literal": undefined,
-                  "location": Object {
-                    "end": Object {
-                      "column": 22,
-                      "line": 43,
-                    },
-                    "file": "",
-                    "start": Object {
-                      "column": 16,
-                      "line": 43,
-                    },
-                  },
-                  "text": "person",
-                },
-              },
             },
             "value": Literal {
               "_location": Object {