Skip to content

Commit

Permalink
fix(interp): Preventing multiple calls for dot-chained methods (#22)
Browse files Browse the repository at this point in the history
* Preventing multiple calls for dot-chained methods

* Fixed `prettier` issue
  • Loading branch information
lvcabral authored Oct 31, 2023
1 parent 7569e86 commit e6c98ae
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 46 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ lib/
types/
node_modules/
.vscode/
coverage/
coverage/
.DS_Store
36 changes: 23 additions & 13 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ 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 @@ -1093,21 +1094,27 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>
expression.callee instanceof Expr.DottedGet ||
expression.callee instanceof Expr.IndexedGet
) {
let maybeM = this.evaluate(expression.callee.obj);
maybeM = isBoxable(maybeM) ? maybeM.box() : maybeM;

if (maybeM.kind === ValueKind.Object) {
if (maybeM instanceof RoAssociativeArray) {
mPointer = maybeM;
}
if (expression.callee.obj instanceof Expr.Call) {
mPointer = this._lastDotGetAA;
} else {
return this.addError(
new BrsError(
"Attempted to retrieve a function from a primitive value",
expression.closingParen.location
)
);
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();
}

return this.inSubEnv((subInterpreter) => {
Expand Down Expand Up @@ -1182,6 +1189,9 @@ export class Interpreter implements Expr.Visitor<BrsType>, Stmt.Visitor<BrsType>

if (isIterable(source)) {
try {
if (source instanceof RoAssociativeArray) {
this._lastDotGetAA = source;
}
return source.get(new BrsString(expression.name.text));
} catch (err: any) {
return this.addError(new BrsError(err.message, expression.name.location));
Expand Down
13 changes: 11 additions & 2 deletions test/e2e/Syntax.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,9 +201,8 @@ describe("end to end syntax", () => {
"run",
"stop",
"then",
"promise-like resolved to 'foo'",
"Hello from line ",
"18",
"14",
]);
});

Expand All @@ -216,4 +215,14 @@ describe("end to end syntax", () => {
"14", // arr = [13]: arr[0]++
]);
});

test("dot-chaining.brs", async () => {
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'",
"optional chaining works",
]);
});
});
36 changes: 36 additions & 0 deletions test/e2e/resources/dot-chaining.brs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
sub main()
a = [" 1 ", " 2 ", " 3 "]
b = a.pop().trim().toInt()
print "removed number '" + b.toStr() + "' from array, remaining " + a.count().toStr()
m.__result = "bad"
immediatePromise("foo").then(sub(result)
print "promise-like resolved to '" + result + "'"
end sub)
print "optional chaining " + testOptionalChaining()
end sub

' 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
' @returns {AssociativeArray} an associative array contianing a `then` property, used to chain
' promise-like constructs.
function immediatePromise(val as dynamic) as object
return {
__result: val
then: sub(resolved as function)
resolved(m.__result)
end sub
}
end function

function testOptionalChaining()
responseData = {
data:{
metricsData:{
addOnsStepStart : "works"
}
}
}
result = responseData?.data.metricsData?.addOnsStepStart
return result
end function
12 changes: 0 additions & 12 deletions test/e2e/resources/multi-file/test1.brs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ sub Main()
print("function in same file: " + sameFileFunc())
print("function in different file: " + differentFileFunc())
print("function with dependency: " + dependentFunc())
print("result" + testOptionalChaining())
end sub

function sameFileFunc()
Expand All @@ -13,14 +12,3 @@ function dependencyFunc()
return "from dependencyFunc()"
end function

function testOptionalChaining()
result = responseData?.data.metricsData?.addOnsStepStart
responseData = {
data:{
metricsData:{
addOnsStepStart : "print"
}
}
}
return result
end function
18 changes: 0 additions & 18 deletions test/e2e/resources/reserved-words.brs
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,5 @@ sub main()
print word
end for

immediatePromise("foo").then(sub(result)
print "promise-like resolved to '" + result + "'"
end sub)

print "Hello from line " LINE_NUM
end sub

' 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
' @returns {AssociativeArray} an associative array contianing a `then` property, used to chain
' promise-like constructs.
function immediatePromise(val as dynamic) as object
return {
__result: val
then: sub(resolved as function)
resolved(m.__result)
end sub
}
end function

0 comments on commit e6c98ae

Please sign in to comment.