Skip to content

Commit

Permalink
Fixed #38 Improved context handling for Callables (#40)
Browse files Browse the repository at this point in the history
Co-authored-by: Bronley Plumb <bronley@gmail.com>
  • Loading branch information
lvcabral and TwitchBronBron authored Dec 11, 2023
1 parent d66a0ed commit 7cdd4a3
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 40 deletions.
11 changes: 11 additions & 0 deletions src/brsTypes/Callable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ export class Callable implements Brs.BrsValue {
/** The signature of this callable within the BrightScript runtime. */
readonly signatures: SignatureAndImplementation[];

/** The context (m) that this callable is running under (if `undefined` is running on root m) */
private context: Brs.RoAssociativeArray | undefined;

/**
* Calls the function this `Callable` represents with the provided `arg`uments using the
* provided `Interpreter` instance.
Expand Down Expand Up @@ -216,6 +219,14 @@ export class Callable implements Brs.BrsValue {
return this.name || "";
}

getContext() {
return this.context;
}

setContext(context: Brs.RoAssociativeArray) {
this.context = context;
}

/**
* 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
44 changes: 9 additions & 35 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ Object.freeze(defaultExecutionOptions);

export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType> {
private _environment = new Environment();
private _lastDotGetAA: RoAssociativeArray = this._environment.getRootM();
private coverageCollector: CoverageCollector | null = null;
private _manifest: PP.Manifest | undefined;

Expand Down Expand Up @@ -1086,8 +1085,6 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>

if (satisfiedSignature) {
try {
let mPointer = this._environment.getRootM();

let signature = satisfiedSignature.signature;
args = args.map((arg, index) => {
// any arguments of type "object" must be automatically boxed
Expand All @@ -1097,34 +1094,10 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>

return arg;
});

if (
expression.callee instanceof Expr.DottedGet ||
expression.callee instanceof Expr.IndexedGet
) {
if (expression.callee.obj instanceof Expr.Call) {
mPointer = this._lastDotGetAA;
} else {
let maybeM = this.evaluate(expression.callee.obj);
maybeM = isBoxable(maybeM) ? maybeM.box() : maybeM;

if (maybeM.kind === ValueKind.Object) {
if (maybeM instanceof RoAssociativeArray) {
mPointer = maybeM;
}
} else {
return this.addError(
new BrsError(
"Attempted to retrieve a function from a primitive value",
expression.closingParen.location
)
);
}
}
} else {
this._lastDotGetAA = this.environment.getRootM();
let mPointer = this._environment.getRootM();
if (expression.callee instanceof Expr.DottedGet) {
mPointer = callee.getContext() ?? mPointer;
}

return this.inSubEnv((subInterpreter) => {
subInterpreter.environment.setM(mPointer);
return callee.call(this, ...args);
Expand Down Expand Up @@ -1197,10 +1170,11 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>

if (isIterable(source)) {
try {
if (source instanceof RoAssociativeArray) {
this._lastDotGetAA = source;
const target = source.get(new BrsString(expression.name.text));
if (isBrsCallable(target) && source instanceof RoAssociativeArray) {
target.setContext(source);
}
return source.get(new BrsString(expression.name.text));
return target;
} catch (err: any) {
return this.addError(new BrsError(err.message, expression.name.location));
}
Expand All @@ -1216,7 +1190,7 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
} else {
return this.addError(
new TypeMismatch({
message: "Attempting to retrieve property from non-iterable value",
message: "DottedGet: Attempting to retrieve property from non-iterable value",
left: {
type: source,
location: expression.location,
Expand All @@ -1231,7 +1205,7 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
if (!isIterable(source)) {
this.addError(
new TypeMismatch({
message: "Attempting to retrieve property from non-iterable value",
message: "IndexedGet: Attempting to retrieve property from non-iterable value",
left: {
type: source,
location: expression.location,
Expand Down
7 changes: 5 additions & 2 deletions test/e2e/Syntax.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,12 @@ describe("end to end syntax", () => {
await execute([resourceFile("dot-chaining.brs")], outputStreams);

expect(allArgs(outputStreams.stdout.write).filter((arg) => arg !== "\n")).toEqual([
"removed number '3' from array, remaining 2",
"promise-like resolved to 'foo'",
"removed number '7' from array, remaining 6",
"removed number '6' from array, remaining 5",
"promise-like resolved to 'success'",
"optional chaining works",
"param test result was: success",
"literal test result was: success",
]);
});
test("try-catch.brs", async () => {
Expand Down
37 changes: 34 additions & 3 deletions test/e2e/resources/dot-chaining.brs
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
sub main()
a = [" 1 ", " 2 ", " 3 "]
a = [" 1 ", " 2 ", " 3 ", " 4 ", " 5 ", " 6 ", " 7 "]
b = a.pop().trim().toInt()
print "removed number '" + b.toStr() + "' from array, remaining " + a.count().toStr()
m.__result = "bad"
immediatePromise("foo").then(sub(result)
c = (a.pop()).trim().toInt()
print "removed number '" + c.toStr() + "' from array, remaining " + a.count().toStr()
m.__result = "fail"
immediatePromise("success").then(sub(result)
print "promise-like resolved to '" + result + "'"
end sub)
print "optional chaining " + testOptionalChaining()
m.name = "root"
level1 = {name: "level1", param: "param test result was: "}
level1.run = sub()
print createLevel2().testResult(m.param)
print createLevel2().testResult("literal test result was: ")
end sub
level1.run()
end sub

function createLevel2()
instance = __level2_builder()
instance.new()
return instance
end function

function __level2_builder()
instance = {name: "level2"}
instance.new = sub()
m.name = "newName"
end sub
instance.testResult = function(param) as string
if m.name = "newName"
result = "success"
else
result = "fail"
end if
return param + result
end function
return instance
end function

' A simple promise-like function that immediately resolves to the provided value.
' You probably don't want to use it in production.
' @param {*} val the value this promise-like should immediately resolve to
Expand Down

0 comments on commit 7cdd4a3

Please sign in to comment.