Skip to content

Commit

Permalink
feat(components): Implemented missing ifEnum methods in roArray a…
Browse files Browse the repository at this point in the history
…nd `roAssociativeArray` (#33)

* Implements missing methods from `ifEnum` in `roArray` and `roAssociativeArray` #26

* Implemented unit tests

* Prettier fix

---------

Co-authored-by: Bronley Plumb <[email protected]>
  • Loading branch information
lvcabral and TwitchBronBron authored Dec 1, 2023
1 parent e98151c commit 6e5b422
Show file tree
Hide file tree
Showing 5 changed files with 367 additions and 66 deletions.
193 changes: 148 additions & 45 deletions src/brsTypes/components/RoArray.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { BrsType, isBrsString, isBrsNumber, Int32 } from "..";
import { BrsValue, ValueKind, BrsString, BrsBoolean, BrsInvalid, Comparable } from "../BrsType";
import { BrsType, isBrsString, isBrsNumber, isBrsBoolean } from "..";
import { BrsComponent, BrsIterable } from "./BrsComponent";
import { Callable, StdlibArgument } from "../Callable";
import { Interpreter } from "../../interpreter";
import { Int32 } from "../Int32";
import { RoAssociativeArray } from "./RoAssociativeArray";

/**
Expand All @@ -28,7 +27,7 @@ function getTypeSortIndex(a: BrsType): number {
}

/**
* Sorts two BrsTypes in the order that ROku would sort them
* Sorts two BrsTypes in the order that Roku would sort them
* @param originalArray A copy of the original array. Used to get the order of items
* @param a
* @param b
Expand Down Expand Up @@ -82,11 +81,13 @@ function sortCompare(

export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
readonly kind = ValueKind.Object;
private elements: BrsType[];
elements: BrsType[];
enumIndex: number;

constructor(elements: BrsType[]) {
super("roArray");
this.elements = elements;
this.enumIndex = elements.length ? 0 : -1;
this.registerMethods({
ifArray: [
this.peek,
Expand All @@ -99,9 +100,11 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
this.clear,
this.append,
],
ifArrayGet: [this.getEntry],
ifArraySet: [this.setEntry],
ifArrayJoin: [this.join],
ifArraySort: [this.sort, this.sortBy, this.reverse],
ifEnum: [this.isEmpty],
ifEnum: [this.isEmpty, this.isNext, this.next, this.reset],
});
}

Expand Down Expand Up @@ -132,33 +135,80 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {

get(index: BrsType) {
switch (index.kind) {
case ValueKind.Float:
return this.getElements()[Math.trunc(index.getValue())] || BrsInvalid.Instance;
case ValueKind.Int32:
return this.getElements()[index.getValue()] || BrsInvalid.Instance;
case ValueKind.String:
return this.getMethod(index.value) || BrsInvalid.Instance;
default:
throw new Error(
"Array indexes must be 32-bit integers, or method names must be strings"
"Array indexes must be 32-bit integers or Float, or method names must be strings"
);
}
}

set(index: BrsType, value: BrsType) {
if (index.kind !== ValueKind.Int32) {
throw new Error("Array indexes must be 32-bit integers");
if (index.kind === ValueKind.Int32 || index.kind === ValueKind.Float) {
this.elements[Math.trunc(index.getValue())] = value;
} else {
throw new Error("Array indexes must be 32-bit integers or Float");
}
return BrsInvalid.Instance;
}

this.elements[index.getValue()] = value;
getNext() {
const index = this.enumIndex;
if (index >= 0) {
this.enumIndex++;
if (this.enumIndex >= this.elements.length) {
this.enumIndex = -1;
}
}
return this.elements[index];
}

return BrsInvalid.Instance;
updateNext() {
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;
}
}

aaCompare(
fieldName: BrsString,
flags: BrsString,
a: RoAssociativeArray,
b: RoAssociativeArray
) {
let compare = 0;
const originalArrayCopy = [...this.elements];
const caseInsensitive = flags.toString().indexOf("i") > -1;
const aHasField = a.elements.has(fieldName.toString().toLowerCase());
const bHasField = b.elements.has(fieldName.toString().toLowerCase());
if (aHasField && bHasField) {
const valueA = a.get(fieldName);
const valueB = b.get(fieldName);
compare = sortCompare(originalArrayCopy, valueA, valueB, caseInsensitive);
} else if (aHasField) {
// assocArray with fields come before assocArrays without
compare = -1;
} else if (bHasField) {
// assocArray with fields come before assocArrays without
compare = 1;
}
return compare;
}

// ifArray
private peek = new Callable("peek", {
signature: {
args: [],
returns: ValueKind.Dynamic,
},
impl: (interpreter: Interpreter) => {
impl: (_: Interpreter) => {
return this.elements[this.elements.length - 1] || BrsInvalid.Instance;
},
});
Expand All @@ -168,8 +218,10 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [],
returns: ValueKind.Dynamic,
},
impl: (interpreter: Interpreter) => {
return this.elements.pop() || BrsInvalid.Instance;
impl: (_: Interpreter) => {
const removed = this.elements.pop();
this.updateNext();
return removed || BrsInvalid.Instance;
},
});

Expand All @@ -178,8 +230,9 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [new StdlibArgument("talue", ValueKind.Dynamic)],
returns: ValueKind.Void,
},
impl: (interpreter: Interpreter, tvalue: BrsType) => {
impl: (_: Interpreter, tvalue: BrsType) => {
this.elements.push(tvalue);
this.updateNext();
return BrsInvalid.Instance;
},
});
Expand All @@ -189,8 +242,10 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [],
returns: ValueKind.Dynamic,
},
impl: (interpreter: Interpreter) => {
return this.elements.shift() || BrsInvalid.Instance;
impl: (_: Interpreter) => {
const removed = this.elements.shift();
this.updateNext();
return removed || BrsInvalid.Instance;
},
});

Expand All @@ -199,8 +254,9 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [new StdlibArgument("tvalue", ValueKind.Dynamic)],
returns: ValueKind.Void,
},
impl: (interpreter: Interpreter, tvalue: BrsType) => {
impl: (_: Interpreter, tvalue: BrsType) => {
this.elements.unshift(tvalue);
this.updateNext();
return BrsInvalid.Instance;
},
});
Expand All @@ -210,12 +266,12 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [new StdlibArgument("index", ValueKind.Int32)],
returns: ValueKind.Boolean,
},
impl: (interpreter: Interpreter, index: Int32) => {
impl: (_: Interpreter, index: Int32) => {
if (index.lessThan(new Int32(0)).toBoolean()) {
return BrsBoolean.False;
}

let deleted = this.elements.splice(index.getValue(), 1);
const deleted = this.elements.splice(index.getValue(), 1);
this.updateNext();
return BrsBoolean.from(deleted.length > 0);
},
});
Expand All @@ -225,7 +281,7 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [],
returns: ValueKind.Int32,
},
impl: (interpreter: Interpreter) => {
impl: (_: Interpreter) => {
return new Int32(this.elements.length);
},
});
Expand All @@ -235,8 +291,9 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [],
returns: ValueKind.Void,
},
impl: (interpreter: Interpreter) => {
impl: (_: Interpreter) => {
this.elements = [];
this.enumIndex = -1;
return BrsInvalid.Instance;
},
});
Expand All @@ -246,20 +303,48 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [new StdlibArgument("array", ValueKind.Object)],
returns: ValueKind.Void,
},
impl: (interpreter: Interpreter, array: BrsComponent) => {
impl: (_: Interpreter, array: BrsComponent) => {
if (!(array instanceof RoArray)) {
// TODO: validate against RBI
return BrsInvalid.Instance;
}

this.elements = [
...this.elements,
...array.elements.filter((element) => !!element), // don't copy "holes" where no value exists
];
this.updateNext();
return BrsInvalid.Instance;
},
});

// ifArrayGet
private getEntry = new Callable("getEntry", {
signature: {
args: [new StdlibArgument("index", ValueKind.Dynamic)],
returns: ValueKind.Dynamic,
},
impl: (_: Interpreter, index: BrsType) => {
if (index.kind === ValueKind.Int32 || index.kind === ValueKind.Float) {
return this.elements[Math.trunc(index.getValue())] || BrsInvalid.Instance;
}
return BrsInvalid.Instance;
},
});

// ifArraySet
private setEntry = new Callable("setEntry", {
signature: {
args: [
new StdlibArgument("index", ValueKind.Dynamic),
new StdlibArgument("tvalue", ValueKind.Dynamic),
],
returns: ValueKind.Void,
},
impl: (_: Interpreter, index: BrsType, tvalue: BrsType) => {
return this.set(index, tvalue);
},
});

// ifArrayJoin
private join = new Callable("join", {
signature: {
Expand Down Expand Up @@ -313,29 +398,11 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
if (flags.toString().match(/([^ir])/g) != null) {
interpreter.stderr.write("roArray.SortBy: Flags contains invalid option(s).\n");
} else {
const caseInsensitive = flags.toString().indexOf("i") > -1;
const originalArrayCopy = [...this.elements];
this.elements = this.elements.sort((a, b) => {
let compare = 0;
if (a instanceof RoAssociativeArray && b instanceof RoAssociativeArray) {
const aHasField = a.elements.has(fieldName.toString().toLowerCase());
const bHasField = b.elements.has(fieldName.toString().toLowerCase());
if (aHasField && bHasField) {
const valueA = a.get(fieldName);
const valueB = b.get(fieldName);
compare = sortCompare(
originalArrayCopy,
valueA,
valueB,
caseInsensitive
);
} else if (aHasField) {
// assocArray with fields come before assocArrays without
compare = -1;
} else if (bHasField) {
// assocArray with fields come before assocArrays without
compare = 1;
}
compare = this.aaCompare(fieldName, flags, a, b);
} else if (a !== undefined && b !== undefined) {
compare = sortCompare(originalArrayCopy, a, b, false, false);
}
Expand All @@ -354,19 +421,55 @@ export class RoArray extends BrsComponent implements BrsValue, BrsIterable {
args: [],
returns: ValueKind.Void,
},
impl: (interpreter: Interpreter, separator: BrsString) => {
impl: (_: Interpreter, separator: BrsString) => {
this.elements = this.elements.reverse();
return BrsInvalid.Instance;
},
});
// ifEnum

/** Checks whether the array contains no elements. */
private isEmpty = new Callable("isEmpty", {
signature: {
args: [],
returns: ValueKind.Boolean,
},
impl: (interpreter: Interpreter) => {
impl: (_: Interpreter) => {
return BrsBoolean.from(this.elements.length === 0);
},
});

/** Checks whether the current position is not past the end of the enumeration. */
private isNext = new Callable("isNext", {
signature: {
args: [],
returns: ValueKind.Boolean,
},
impl: (_: Interpreter) => {
return BrsBoolean.from(this.enumIndex >= 0);
},
});

/** Resets the current position to the first element of the enumeration. */
private reset = new Callable("reset", {
signature: {
args: [],
returns: ValueKind.Void,
},
impl: (_: Interpreter) => {
this.enumIndex = this.elements.length > 0 ? 0 : -1;
return BrsInvalid.Instance;
},
});

/** Increments the position of an enumeration. */
private next = new Callable("next", {
signature: {
args: [],
returns: ValueKind.Dynamic,
},
impl: (_: Interpreter) => {
return this.getNext() ?? BrsInvalid.Instance;
},
});
}
Loading

0 comments on commit 6e5b422

Please sign in to comment.