Closed nonkit closed 5 years ago
Ah, I think I see. It looks like SB does shadow variables correctly but in this instance, in the snippet
test = "Hello"
the name test
is not being treated as the name of a variable but a subroutine call. This does seem incorrect for a few reasons
I think we may be able to get around this by adding a special sequence of characters to the end (or beginning) of subroutines as a sort of flag so when writing to the environment and looking the values up we never make a mistake.
However, this sounds like a pretty substantial design choice and I'm not sure which way to go. @OmarTawfik could you drop your opinion in the ring?
As an update, this works:
for test = 1 to 10
TextWindow.WriteLine(1)
EndFor
test()
Sub test
TextWindow.WriteLine(1)
EndSub
but this does not:
for test = 1 to 10
TextWindow.WriteLine(test)
EndFor
test()
Sub test
TextWindow.WriteLine(1)
EndSub
and of course anything with TextWindow.WriteLine(test)
doesn't work
I think you just discovered a bug in the binder :) To correct this, do you think we should allow shadowing? IMO, it just teaches users bad practices, and we should disallow it consistently, but I leave this up for discussion. Please let me know if you have thoughts on it.
@OmarTawfik to be honest I think the issue here is with the parser treating an expression as a value - shadowing seems to have already been decided for the language and (I think) is performing incorrectly here. Do you think backend/compiler/parser.cs is a good place to start looking?
I also reported this as a parity issue because SBD and SBO v0.91 allow shadowing. But my opinion is that shadowing is not so important for programming learners.
@PieterBenjamin my hypothesis was that after parsing is done, there is a single method (below) that is "binding" all random identifiers to either: 1) a library. 2) a submodule. 3) a variable.
Since collecting sub-module names happens first, we automatically assume test
is a sub-module, and therefore it cannot be assigned to or printed to the text window.
To verify if that is the case, I suggest starting by:
1) adding a test like the one I pasted below, and put a break point in the binder method to see if it was so.
2) the fix might be as easy as if expectsValue
was true, we can just return an identifier and not check sub-modules.
3) add a few tests to verify corner cases, and make sure the existing tests also pass.
@OmarTawfik sorry for the long silence, had some assorted business on my end to deal with.
I tried reordering the binding method as you suggested to the following:
private BaseBoundExpression BindIdentifierExpression(IdentifierExpressionSyntax syntax, bool expectsValue)
{
bool hasErrors = false;
string name = syntax.IdentifierToken.Text;
if (Libraries.Types.TryGetValue(name, out Library library))
{
if (expectsValue)
{
hasErrors = true;
this.diagnostics.ReportExpectedExpressionWithAValue(syntax.Range);
}
return new BoundLibraryTypeExpression(syntax, hasValue: false, hasErrors, name);
}
else if (expectsValue)
{
return new BoundVariableExpression(syntax, hasValue: true, hasErrors, name);
}
else
{
if (expectsValue)
{
hasErrors = true;
this.diagnostics.ReportExpectedExpressionWithAValue(syntax.Range);
}
return new BoundSubModuleExpression(syntax, hasValue: false, hasErrors, name);
}
}
Note: all I did was move a check for expectsValue
before the previous
check else if (this.definedSubModules.Contains(name))
And that seems to have solved it from my basic testing, but I'm not sure how to write tests on the backend. I'll get working on that and let you know when I've made some progress.
Could you explain why this appears to be the fix? The following code segment works as expected on my version, but won't compile on the live super basic
test()
for test = 1 to 10
TextWindow.WriteLine(test)
EndFor
test()
Sub test
TextWindow.WriteLine("hello world!")
EndSub
test()
I guess my main confusion is I'm unsure what the Binder
does. It sounds like it would set the behavior for a keyword across the program but the above code would disagree with that.
As a quick update, I've figured out how to add tests. Would you like any explanatory comments added for each test?
Also, this is definitely a good time to revisit the shadowing issue. I think it's a decent crutch for beginners, but I'd love to have a discussion on it.
@PieterBenjamin thanks for looking into it! to answer your questions:
BoundSubModuleExpression
if it wasn't defined, that will just lead to confusing error messages. One valid order would be:
IF_TOKEN
, IDENTIFIER
, ELSE_IF_TOKEN
, etc...x = Math.Foo()
it will bind it into an assignment expression, without actually checking if Foo
exists.Foo
, the IDE won't be able to use the resulting tree to give you intellisense in a different part of the file.@OmarTawfik I think I got it this time:
else if (this.definedSubModules.Contains(name))
{
if (expectsValue)
{
return new BoundVariableExpression(syntax, hasValue: true, hasErrors, name);
}
return new BoundSubModuleExpression(syntax, hasValue: false, hasErrors, name);
}
It's also worth noting that the hasErrors variable is somewhat unnecessary at this point. If this looks like a proper fix, I can whip up some tests and submit a pr
edit: I'll have to change the expected output for the ItReportsAssigningToSubModule test. In the current version, this would be considered an error but with the changes I believe this would be correct behavior. Is it alright if I change that test?
@PieterBenjamin that would be great! Please do 😄
@OmarTawfik created a (hopefully successful) pull request. The changes were pretty minimal so I'm sorry I didn't get this all done sooner!
Following sample code causes syntax error in SBO v1.0.