Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(components): Implemented missing ifEnum methods in roArray and roAssociativeArray #33

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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;
},
});
}