Skip to content

Commit

Permalink
Support parsing nonlocal and global (#6)
Browse files Browse the repository at this point in the history
* support parsing nonlocal and global
  • Loading branch information
altanh authored Sep 9, 2021
1 parent bdb9aba commit b35908b
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 0 deletions.
36 changes: 36 additions & 0 deletions synr/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,42 @@ def my_function():
call: Call


@attr.s(auto_attribs=True, frozen=True)
class Nonlocal(Stmt):
"""A nonlocal statement.
Example
-------
.. code-block:: python
x, y = 1, 2
def foo():
nonlocal x, y
return x
In :code:`nonlocal x, y`, :code:`vars` is :code`[x, y]`.
"""

vars: List[Var]


@attr.s(auto_attribs=True, frozen=True)
class Global(Stmt):
"""A global statement.
Example
-------
.. code-block:: python
x, y = 1, 2
def foo():
global x, y
return x
In :code:`global x, y`, :code:`vars` is :code`[x, y]`.
"""

vars: List[Var]


@attr.s(auto_attribs=True, frozen=True)
class Block(Node):
"""A sequence of statements.
Expand Down
14 changes: 14 additions & 0 deletions synr/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,20 @@ def compile_stmt(self, stmt: py_ast.stmt) -> Stmt:
None if stmt.msg is None else self.compile_expr(stmt.msg),
)

elif isinstance(stmt, py_ast.Nonlocal):
# TODO: the variable spans here are incorrect as the Python AST stores each identifier
# as a raw string (with no span information), so we just use the statement's span
return Nonlocal(
stmt_span, [Var(stmt_span, Id(stmt_span, name)) for name in stmt.names]
)

elif isinstance(stmt, py_ast.Global):
# TODO: the variable spans here are incorrect as the Python AST stores each identifier
# as a raw string (with no span information), so we just use the statement's span
return Global(
stmt_span, [Var(stmt_span, Id(stmt_span, name)) for name in stmt.names]
)

else:
self.error(f"Found unexpected {type(stmt)} when compiling stmt", stmt_span)
return Stmt(Span.invalid())
Expand Down
43 changes: 43 additions & 0 deletions tests/test_synr.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,45 @@ def bar():
assert bar.decorators[1].span.start_line == start_line + 3


def test_nonlocal():
x, y = 1, 2

def foo():
nonlocal x, y
return x + y

module = to_ast(foo)
fn = assert_one_fn(module, "foo")
nl = fn.body.stmts[0]
assert isinstance(nl, synr.ast.Nonlocal)
assert len(nl.vars) == 2
x, y = nl.vars
assert isinstance(x, synr.ast.Var) and x.id.name == "x"
assert isinstance(y, synr.ast.Var) and y.id.name == "y"

_, start_line = inspect.getsourcelines(foo)
assert nl.span.start_line == start_line + 1
# NOTE: variable spans are a bit hacky so we don't check them here


def test_global():
def foo():
global x, y
return x + y

module = to_ast(foo)
fn = assert_one_fn(module, "foo")
gl = fn.body.stmts[0]
assert isinstance(gl, synr.ast.Global)
assert len(gl.vars) == 2
x, y = gl.vars
assert isinstance(x, synr.ast.Var) and x.id.name == "x"
assert isinstance(y, synr.ast.Var) and y.id.name == "y"

_, start_line = inspect.getsourcelines(foo)
assert gl.span.start_line == start_line + 1


if __name__ == "__main__":
test_id_function()
test_class()
Expand All @@ -589,3 +628,7 @@ def bar():
test_constants()
test_err_msg()
test_scoped_func()
test_local_func()
test_decorators()
test_nonlocal()
test_global()

0 comments on commit b35908b

Please sign in to comment.