Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
EnTerr
Level 9

Puzzle: Invalid value for left-side of expression

I just shot myself in the foot in a rather fascinating way, I thought i'll share it as a puzzle.

I was sketching a stack object constructor as an example (don't ask why, that's besides the point):
function Stack(n):
dim stack[n]
return {
stk: stack,
ptr: 0,
push: function(x): m.stk[m.ptr] = x: m.ptr = m.ptr + 1: end function,
pull: function(): m.ptr = m.ptr - 1: return m.stk[m.ptr]: end function
}
end function

And then i tested it:
BrightScript Debugger> s = Stack(100)
BrightScript Debugger> s.push(42)
Invalid value for left-side of expression. (runtime error &he4) in pkg:/source/main.brs(6)
Whaaat?
I kept reading and reading, and re-reading #6 ` push: function(x): m.stk[m.ptr] = x: m.ptr = m.ptr + 1: end function,` and i could not see anything to justify the error. Took me a long while to find the cause.

Can you see the reason?
0 Kudos
7 Replies
TheEndless
Level 7

Re: Puzzle: Invalid value for left-side of expression

My guess would be it's because you tried to use a locally scoped variable with the same name as the function, so m.stk is actually equal to the function Stack() and not your array stack.
My Channels: http://roku.permanence.com - Twitter: @TheEndlessDev
Instant Watch Browser (NetflixIWB), Aquarium Screensaver (AQUARIUM), Clever Clocks Screensaver (CLEVERCLOCKS), iTunes Podcasts (ITPC), My Channels (MYCHANNELS)
0 Kudos
EnTerr
Level 9

Re: Puzzle: Invalid value for left-side of expression

"TheEndless" wrote:
My guess would be it's because you tried to use a locally scoped variable with the same name as the function, so m.stk is actually equal to the function Stack() and not your array stack.

Yep. Insidious, isn't it?
Since there is no primary error on name-duping and as bonus the error printed is not what i'd expect.

This seems like a great use for one of them new-fangled WARNING messages, if not a full-blown error. 'Cause current behavior is not particularly sane. (And if at some point in the future a reasonable policy on name scope priority comes, it is always easier to allow something that was forbidden - than to forbid something that was allowed).

I don't even know how the heck does this mis-happen? Since all local variables are resolved to indices at compile-time and that did not happen at compile time for `stk: stack`, doesn't that mean compiler first checked the global function table - as it was at that point - before locals table? And if so - and here it goes crazy - that will make it non-deterministic, since what if "Stack()" was not the current or previous function but defined a few lines later? Wouldn't then `stk: stack` usage resolved into a local?

I would like to say "thanks but not really" to the person that recently disabled console's "DA" command, it sure is more fun me having to talk out of my <censored> that to look at op-codes and figure what might be wrong.
0 Kudos
EnTerr
Level 9

Re: Puzzle: Invalid value for left-side of expression

"EnTerr" wrote:
I don't even know how the heck does this mis-happen? Since all local variables are resolved to indices at compile-time and that did not happen at compile time for `stk: stack`, doesn't that mean compiler first checked the global function table - as it was at that point - before locals table? And if so - and here it goes crazy - that will make it non-deterministic, since what if "Stack()" was not the current or previous function but defined a few lines later? Wouldn't then `stk: stack` usage resolved into a local?

Necessarily speaking from my Equus asinus, i experimented and seems that BrS compiler does "hoisting" for global functions. What would you expect foo() to print here:
sub foo():
bar = 1
foobar = bar
print foobar
end sub
'
' ... big chunk of code here ...
'
sub bar():
end sub
[spoiler=spoiler:3hkbg4ey]BrightScript Debugger> foo()
<bsTypedValue: Function>[/spoiler:3hkbg4ey]
The result is startling for couple of reasons. First, bar gets treated differently depending on which side of the assignment operator it is! When it is a L-value, being assigned to - it is a local variable (we see later the global did not get mangled). When it is a R-value (to the right of assignment) though, it returns the global.

And second (this requires a little more thinking but): when compiling foo(), how did compiler know there will be a global bar() - since it comes much later textually? The explanation is "function hoisting" - it has gone first through all source to peek at all function names before starting to compile function bodies. (I know function hoisting from Javascript and odd things happen there too because of it).

Weird, huh! And yet there is not even a warning during execution.
0 Kudos
TheEndless
Level 7

Re: Puzzle: Invalid value for left-side of expression

"EnTerr" wrote:
"EnTerr" wrote:
I don't even know how the heck does this mis-happen? Since all local variables are resolved to indices at compile-time and that did not happen at compile time for `stk: stack`, doesn't that mean compiler first checked the global function table - as it was at that point - before locals table? And if so - and here it goes crazy - that will make it non-deterministic, since what if "Stack()" was not the current or previous function but defined a few lines later? Wouldn't then `stk: stack` usage resolved into a local?

Necessarily speaking from my Equus asinus, i experimented and seems that BrS compiler does "hoisting" for global functions. What would you expect foo() to print here:
sub foo():
bar = 1
foobar = bar
print foobar
end sub
'
' ... big chunk of code here ...
'
sub bar():
end sub
[spoiler=spoiler:2j6ds0kf]BrightScript Debugger> foo()
<bsTypedValue: Function>[/spoiler:2j6ds0kf]
The result is startling for couple of reasons. First, bar gets treated differently depending on which side of the assignment operator it is! When it is a L-value, being assigned to - it is a local variable (we see later the global did not get mangled). When it is a R-value (to the right of assignment) though, it returns the global.

And second (this requires a little more thinking but): when compiling foo(), how did compiler know there will be a global bar() - since it comes much later textually? The explanation is "function hoisting" - it has gone first through all source to peek at all function names before starting to compile function bodies. (I know function hoisting from Javascript and odd things happen there too because of it).

Weird, huh! And yet there is not even a warning during execution.

I'm not sure why you'd be startled by that result. It's already been established that functions always take precedence over variables (FWIW, I'm not happy about it either). I'd also expect it to do "function hoisting".. otherwise you'd have to be very deliberate in how you structure your BRS files, and you wouldn't be able to define "methods" in the construction of an AA-based "class".
My Channels: http://roku.permanence.com - Twitter: @TheEndlessDev
Instant Watch Browser (NetflixIWB), Aquarium Screensaver (AQUARIUM), Clever Clocks Screensaver (CLEVERCLOCKS), iTunes Podcasts (ITPC), My Channels (MYCHANNELS)
0 Kudos
EnTerr
Level 9

Re: Puzzle: Invalid value for left-side of expression

Hm, did you really have to quote the whole shebang i said? Smiley Wink

"TheEndless" wrote:
I'm not sure why you'd be startled by that result. It's already been established that functions always take precedence over variables (FWIW, I'm not happy about it either).
Nope, not always. See the example i gave, read the juxtaposition of Lvalue and Rvalue. Therein lies the problem, it an easy-to-learn language does not make. It should not flip-flop in treatment of names depending on which side of "=" they lie.

I'd also expect it to do "function hoisting".. otherwise you'd have to be very deliberate in how you structure your BRS files,
Which is why hoisting was done, so that you don't have to put Main() at the very end and also can have mutually-recursive functions (foo() calls bar(), bar() calls foo()). I did not object to it - rather pointed it was done and the quirks it adds.

and you wouldn't be able to define "methods" in the construction of an AA-based "class".
That... that's completely unrelated. This
obj = {
f: function(): return m.field: end function,
field: 0
}
relies neither on hoisting nor on named-function globality. Or did i misunderstand you, through example if i did.
0 Kudos
TheEndless
Level 7

Re: Puzzle: Invalid value for left-side of expression

"EnTerr" wrote:
Hm, did you really have to quote the whole shebang i said?

I was too lazy for cut and paste, so just hit the quote button on your previous post. So, no, I didn't have to quote the whole thing... Smiley Wink

"EnTerr" wrote:
Nope, not always. See the example i gave, read the juxtaposition of Lvalue and Rvalue. Therein lies the problem, it an easy-to-learn language does not make. It should not flip-flop in treatment of names depending on which side of "=" they lie.

That's an issue of scope, I think. The Lvalue is a locally scoped Set, so there's most likely a locally scoped "bar" variable on the stack at that point. You just can't get to it, because as an Rvalue, it's evaluated to the global function instead.

"EnTerr" wrote:
That... that's completely unrelated. This
obj = {
    f: function(): return m.field: end function,
    field: 0
}

relies neither on hoisting nor on named-function globality. Or did i misunderstand you, through example if i did.

I don't think you misunderstood, you just didn't expand your understanding to include the scenario I was referring to. I rarely use anonymous functions in my code, so I was referring to something like this:
Function MyClass() As Object
this = {
ClassName: "MyClass"
MyMethod: MyClass_MyMethod
}
Return this
End Function

Sub MyClass_MyMethod()
' Do Something
End Sub

Without function hoisting, that wouldn't be possible, because MyClass_MyMethod wouldn't exist, yet, when MyClass was defined.
My Channels: http://roku.permanence.com - Twitter: @TheEndlessDev
Instant Watch Browser (NetflixIWB), Aquarium Screensaver (AQUARIUM), Clever Clocks Screensaver (CLEVERCLOCKS), iTunes Podcasts (ITPC), My Channels (MYCHANNELS)
0 Kudos
EnTerr
Level 9

Re: Puzzle: Invalid value for left-side of expression

"TheEndless" wrote:
I was too lazy for cut and paste, so just hit the quote button on your previous post. So, no, I didn't have to quote the whole thing... Smiley Wink
Don't have to copy&paste. I do select + delete. "Cut on byte pollution, save the environment" Smiley Very Happy


"EnTerr" wrote:
Nope, not always. See the example i gave, read the juxtaposition of Lvalue and Rvalue. Therein lies the problem, it an easy-to-learn language does not make. It should not flip-flop in treatment of names depending on which side of "=" they lie.

That's an issue of scope, I think. The Lvalue is a locally scoped Set, so there's most likely a locally scoped "bar" variable on the stack at that point. You just can't get to it, because as an Rvalue, it's evaluated to the global function instead.
And that is the crux of the matter!
It should not change the semantics (who are you talking about) depending on whether a name is used on the left side or the right side of =.
There is no other language that confuses name resolution like that. Not to mention the morass of explaining l-value vs r-value (which one has probably never heard of unless they've read K&R)

I rarely use anonymous functions in my code, so I was referring to something like this:
Function MyClass() As Object
this = {
ClassName: "MyClass"
MyMethod: MyClass_MyMethod
}
Return this
End Function

Sub MyClass_MyMethod()
' Do Something
End Sub
Curious. You wouldn't happen to come from C++ provenance, are you?
It's not worse style, it's just... different. I don't think there is any difference between anonymous and "onymous" functions in B/S. There is no reflection to ask fn of its own name. Yeah, you can use the global-named ones as standalone functions for testing but then again i can "steal" anonymous function from an object/instance and do the same.

Without function hoisting, that wouldn't be possible, because MyClass_MyMethod wouldn't exist, yet, when MyClass was defined.
Yeah, so what? Smiley Very Happy That would just follow from (an imaginary) general rule that a function - MyClass() in your case - can only use other functions defined before it. In other words MyClass_MyMethod() would have to be textually written before it. Which turns out to be reasonable requirement, if you have done programming in Pascal/Modula. Hoisting is needed in the cases of circular reasoning, though that also has a work-around - forward declarations.
0 Kudos