diff --git a/src/core/brsTypes/components/BrsComponent.ts b/src/core/brsTypes/components/BrsComponent.ts index d25c94ee..84732730 100644 --- a/src/core/brsTypes/components/BrsComponent.ts +++ b/src/core/brsTypes/components/BrsComponent.ts @@ -132,4 +132,9 @@ export interface BrsIterable { * Resets the iteration sequence to the beginning of the iterable component. */ resetNext(): void; + + /** + * Update the iteration index to the next element in the iteration sequence. + */ + updateNext(): void; } diff --git a/src/core/brsTypes/components/RoArray.ts b/src/core/brsTypes/components/RoArray.ts index 5c81fc1e..2fd3d4bb 100644 --- a/src/core/brsTypes/components/RoArray.ts +++ b/src/core/brsTypes/components/RoArray.ts @@ -1,15 +1,16 @@ import { BrsType, isBrsString, isBrsNumber, Int32, Float } from ".."; import { BrsValue, ValueKind, BrsString, BrsBoolean, BrsInvalid, Comparable } from "../BrsType"; -import { BrsComponent, BrsIterable } from "./BrsComponent"; +import { BrsComponent } from "./BrsComponent"; import { Callable, StdlibArgument } from "../Callable"; import { Interpreter } from "../../interpreter"; import { RoAssociativeArray } from "./RoAssociativeArray"; +import { BrsArray, IfArray, IfArrayGet, IfArraySet } from "../interfaces/IfArray"; import { IfEnum } from "../interfaces/IfEnum"; -export class RoArray extends BrsComponent implements BrsValue, BrsIterable { +export class RoArray extends BrsComponent implements BrsValue, BrsArray { readonly kind = ValueKind.Object; - private readonly resizable: boolean = true; - private maxSize = 0; + readonly resizable: boolean = true; + maxSize = 0; elements: BrsType[]; enumIndex: number; @@ -36,25 +37,28 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable { ); } this.enumIndex = this.elements.length ? 0 : -1; + const ifArray = new IfArray(this); + const ifArrayGet = new IfArrayGet(this); + const ifArraySet = new IfArraySet(this); const ifEnum = new IfEnum(this); this.registerMethods({ ifArray: [ - this.peek, - this.pop, - this.push, - this.shift, - this.unshift, - this.delete, - this.count, - this.clear, - this.append, + ifArray.peek, + ifArray.pop, + ifArray.push, + ifArray.shift, + ifArray.unshift, + ifArray.delete, + ifArray.count, + ifArray.clear, + ifArray.append, ], - ifArrayGet: [this.getEntry], - ifArraySet: [this.setEntry], + ifArrayGet: [ifArrayGet.getEntry], + ifArraySet: [ifArraySet.setEntry], ifArrayJoin: [this.join], ifArraySort: [this.sort, this.sortBy, this.reverse], - ifArraySizeInfo: [this.capacity, this.isResizable], ifArraySlice: [this.slice], + ifArraySizeInfo: [this.capacity, this.isResizable], ifEnum: [ifEnum.isEmpty, ifEnum.isNext, ifEnum.next, ifEnum.reset], }); } @@ -86,6 +90,42 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable { return this.elements.slice(); } + add(element: BrsType, onTail: boolean = true) { + this.addChildRef(element); + if (onTail) { + this.elements.push(element); + } else { + this.elements.unshift(element); + } + this.updateNext(true); + } + + remove(index: number) { + let removed; + if (index === 0) { + removed = this.elements.shift(); + } else if (index === this.tail()) { + removed = this.elements.pop(); + } else { + removed = this.elements.splice(index, 1)[0]; + } + this.updateNext(); + this.removeChildRef(removed); + return removed; + } + + clear() { + this.elements.forEach((element) => { + this.removeChildRef(element); + }); + this.elements.length = 0; + this.enumIndex = -1; + } + + tail() { + return this.elements.length - 1; + } + get(index: BrsType) { switch (index.kind) { case ValueKind.Float: @@ -128,16 +168,19 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable { this.enumIndex = this.elements.length > 0 ? 0 : -1; } - updateNext() { + updateNext(grow?: boolean, factor?: number) { const hasItems = this.elements.length > 0; if (this.enumIndex === -1 && hasItems) { this.enumIndex = 0; } else if (this.enumIndex >= this.elements.length || !hasItems) { this.enumIndex = -1; } + if (grow) { + this.updateCapacity(factor ?? 1.25); + } } - updateCapacity(growthFactor = 0) { + private updateCapacity(growthFactor = 0) { if (this.resizable && growthFactor > 0) { if (this.elements.length > 0 && this.elements.length > this.maxSize) { let count = this.elements.length - 1; @@ -153,7 +196,7 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable { } } - aaCompare( + private aaCompare( fieldName: BrsString, flags: BrsString, a: RoAssociativeArray, @@ -196,179 +239,6 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable { } } - // ifArray - private readonly peek = new Callable("peek", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.elements[this.elements.length - 1] || BrsInvalid.Instance; - }, - }); - - private readonly pop = new Callable("pop", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - const removed = this.elements.pop(); - this.removeChildRef(removed); - this.updateNext(); - return removed || BrsInvalid.Instance; - }, - }); - - private readonly push = new Callable("push", { - signature: { - args: [new StdlibArgument("talue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (interpreter: Interpreter, tvalue: BrsType) => { - if (this.resizable || this.elements.length < this.maxSize) { - this.addChildRef(tvalue); - this.elements.push(tvalue); - this.updateNext(); - this.updateCapacity(1.25); - } else { - interpreter.stderr.write( - `warning,BRIGHTSCRIPT: ERROR: roArray.Push: set ignored for index out of bounds on non-resizable array: ${interpreter.formatLocation()}` - ); - } - return BrsInvalid.Instance; - }, - }); - - private readonly shift = new Callable("shift", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - const removed = this.elements.shift(); - this.removeChildRef(removed); - this.updateNext(); - return removed || BrsInvalid.Instance; - }, - }); - - private readonly unshift = new Callable("unshift", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (interpreter: Interpreter, tvalue: BrsType) => { - if (this.resizable || this.elements.length < this.maxSize) { - this.addChildRef(tvalue); - this.elements.unshift(tvalue); - this.updateNext(); - this.updateCapacity(1.25); - } else { - interpreter.stderr.write( - `warning,BRIGHTSCRIPT: ERROR: roArray.Unshift: set ignored for index out of bounds on non-resizable array: ${interpreter.formatLocation()}` - ); - } - return BrsInvalid.Instance; - }, - }); - - private readonly delete = new Callable("delete", { - signature: { - args: [new StdlibArgument("index", ValueKind.Int32)], - returns: ValueKind.Boolean, - }, - impl: (_: Interpreter, index: Int32) => { - if (index.lessThan(new Int32(0)).toBoolean()) { - return BrsBoolean.False; - } - const deleted = this.elements.splice(index.getValue(), 1); - deleted.forEach((element) => { - this.removeChildRef(element); - }); - this.updateNext(); - return BrsBoolean.from(deleted.length > 0); - }, - }); - - private readonly count = new Callable("count", { - signature: { - args: [], - returns: ValueKind.Int32, - }, - impl: (_: Interpreter) => { - return new Int32(this.elements.length); - }, - }); - - private readonly clear = new Callable("clear", { - signature: { - args: [], - returns: ValueKind.Void, - }, - impl: (_: Interpreter) => { - this.elements.forEach((element) => { - this.removeChildRef(element); - }); - this.elements = []; - this.enumIndex = -1; - return BrsInvalid.Instance; - }, - }); - - private readonly append = new Callable("append", { - signature: { - args: [new StdlibArgument("array", ValueKind.Object)], - returns: ValueKind.Void, - }, - impl: (interpreter: Interpreter, array: BrsComponent) => { - if (!(array instanceof RoArray)) { - interpreter.stderr.write( - `warning,BRIGHTSCRIPT: ERROR: roArray.Append: invalid parameter type ${array.getComponentName()}: ${interpreter.formatLocation()}` - ); - return BrsInvalid.Instance; - } - - if (this.resizable || this.elements.length + array.elements.length <= this.maxSize) { - array.elements.forEach((element) => { - this.addChildRef(element); - }); - this.elements = [ - ...this.elements, - ...array.elements.filter((element) => !!element), // don't copy "holes" where no value exists - ]; - this.updateNext(); - this.updateCapacity(); - } - return BrsInvalid.Instance; - }, - }); - - // ifArrayGet - private readonly getEntry = new Callable("getEntry", { - signature: { - args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter, index: Int32 | Float) => { - return this.elements[Math.trunc(index.getValue())] || BrsInvalid.Instance; - }, - }); - - // ifArraySet - private readonly setEntry = new Callable("setEntry", { - signature: { - args: [ - new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float), - new StdlibArgument("tvalue", ValueKind.Dynamic), - ], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, index: Int32 | Float, tvalue: BrsType) => { - return this.set(index, tvalue); - }, - }); - // ifArrayJoin private readonly join = new Callable("join", { signature: { diff --git a/src/core/brsTypes/components/RoList.ts b/src/core/brsTypes/components/RoList.ts index 0b0525c0..66a3370a 100644 --- a/src/core/brsTypes/components/RoList.ts +++ b/src/core/brsTypes/components/RoList.ts @@ -1,12 +1,14 @@ -import { BrsType, Float, Int32, RoArray } from ".."; +import { BrsType } from ".."; import { BrsValue, ValueKind, BrsBoolean, BrsInvalid } from "../BrsType"; -import { BrsComponent, BrsIterable } from "./BrsComponent"; -import { Callable, StdlibArgument } from "../Callable"; -import { Interpreter } from "../../interpreter"; +import { BrsComponent } from "./BrsComponent"; +import { BrsList, IfList, IfListToArray } from "../interfaces/IfList"; +import { IfArray, IfArrayGet, IfArraySet } from "../interfaces/IfArray"; import { IfEnum } from "../interfaces/IfEnum"; -export class RoList extends BrsComponent implements BrsValue, BrsIterable { +export class RoList extends BrsComponent implements BrsValue, BrsList { readonly kind = ValueKind.Object; + readonly resizable: boolean = true; + maxSize = 0; elements: BrsType[]; listIndex: number; enumIndex: number; @@ -22,33 +24,38 @@ export class RoList extends BrsComponent implements BrsValue, BrsIterable { } this.listIndex = -1; this.enumIndex = -1; + const ifList = new IfList(this); + const ifListToArray = new IfListToArray(this); + const ifArray = new IfArray(this); + const ifArrayGet = new IfArrayGet(this); + const ifArraySet = new IfArraySet(this); const ifEnum = new IfEnum(this); this.registerMethods({ ifList: [ - this.addHead, - this.addTail, - this.getHead, - this.getTail, - this.removeHead, - this.removeTail, - this.resetIndex, - this.getIndex, - this.removeIndex, + ifList.addHead, + ifList.addTail, + ifList.getHead, + ifList.getTail, + ifList.removeHead, + ifList.removeTail, + ifList.resetIndex, + ifList.getIndex, + ifList.removeIndex, ], - ifListToArray: [this.toArray], + ifListToArray: [ifListToArray.toArray], ifArray: [ - this.peek, - this.pop, - this.push, - this.shift, - this.unshift, - this.delete, - this.count, - this.clear, - this.append, + ifArray.peek, + ifArray.pop, + ifArray.push, + ifArray.shift, + ifArray.unshift, + ifArray.delete, + ifArray.count, + ifArray.clear, + ifArray.append, ], - ifArrayGet: [this.getEntry], - ifArraySet: [this.setEntry], + ifArrayGet: [ifArrayGet.getEntry], + ifArraySet: [ifArraySet.setEntry], ifEnum: [ifEnum.isEmpty, ifEnum.isNext, ifEnum.next, ifEnum.reset], }); } @@ -171,6 +178,15 @@ export class RoList extends BrsComponent implements BrsValue, BrsIterable { return removed; } + clear() { + this.elements.forEach((element) => { + this.removeChildRef(element); + }); + this.elements.length = 0; + this.listIndex = -1; + this.enumIndex = -1; + } + getCurrent() { const index = this.listIndex; if (index >= 0) { @@ -182,6 +198,11 @@ export class RoList extends BrsComponent implements BrsValue, BrsIterable { return this.elements[index]; } + resetCurrent() { + this.listIndex = this.elements.length > 0 ? 0 : -1; + return this.listIndex === 0; + } + length() { return this.elements.length; } @@ -207,264 +228,4 @@ export class RoList extends BrsComponent implements BrsValue, BrsIterable { value.removeReference(); } } - - //--------------------------------- ifList --------------------------------- - - /** Adds typed value to head of list */ - private readonly addHead = new Callable("addHead", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: BrsType) => { - this.add(tvalue, false); - return BrsInvalid.Instance; - }, - }); - - /** Adds typed value to tail of list */ - private readonly addTail = new Callable("addTail", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: BrsType) => { - this.elements.push(tvalue); - return BrsInvalid.Instance; - }, - }); - - /** Gets the entry at head of list and keep entry in list */ - private readonly getHead = new Callable("getHead", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.elements[0] || BrsInvalid.Instance; - }, - }); - - /** Gets the Object at tail of List and keep Object in list */ - private readonly getTail = new Callable("getTail", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.elements[this.tail()] || BrsInvalid.Instance; - }, - }); - - /** Removes entry at head of list */ - private readonly removeHead = new Callable("removeHead", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.remove(0) || BrsInvalid.Instance; - }, - }); - - /** Removes entry at tail of list */ - private readonly removeTail = new Callable("removeTail", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.remove(this.tail()) || BrsInvalid.Instance; - }, - }); - - /** Resets the current index or position in list to the head element */ - private readonly resetIndex = new Callable("resetIndex", { - signature: { - args: [], - returns: ValueKind.Boolean, - }, - impl: (_: Interpreter) => { - this.listIndex = this.elements.length > 0 ? 0 : -1; - return BrsBoolean.from(this.listIndex === 0); - }, - }); - - /** Gets the entry at current index or position from the list and increments the index or position in the list */ - private readonly getIndex = new Callable("getIndex", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.getCurrent() ?? BrsInvalid.Instance; - }, - }); - - /** Removes the entry at the current index or position from the list and increments the index or position in the list */ - private readonly removeIndex = new Callable("removeIndex", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.remove(this.listIndex) || BrsInvalid.Instance; - }, - }); - - //--------------------------------- ifListToArray --------------------------------- - - /** Returns an roArray containing the same elements as the list */ - private readonly toArray = new Callable("toArray", { - signature: { - args: [], - returns: ValueKind.Object, - }, - impl: (_: Interpreter) => { - return new RoArray(this.elements); - }, - }); - - //--------------------------------- ifArray --------------------------------- - - private readonly peek = new Callable("peek", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.elements[this.tail()] || BrsInvalid.Instance; - }, - }); - - private readonly pop = new Callable("pop", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.remove(this.tail()) || BrsInvalid.Instance; - }, - }); - - private readonly push = new Callable("push", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: BrsType) => { - this.add(tvalue, true); - return BrsInvalid.Instance; - }, - }); - - private readonly shift = new Callable("shift", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.remove(0) || BrsInvalid.Instance; - }, - }); - - private readonly unshift = new Callable("unshift", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: BrsType) => { - this.add(tvalue, false); - return BrsInvalid.Instance; - }, - }); - - private readonly delete = new Callable("delete", { - signature: { - args: [new StdlibArgument("index", ValueKind.Dynamic)], - returns: ValueKind.Boolean, - }, - impl: (_: Interpreter, index: BrsType) => { - if ( - (index.kind === ValueKind.Int32 || index.kind === ValueKind.Float) && - index.getValue() >= 0 - ) { - return this.remove(index.getValue()) ? BrsBoolean.True : BrsBoolean.False; - } - return BrsBoolean.False; - }, - }); - - private readonly count = new Callable("count", { - signature: { - args: [], - returns: ValueKind.Int32, - }, - impl: (_: Interpreter) => { - return new Int32(this.elements.length); - }, - }); - - private readonly clear = new Callable("clear", { - signature: { - args: [], - returns: ValueKind.Void, - }, - impl: (_: Interpreter) => { - this.elements = new Array(); - this.listIndex = -1; - this.enumIndex = -1; - return BrsInvalid.Instance; - }, - }); - - private readonly append = new Callable("append", { - signature: { - args: [new StdlibArgument("array", ValueKind.Object)], - returns: ValueKind.Void, - }, - impl: (interpreter: Interpreter, array: BrsComponent) => { - if (!(array instanceof RoList)) { - interpreter.stderr.write( - `warning,BRIGHTSCRIPT: ERROR: roList.Append: invalid parameter type ${array.getComponentName()}: ${interpreter.formatLocation()}` - ); - return BrsInvalid.Instance; - } - - this.elements = [ - ...this.elements, - ...array.elements.filter((element) => !!element), // don't copy "holes" where no value exists - ]; - - return BrsInvalid.Instance; - }, - }); - - //------------------------------- ifArrayGet -------------------------------- - - /** Returns an array entry based on the provided index. */ - private readonly getEntry = new Callable("getEntry", { - signature: { - args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter, index: Int32 | Float) => { - return this.elements[Math.trunc(index.getValue())] || BrsInvalid.Instance; - }, - }); - - //------------------------------- ifArraySet -------------------------------- - - private readonly setEntry = new Callable("setEntry", { - signature: { - args: [ - new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float), - new StdlibArgument("tvalue", ValueKind.Dynamic), - ], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, index: Int32 | Float, tvalue: BrsType) => { - return this.set(index, tvalue); - }, - }); } diff --git a/src/core/brsTypes/components/RoXMLElement.ts b/src/core/brsTypes/components/RoXMLElement.ts index 714214e8..32b12a72 100644 --- a/src/core/brsTypes/components/RoXMLElement.ts +++ b/src/core/brsTypes/components/RoXMLElement.ts @@ -80,6 +80,10 @@ export class RoXMLElement extends BrsComponent implements BrsValue, BrsIterable this.childElements().resetNext(); } + updateNext() { + this.childElements().updateNext(); + } + getAttribute(index: BrsType) { if (index.kind !== ValueKind.String) { throw new Error("XML Element attribute must be strings"); diff --git a/src/core/brsTypes/components/RoXMLList.ts b/src/core/brsTypes/components/RoXMLList.ts index 3a6b4489..038e9a15 100644 --- a/src/core/brsTypes/components/RoXMLList.ts +++ b/src/core/brsTypes/components/RoXMLList.ts @@ -1,20 +1,28 @@ -import { BrsType, Float, Int32 } from ".."; +import { BrsType } from ".."; import { BrsValue, ValueKind, BrsBoolean, BrsInvalid, BrsString } from "../BrsType"; -import { BrsComponent, BrsIterable } from "./BrsComponent"; +import { BrsComponent } from "./BrsComponent"; import { Callable, StdlibArgument } from "../Callable"; import { Interpreter } from "../../interpreter"; import { RoList } from "./RoList"; -import { RoArray } from "./RoArray"; import { RoXMLElement } from "./RoXMLElement"; +import { BrsList, IfList, IfListToArray } from "../interfaces/IfList"; +import { IfArray, IfArrayGet, IfArraySet } from "../interfaces/IfArray"; import { IfEnum } from "../interfaces/IfEnum"; -export class RoXMLList extends BrsComponent implements BrsValue, BrsIterable { +export class RoXMLList extends BrsComponent implements BrsValue, BrsList { readonly kind = ValueKind.Object; - private roList: RoList; + private readonly roList: RoList; + readonly resizable: boolean = true; + maxSize = 0; constructor() { super("roXMLList"); this.roList = new RoList(); + const ifList = new IfList(this); + const ifListToArray = new IfListToArray(this); + const ifArray = new IfArray(this); + const ifArrayGet = new IfArrayGet(this); + const ifArraySet = new IfArraySet(this); const ifEnum = new IfEnum(this); this.registerMethods({ ifXMLList: [ @@ -26,25 +34,38 @@ export class RoXMLList extends BrsComponent implements BrsValue, BrsIterable { this.simplify, ], ifList: [ - this.addHead, - this.addTail, - this.getHead, - this.getTail, - this.removeHead, - this.removeTail, - this.count, - this.clear, - this.resetIndex, - this.getIndex, - this.removeIndex, + ifList.addHead, + ifList.addTail, + ifList.getHead, + ifList.getTail, + ifList.removeHead, + ifList.removeTail, + ifList.resetIndex, + ifList.getIndex, + ifList.removeIndex, ], - ifArrayGet: [this.getEntry], - ifArraySet: [this.setEntry], + ifListToArray: [ifListToArray.toArray], + ifArray: [ + ifArray.peek, + ifArray.pop, + ifArray.push, + ifArray.shift, + ifArray.unshift, + ifArray.delete, + ifArray.count, + ifArray.clear, + ifArray.append, + ], + ifArrayGet: [ifArrayGet.getEntry], + ifArraySet: [ifArraySet.setEntry], ifEnum: [ifEnum.isEmpty, ifEnum.isNext, ifEnum.next, ifEnum.reset], - ifListToArray: [this.toArray], }); } + get listIndex(): number { + return this.roList.listIndex; + } + toString(parent?: BrsType): string { if (parent) { return ""; @@ -115,14 +136,38 @@ export class RoXMLList extends BrsComponent implements BrsValue, BrsIterable { this.roList.resetNext(); } + updateNext(): void { + this.roList.updateNext(); + } + add(element: RoXMLElement) { this.roList.add(element); } + remove(index: number) { + return this.roList.remove(index); + } + + clear(): void { + this.roList.clear(); + } + length() { return this.roList.length(); } + getCurrent() { + return this.roList.getCurrent(); + } + + resetCurrent(): boolean { + return this.roList.resetCurrent(); + } + + tail(): number { + return this.roList.tail(); + } + namedElements(name: string, ci: boolean) { if (ci) { name = name.toLocaleLowerCase(); @@ -234,172 +279,4 @@ export class RoXMLList extends BrsComponent implements BrsValue, BrsIterable { return this; }, }); - - //--------------------------------- ifList --------------------------------- - - /** Adds typed value to head of list */ - private readonly addHead = new Callable("addHead", { - signature: { - args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: RoXMLElement) => { - this.roList.add(tvalue, false); - return BrsInvalid.Instance; - }, - }); - - /** Adds typed value to tail of list */ - private readonly addTail = new Callable("addTail", { - signature: { - args: [new StdlibArgument("talue", ValueKind.Dynamic)], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, tvalue: RoXMLElement) => { - this.roList.add(tvalue, true); - return BrsInvalid.Instance; - }, - }); - - /** Gets the entry at head of list and keep entry in list */ - private readonly getHead = new Callable("getHead", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.elements[0] || BrsInvalid.Instance; - }, - }); - - /** Gets the Object at tail of List and keep Object in list */ - private readonly getTail = new Callable("getTail", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.elements[this.roList.tail()] || BrsInvalid.Instance; - }, - }); - - /** Removes entry at head of list */ - private readonly removeHead = new Callable("removeHead", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.remove(0) || BrsInvalid.Instance; - }, - }); - - /** Removes entry at tail of list */ - private readonly removeTail = new Callable("removeTail", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.remove(this.roList.tail()) || BrsInvalid.Instance; - }, - }); - - /** Resets the current index or position in list to the head element */ - private readonly resetIndex = new Callable("resetIndex", { - signature: { - args: [], - returns: ValueKind.Boolean, - }, - impl: (_: Interpreter) => { - this.roList.listIndex = this.length() > 0 ? 0 : -1; - return BrsBoolean.from(this.roList.listIndex === 0); - }, - }); - - /** Gets the entry at current index or position from the list and increments the index or position in the list */ - private readonly getIndex = new Callable("getIndex", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.getCurrent() ?? BrsInvalid.Instance; - }, - }); - - /** Removes the entry at the current index or position from the list and increments the index or position in the list */ - private readonly removeIndex = new Callable("removeIndex", { - signature: { - args: [], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter) => { - return this.roList.remove(this.roList.listIndex) || BrsInvalid.Instance; - }, - }); - - /** Returns the number of elements in list */ - private readonly count = new Callable("count", { - signature: { - args: [], - returns: ValueKind.Int32, - }, - impl: (_: Interpreter) => { - return new Int32(this.length()); - }, - }); - - /** Removes all elements from list */ - private readonly clear = new Callable("clear", { - signature: { - args: [], - returns: ValueKind.Void, - }, - impl: (_: Interpreter) => { - this.roList = new RoList(); - return BrsInvalid.Instance; - }, - }); - - //--------------------------------- ifListToArray --------------------------------- - - /** Returns an roArray containing the same elements as the list */ - private readonly toArray = new Callable("toArray", { - signature: { - args: [], - returns: ValueKind.Object, - }, - impl: (_: Interpreter) => { - return new RoArray(this.roList.elements); - }, - }); - - //------------------------------- ifArrayGet -------------------------------- - - /** Returns an array entry based on the provided index. */ - private readonly getEntry = new Callable("getEntry", { - signature: { - args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)], - returns: ValueKind.Dynamic, - }, - impl: (_: Interpreter, index: Int32 | Float) => { - return this.roList.elements[Math.trunc(index.getValue())] || BrsInvalid.Instance; - }, - }); - - //------------------------------- ifArraySet -------------------------------- - - private readonly setEntry = new Callable("setEntry", { - signature: { - args: [ - new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float), - new StdlibArgument("tvalue", ValueKind.Dynamic), - ], - returns: ValueKind.Void, - }, - impl: (_: Interpreter, index: Int32 | Float, tvalue: BrsType) => { - return this.set(index, tvalue); - }, - }); } diff --git a/src/core/brsTypes/interfaces/IfArray.ts b/src/core/brsTypes/interfaces/IfArray.ts new file mode 100644 index 00000000..29b08e00 --- /dev/null +++ b/src/core/brsTypes/interfaces/IfArray.ts @@ -0,0 +1,212 @@ +import { BrsComponent, BrsIterable, BrsType, Float, Int32 } from ".."; +import { Interpreter } from "../../interpreter"; +import { BrsBoolean, BrsInvalid, ValueKind } from "../BrsType"; +import { Callable, StdlibArgument } from "../Callable"; + +export class IfArray { + private readonly component: BrsComponent & BrsArray; + private readonly name: string; + + constructor(component: BrsComponent & BrsArray) { + this.component = component; + this.name = component.getComponentName(); + } + + readonly peek = new Callable("peek", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + const elements = this.component.getElements(); + return elements[elements.length - 1] || BrsInvalid.Instance; + }, + }); + + readonly pop = new Callable("pop", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.remove(this.component.tail()) || BrsInvalid.Instance; + }, + }); + + readonly push = new Callable("push", { + signature: { + args: [new StdlibArgument("talue", ValueKind.Dynamic)], + returns: ValueKind.Void, + }, + impl: (interpreter: Interpreter, tvalue: BrsType) => { + const elements = this.component.getValue(); + if (this.component.resizable || elements.length < this.component.maxSize) { + this.component.add(tvalue, true); + } else { + interpreter.stderr.write( + `warning,BRIGHTSCRIPT: ERROR: ${ + this.name + }.Push: set ignored for index out of bounds on non-resizable array: ${interpreter.formatLocation()}` + ); + } + return BrsInvalid.Instance; + }, + }); + + readonly shift = new Callable("shift", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.remove(0) || BrsInvalid.Instance; + }, + }); + + readonly unshift = new Callable("unshift", { + signature: { + args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], + returns: ValueKind.Void, + }, + impl: (interpreter: Interpreter, tvalue: BrsType) => { + const elements = this.component.getValue(); + if (this.component.resizable || elements.length < this.component.maxSize) { + this.component.add(tvalue, false); + } else { + interpreter.stderr.write( + `warning,BRIGHTSCRIPT: ERROR: ${ + this.name + }.Unshift: set ignored for index out of bounds on non-resizable array: ${interpreter.formatLocation()}` + ); + } + return BrsInvalid.Instance; + }, + }); + + readonly delete = new Callable("delete", { + signature: { + args: [new StdlibArgument("index", ValueKind.Int32)], + returns: ValueKind.Boolean, + }, + impl: (_: Interpreter, index: Int32) => { + if (index.lessThan(new Int32(0)).toBoolean()) { + return BrsBoolean.False; + } + return this.component.remove(index.getValue()) ? BrsBoolean.True : BrsBoolean.False; + }, + }); + + readonly count = new Callable("count", { + signature: { + args: [], + returns: ValueKind.Int32, + }, + impl: (_: Interpreter) => { + return new Int32(this.component.getValue().length); + }, + }); + + readonly clear = new Callable("clear", { + signature: { + args: [], + returns: ValueKind.Void, + }, + impl: (_: Interpreter) => { + this.component.clear(); + return BrsInvalid.Instance; + }, + }); + + readonly append = new Callable("append", { + signature: { + args: [new StdlibArgument("array", ValueKind.Object)], + returns: ValueKind.Void, + }, + impl: (interpreter: Interpreter, array: BrsComponent & BrsArray) => { + if (this.name !== array.getComponentName()) { + interpreter.stderr.write( + `warning,BRIGHTSCRIPT: ERROR: ${ + this.name + }.Append: invalid parameter type ${array.getComponentName()}: ${interpreter.formatLocation()}` + ); + return BrsInvalid.Instance; + } + const comp = this.component; + const elem = comp.getValue(); + if (comp.resizable || elem.length + array.getValue().length <= comp.maxSize) { + // don't copy "holes" where no value exists + const noGap = array.getValue().filter((element) => { + if (element) { + if (comp.addChildRef) { + comp.addChildRef(element); + } + return true; + } + return false; + }); + elem.push(...noGap); + this.component.updateNext(true, 0); + } + return BrsInvalid.Instance; + }, + }); +} + +export class IfArrayGet { + readonly kind = ValueKind.Object; + private readonly component: BrsComponent & BrsArray; + + constructor(component: BrsComponent & BrsArray) { + this.component = component; + } + + readonly getEntry = new Callable("getEntry", { + signature: { + args: [new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float)], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter, index: Int32 | Float) => { + return this.component.get(index) || BrsInvalid.Instance; + }, + }); +} + +export class IfArraySet { + readonly kind = ValueKind.Object; + private readonly component: BrsComponent & BrsArray; + + constructor(component: BrsComponent & BrsArray) { + this.component = component; + } + readonly setEntry = new Callable("setEntry", { + signature: { + args: [ + new StdlibArgument("index", ValueKind.Int32 | ValueKind.Float), + new StdlibArgument("tvalue", ValueKind.Dynamic), + ], + returns: ValueKind.Void, + }, + impl: (_: Interpreter, index: Int32 | Float, tvalue: BrsType) => { + return this.component.set(index, tvalue); + }, + }); +} + +export interface BrsArray extends BrsIterable { + readonly resizable: boolean; + readonly maxSize: number; + + getValue(): BrsType[]; + + add(value: BrsType, toTail: boolean): void; + + remove(index: number): BrsType | undefined; + + clear(): void; + + updateNext(grow?: boolean, index?: number): void; + + tail(): number; + + addChildRef?: (value: BrsType | undefined) => void; +} diff --git a/src/core/brsTypes/interfaces/IfList.ts b/src/core/brsTypes/interfaces/IfList.ts new file mode 100644 index 00000000..e7d030fa --- /dev/null +++ b/src/core/brsTypes/interfaces/IfList.ts @@ -0,0 +1,144 @@ +import { BrsComponent, BrsType, RoArray } from ".."; +import { Interpreter } from "../../interpreter"; +import { BrsBoolean, BrsInvalid, ValueKind } from "../BrsType"; +import { Callable, StdlibArgument } from "../Callable"; +import { BrsArray } from "./IfArray"; + +export class IfList { + private readonly component: BrsComponent & BrsList; + + constructor(component: BrsComponent & BrsList) { + this.component = component; + } + + /** Adds typed value to head of list */ + readonly addHead = new Callable("addHead", { + signature: { + args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], + returns: ValueKind.Void, + }, + impl: (_: Interpreter, tvalue: BrsType) => { + this.component.add(tvalue, false); + return BrsInvalid.Instance; + }, + }); + + /** Adds typed value to tail of list */ + readonly addTail = new Callable("addTail", { + signature: { + args: [new StdlibArgument("tvalue", ValueKind.Dynamic)], + returns: ValueKind.Void, + }, + impl: (_: Interpreter, tvalue: BrsType) => { + const elements = this.component.getValue(); + elements.push(tvalue); + return BrsInvalid.Instance; + }, + }); + + /** Gets the entry at head of list and keep entry in list */ + readonly getHead = new Callable("getHead", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + const elements = this.component.getValue(); + return elements[0] || BrsInvalid.Instance; + }, + }); + + /** Gets the Object at tail of List and keep Object in list */ + readonly getTail = new Callable("getTail", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + const elements = this.component.getValue(); + return elements[this.component.tail()] || BrsInvalid.Instance; + }, + }); + + /** Removes entry at head of list */ + readonly removeHead = new Callable("removeHead", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.remove(0) || BrsInvalid.Instance; + }, + }); + + /** Removes entry at tail of list */ + readonly removeTail = new Callable("removeTail", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.remove(this.component.tail()) || BrsInvalid.Instance; + }, + }); + + /** Resets the current index or position in list to the head element */ + readonly resetIndex = new Callable("resetIndex", { + signature: { + args: [], + returns: ValueKind.Boolean, + }, + impl: (_: Interpreter) => { + return BrsBoolean.from(this.component.resetCurrent()); + }, + }); + + /** Gets the entry at current index or position from the list and increments the index or position in the list */ + readonly getIndex = new Callable("getIndex", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.getCurrent() ?? BrsInvalid.Instance; + }, + }); + + /** Removes the entry at the current index or position from the list and increments the index or position in the list */ + readonly removeIndex = new Callable("removeIndex", { + signature: { + args: [], + returns: ValueKind.Dynamic, + }, + impl: (_: Interpreter) => { + return this.component.remove(this.component.listIndex) || BrsInvalid.Instance; + }, + }); +} + +export class IfListToArray { + readonly kind = ValueKind.Object; + private readonly component: BrsComponent & BrsArray; + + constructor(component: BrsComponent & BrsArray) { + this.component = component; + } + /** Returns an roArray containing the same elements as the list */ + readonly toArray = new Callable("toArray", { + signature: { + args: [], + returns: ValueKind.Object, + }, + impl: (_: Interpreter) => { + return new RoArray(this.component.getValue()); + }, + }); +} + +export interface BrsList extends BrsArray { + readonly listIndex: number; + + getCurrent(): BrsType; + + resetCurrent(): boolean; +} diff --git a/test/e2e/BrsComponents.test.js b/test/e2e/BrsComponents.test.js index 50c9bdae..e1842220 100644 --- a/test/e2e/BrsComponents.test.js +++ b/test/e2e/BrsComponents.test.js @@ -360,6 +360,7 @@ describe("end to end brightscript functions", () => { `getNamedElementsCi("child1") count = 2`, "name of first child = Child1", "mame of second child = CHILD1", + "", ]); }); }); diff --git a/test/e2e/resources/components/roXMLElement.brs b/test/e2e/resources/components/roXMLElement.brs index 100fc33b..35c9c306 100644 --- a/test/e2e/resources/components/roXMLElement.brs +++ b/test/e2e/resources/components/roXMLElement.brs @@ -11,4 +11,5 @@ sub main() ?"getNamedElementsCi(""child1"") count = " children.count() ?"name of first child = "children[0].getName() ?"mame of second child = "children[1].getName() + ?children.peek() end sub \ No newline at end of file