py5coding / py5generator

Meta-programming project that creates the py5 library code.
https://py5coding.org/
GNU General Public License v3.0
52 stars 13 forks source link

Comprehension scope error for static-mode sketches #44

Closed tabreturn closed 3 years ago

tabreturn commented 3 years ago

Running this:

x = 10
[i*x for i in range(10)]

produces this error:

py5 encountered an error in your code:
    23   def setup():
    24       with open('/tmp/tmp6u6nfx3y/_PY5_STATIC_SETUP_CODE_.py', 'r') as f:
    25           exec(
--> 26               compile(
    27                   _PY5BOT_parsing.transform_py5_code(

File "/tmp/tmp6u6nfx3y/_PY5_STATIC_SETUP_CODE_.py", line 2, in <module>
    1    x = 10
--> 2    [i*x for i in range(10)]
    ..................................................
     x = 10
    ..................................................

File "/tmp/tmp6u6nfx3y/_PY5_STATIC_SETUP_CODE_.py", line 2, in <listcomp>
    1    x = 10
--> 2    [i*x for i in range(10)]
    ..................................................
     i = 0
    ..................................................

NameError: name 'x' is not defined
>>> 

This, however, works fine --

def setup():
    x = 10
    [i*x for i in range(10)]
hx2A commented 3 years ago

@tabreturn You're giving me a challenging bug to fix today....

hx2A commented 3 years ago

Oddly, prepending that code with global x makes the error go away.

hx2A commented 3 years ago

This has the same problem:

foo = 42

def test(bar):
    return foo + bar

print(test(1))

The issue seems to be with how py5bot and run_sketch compile and execute static mode code. I have an idea for how to investigate but will save it for tomorrow.

hx2A commented 3 years ago

Fixed.

Like a lot of bugs, the path forward starts with isolating the problem. In this case I could reproduce it with this:

test_code = """
x = 10
[i*x for i in range(10)]
"""

def test_function():
    exec(test_code)

test_function()

That code can be fixed with this:

test_code = """
x = 10
[i*x for i in range(10)]
"""

ns1 = {}

def test_function():
    exec(test_code, ns1)

test_function()

Interestingly, this doesn't fix it:

test_code = """
x = 10
[i*x for i in range(10)]
"""

ns1 = {}
ns2 = {}

def test_function():
    exec(test_code, ns1, ns2)

test_function()

What's happening here is that it stores x in the local namespace but then tries to retrieve it from the global namespace in the second line. If both namespaces are the same, it works. exec() by default separates them, using locals() and globals().

This fix relates to #40.

At the end of #40 I wrote:

I think I have everything working now. I also add sensible global support for py5bot, even though using the global keyword outside of a function is a bit dubious. Not supporting this would potentially lead to other confusing problems for users.

Previously you could use the global keyword in py5bot and the py5 magics like %%py5draw but that will no longer have any effect. That's probably for the best as that was really an implementation side effect and not a real feature anyone should be using.

Both of these bugs helped improve an important part of py5. Thank you for reporting them.