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

Implement try...catch and throw #72

Merged
merged 16 commits into from
May 1, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
410 changes: 409 additions & 1 deletion src/Error.ts

Large diffs are not rendered by default.

11 changes: 11 additions & 0 deletions src/brsTypes/Callable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ export class Callable implements Brs.BrsValue {
/** The context (m) that this callable is running under (if `undefined` is running on root m) */
private context: Brs.RoAssociativeArray | undefined;

/** The location where this callable is on the source code */
private location: Location | undefined;

/**
* Calls the function this `Callable` represents with the provided `arg`uments using the
* provided `Interpreter` instance.
Expand Down Expand Up @@ -227,6 +230,14 @@ export class Callable implements Brs.BrsValue {
this.context = context;
}

setLocation(location: Location) {
this.location = location;
}

getLocation() {
return this.location;
}

/**
* Attempts to satisfy each signature of this Callable using the provided arguments, coercing arguments into other
* types where necessary and possible, returning the first satisfied signature and the arguments.
Expand Down
50 changes: 36 additions & 14 deletions src/brsTypes/components/RoSGNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,29 +610,29 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {
},
impl: (
interpreter: Interpreter,
functionname: BrsString,
...functionargs: BrsType[]
functionName: BrsString,
...functionArgs: BrsType[]
) => {
// We need to search the callee's environment for this function rather than the caller's.
let componentDef = interpreter.environment.nodeDefMap.get(
this.nodeSubtype.toLowerCase()
);

// Only allow public functions (defined in the interface) to be called.
if (componentDef && functionname.value in componentDef.functions) {
if (componentDef && functionName.value in componentDef.functions) {
// Use the mocked component functions instead of the real one, if it's a mocked component.
if (interpreter.environment.isMockedObject(this.nodeSubtype.toLowerCase())) {
let maybeMethod = this.getMethod(functionname.value);
let maybeMethod = this.getMethod(functionName.value);
return (
maybeMethod?.call(interpreter, ...functionargs) || BrsInvalid.Instance
maybeMethod?.call(interpreter, ...functionArgs) || BrsInvalid.Instance
);
}

return interpreter.inSubEnv((subInterpreter) => {
let functionToCall = subInterpreter.getCallableFunction(functionname.value);
let functionToCall = subInterpreter.getCallableFunction(functionName.value);
if (!functionToCall) {
interpreter.stderr.write(
`Ignoring attempt to call non-implemented function ${functionname}`
`Ignoring attempt to call non-implemented function ${functionName}`
);
return BrsInvalid.Instance;
}
Expand All @@ -643,16 +643,38 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {

try {
// Determine whether the function should get arguments or not.
if (functionToCall.getFirstSatisfiedSignature(functionargs)) {
return functionToCall.call(subInterpreter, ...functionargs);
} else if (functionToCall.getFirstSatisfiedSignature([])) {
return functionToCall.call(subInterpreter);
let satisfiedSignature =
functionToCall.getFirstSatisfiedSignature(functionArgs);
let args = satisfiedSignature ? functionArgs : [];
if (!satisfiedSignature) {
satisfiedSignature = functionToCall.getFirstSatisfiedSignature([]);
}
if (satisfiedSignature) {
const funcLoc =
functionToCall.getLocation() ?? interpreter.location;
interpreter.addToStack({
functionName: functionName.value,
functionLocation: funcLoc,
callLocation: funcLoc,
signature: satisfiedSignature.signature,
});
try {
const returnValue = functionToCall.call(
subInterpreter,
...args
);
interpreter.stack.pop();
return returnValue;
} catch (err) {
throw err;
}
} else {
return interpreter.addError(
generateArgumentMismatchError(
functionToCall,
functionargs,
subInterpreter.stack[subInterpreter.stack.length - 1]
functionArgs,
interpreter.stack[interpreter.stack.length - 1]
.functionLocation
)
);
}
Expand All @@ -667,7 +689,7 @@ export class RoSGNode extends BrsComponent implements BrsValue, BrsIterable {
}

interpreter.stderr.write(
`Warning calling function in ${this.nodeSubtype}: no function interface specified for ${functionname}`
`Warning calling function in ${this.nodeSubtype}: no function interface specified for ${functionName}`
);
return BrsInvalid.Instance;
},
Expand Down
5 changes: 5 additions & 0 deletions src/coverage/FileCoverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ export class FileCoverage implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType
return BrsInvalid.Instance;
}

visitThrow(statement: Stmt.Throw) {
// TODO: implement statement/expression coverage for throw
return BrsInvalid.Instance;
}

visitFor(statement: Stmt.For) {
this.execute(statement.counterDeclaration);
this.evaluate(statement.counterDeclaration.value);
Expand Down
29 changes: 16 additions & 13 deletions src/extensions/GetStackTrace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ export const GetStackTrace = new Callable("GetStackTrace", {
returns: ValueKind.Object,
},
impl: (interpreter: Interpreter, numEntries: Int32, excludePatterns: BrsType) => {
let stack = interpreter.stack;

let stack = interpreter.stack.slice();
// Filter out any files that the consumer doesn't want to include
if (excludePatterns instanceof RoArray) {
excludePatterns.getValue().forEach((pattern) => {
if (isBrsString(pattern)) {
let regexFilter = new RegExp(pattern.value);
stack = stack.filter((location) => !regexFilter.test(location.file));
stack = stack.filter(
(tracePoint) => !regexFilter.test(tracePoint.functionLocation.file)
);
}
});
} else if (!(excludePatterns instanceof BrsInvalid)) {
Expand All @@ -47,19 +48,21 @@ export const GetStackTrace = new Callable("GetStackTrace", {
return new RoArray(
stack
// Filter out any internal stack traces.
.filter((location) => !INTERNAL_REGEX_FILTER.test(location.file))
.filter(
(tracePoint) => !INTERNAL_REGEX_FILTER.test(tracePoint.functionLocation.file)
)
// Remove any duplicate entries that appear next to each other in the stack.
.filter((location, index, locations) => {
.filter((tracePoint, index, backTrace) => {
if (index === 0) return true;
let prevLocation = locations[index - 1];
return !Location.equalTo(location, prevLocation);
let prevLocation = backTrace[index - 1].functionLocation;
return !Location.equalTo(tracePoint.functionLocation, prevLocation);
})
.map((tracePoint) => {
const location = tracePoint.functionLocation;
return new BrsString(
`${location.file}:${location.start.line}:${location.start.column}`
);
})
.map(
(location) =>
new BrsString(
`${location.file}:${location.start.line}:${location.start.column}`
)
)
// Get the last item on the stack
.slice(-1 * numEntries.getValue())
.reverse()
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ export function repl() {
return;
} else if (["vars", "var"].includes(cmd)) {
console.log(chalk.cyanBright(`\r\nLocal variables:\r\n`));
console.log(chalk.cyanBright(replInterpreter.debugLocalVariables()));
console.log(chalk.cyanBright(replInterpreter.formatLocalVariables()));
rl.prompt();
return;
}
Expand Down
4 changes: 3 additions & 1 deletion src/interpreter/BrsFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { Scope, Environment } from "./Environment";
* @returns a `Callable` version of that function
*/
export function toCallable(func: Expr.Function, name: string = "[Function]") {
return new Callable(name, {
const callFunc = new Callable(name, {
signature: {
args: func.parameters,
returns: func.returns,
Expand All @@ -26,4 +26,6 @@ export function toCallable(func: Expr.Function, name: string = "[Function]") {
return func.body.accept(interpreter);
},
});
callFunc.setLocation(func.location);
return callFunc;
}
1 change: 0 additions & 1 deletion src/interpreter/Environment.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Identifier } from "../lexer";
import { BrsType, RoAssociativeArray, Int32, BrsInvalid, RoSGNode, Callable } from "../brsTypes";
import { ComponentDefinition } from "../componentprocessor";
import { BrsError } from "../Error";

/** The logical region from a particular variable or function that defines where it may be accessed from. */
export enum Scope {
Expand Down
31 changes: 15 additions & 16 deletions src/interpreter/TypeMismatch.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BrsType, ValueKind } from "../brsTypes";
import { BrsError } from "../Error";
import { RuntimeError, RuntimeErrorDetail } from "../Error";
import type { Location } from "../lexer";

/** Wraps up the metadata associated with a type mismatch error. */
Expand All @@ -13,6 +13,7 @@ export interface TypeMismatchMetadata {
left: TypeAndLocation;
/** The value on the right-hand side of a binary operator. */
right?: TypeAndLocation;
cast?: boolean;
}

export type TypeAndLocation = {
Expand All @@ -26,23 +27,21 @@ export type TypeAndLocation = {
* Creates a "type mismatch"-like error message, but with the appropriate types specified.
* @return a type mismatch error that will be tracked by this module.
*/
export class TypeMismatch extends BrsError {
export class TypeMismatch extends RuntimeError {
constructor(mismatchMetadata: TypeMismatchMetadata) {
let messageLines = [
mismatchMetadata.message,
` left: ${ValueKind.toString(getKind(mismatchMetadata.left.type))}`,
];
let location = mismatchMetadata.left.location;

if (mismatchMetadata.right) {
messageLines.push(
` right: ${ValueKind.toString(getKind(mismatchMetadata.right.type))}`
);

location.end = mismatchMetadata.right.location.end;
const errDetail = RuntimeErrorDetail.TypeMismatch;
let errMessage = `${errDetail.message} ${mismatchMetadata.message} `;
if (!mismatchMetadata.cast) {
errMessage += `"${ValueKind.toString(getKind(mismatchMetadata.left.type))}"`;
if (mismatchMetadata.right) {
errMessage += ` and "${ValueKind.toString(getKind(mismatchMetadata.right.type))}"`;
}
} else if (mismatchMetadata.right) {
errMessage += `"${ValueKind.toString(getKind(mismatchMetadata.right.type))}"`;
errMessage += ` to "${ValueKind.toString(getKind(mismatchMetadata.left.type))}"`;
}

super(messageLines.join("\n"), location);
errMessage += ".";
super({ errno: errDetail.errno, message: errMessage }, mismatchMetadata.left.location);
}
}

Expand Down
Loading