Snek Adopts More Python Scoping

Python's implicit variable declarations are tricky and Snek had them slightly wrong. Fixing this meant figuring out how they work in Python, then figuring out the simplest possible expression to make the result fit in the ROM.

Local Variable Declaration

Local variables are declared in Python either as formal parameter names, or by placing them on the left hand side of a simple assignment operator:

def foo(a, b):
    c = 12
    return a + b + c

There are three local variables in function foo — a, b and c.

Global Variable Declaration

Global variables are declared in Python in one of two ways:

1) A simple assignment at global scope

2) A simple assignment in a function which also has a 'global' statement including the same name

a = 12

def foo(c):
    global b
    b = c

This defines both 'a' and 'b' as globals.

Global Variable Usage

Global variables can be used within functions without explicitly declaring them.

a = 12

def foo(c):
    return a + c

You may be explicit about a's scope using a 'global' statement

a = 12

def foo(c):
    global a
    return a + c

These two forms are equivalent, unless you also include an assignment expression with a on the LHS (left hand side):

a = 12

def foo(c):
    a = 13
    return a + c

is not the same as

a = 12

def foo(c):
    global a
    a = 13

as the former declares a new local, 'a', and leaves the global unchanged while the latter changes the global value.

Local Variable Usage

Python3 does whole-function analysis to figure out whether a name is local or not. If there is any assignment of a name within a function, that name references a local variable. Consider the following:

a = 12

def foo(c):
    b = a + c
    return b

def bar(c):
    b = a + c
    a = 1
    return b

The function 'foo' references the global named 'a', while the function 'bar' attempts to reference the local named 'a' before it has been assigned a value and, hence, generates an error.

Snek doesn't do this whole-function analysis, so 'bar' uses the global 'a' in the first statement as it hasn't yet reached the definition of 'a' as a local variable.

Augmented Assignments

Python Augmented Assignment statements are similar to C's Compound assignment operators — +=, *=, /=, etc. The Python reference has this to say about them:

"An augmented assignment expression like x += 1 can be rewritten as x = x + 1 to achieve a similar, but not exactly equal effect."

Because they work similar to assignment statements, they can declare a new variable in the current scope, if no such name has been included in previous assignment, global or non-local statements. Also, because they reference the variable on the RHS (right hand side), they need that variable to have already been defined before this statement executes.

Scoping in Snek

Because Snek doesn't do whole-function analysis, it can't 'see' later assignments in a function, and so a function with a use-before-assignment generates the following (non-Pythonic) result:

a = 12

def foo(c):
    b = a + c
    a = 1
    return b

> foo(13)
25

Fixing this would require additional tracking within the compiler, which I may add at some point, but for now, saving memory during compilation seems useful.

Snek Augmented Assignments

While Snek doesn't currently handle the general case of use-before-assignment involving separate statements, the simpler case with augmented assignments doesn't require saving any state during compilation and seems like something more useful to catch as without it, you would get:

a = 12

def foo(c):
    a += c
    return a

> foo(13)
25
> a
12

The value of 'a' is left as 12 because the augmented assignment fetches 'a' first, which finds the global variable 'a', but then when it assigns the resulting value, it creates a new local variable 'a', just as if this code looked like:

a = 12

def foo(c):
    b = a + c
    return b

Checking this case requires adding a special-case for augmented assignment within a function to see if the name has been declared or included in 'global' statement in the function.