Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
typon
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Tom Niget
typon
Commits
1b677c3e
Commit
1b677c3e
authored
Mar 09, 2023
by
Tom Niget
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Scopes work? I think – handles nested decls in functions
parent
3faf1b4e
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
163 additions
and
36 deletions
+163
-36
trans/tests/builtins_test.py
trans/tests/builtins_test.py
+7
-2
trans/transpiler/__init__.py
trans/transpiler/__init__.py
+156
-34
No files found.
trans/tests/builtins_test.py
View file @
1b677c3e
...
...
@@ -7,11 +7,16 @@ test = (2 + 3) * 4
glob
=
5
def
g
():
a
=
8
if
True
:
b
=
9
if
True
:
c
=
10
if
True
:
x
=
5
print
(
x
)
d
=
a
+
b
+
c
if
True
:
e
=
d
+
1
print
(
e
)
def
f
(
x
):
return
x
+
1
...
...
trans/transpiler/__init__.py
View file @
1b677c3e
...
...
@@ -146,8 +146,7 @@ class PrecedenceContext:
self
.
op
=
op
def
__enter__
(
self
):
if
self
.
visitor
.
precedence
[
-
1
:]
!=
[
self
.
op
]:
self
.
visitor
.
precedence
.
append
(
self
.
op
)
self
.
visitor
.
precedence
.
append
(
self
.
op
)
def
__exit__
(
self
,
exc_type
,
exc_val
,
exc_tb
):
self
.
visitor
.
precedence
.
pop
()
...
...
@@ -290,18 +289,82 @@ class ExpressionVisitor(NodeVisitor):
yield
" : "
yield
from
self
.
visit
(
node
.
orelse
)
@
dataclass
class
VarDecl
:
kind
:
VarKind
val
:
Optional
[
str
]
@
dataclass
class
Scope
:
parent
:
Optional
[
"Scope"
]
=
None
vars
:
Dict
[
str
,
VarKind
]
=
field
(
default_factory
=
dict
)
is_function
:
bool
=
False
vars
:
Dict
[
str
,
VarDecl
]
=
field
(
default_factory
=
dict
)
def
is_global
(
self
):
def
is_global
(
self
)
->
bool
:
"""
Determines whether this scope is the global scope. The global scope is the only scope to have no parent.
"""
return
self
.
parent
is
None
def
exists
(
self
,
name
:
str
)
->
bool
:
"""
Determines whether a variable exists in the current scope or any parent scope.
"""
return
name
in
self
.
vars
or
(
self
.
parent
is
not
None
and
self
.
parent
.
exists
(
name
))
def
exists_local
(
self
,
name
:
str
)
->
bool
:
"""
Determines whether a variable exists in the current function or global scope.
The check does not cross function boundaries; i.e. global variables are not taken into account from inside
functions.
"""
return
name
in
self
.
vars
or
(
not
self
.
is_function
and
self
.
parent
is
not
None
and
self
.
parent
.
exists_local
(
name
))
def
child
(
self
)
->
"Scope"
:
"""
Creates a child scope with a new variable dictionary.
This is used for first-level elements of a function.
"""
return
Scope
(
self
,
False
,
{})
def
child_share
(
self
)
->
"Scope"
:
"""
Creates a child scope sharing the variable dictionary with the parent scope.
This is used for Python blocks, which share the variable scope with their parent block.
"""
return
Scope
(
self
,
False
,
self
.
vars
)
def
function
(
self
,
**
kwargs
)
->
"Scope"
:
"""
Creates a function scope.
"""
return
Scope
(
self
,
True
,
**
kwargs
)
def
is_root
(
self
)
->
Optional
[
Dict
[
str
,
VarDecl
]]:
"""
Determines whether this scope is a root scope.
A root scope is either the global scope, or the first inner scope of a function.
Variable declarations in the generated code only ever appear in root scopes.
:return: `None` if this scope is not a root scope, otherwise the variable dictionary of the root scope.
"""
if
self
.
parent
is
None
:
return
self
.
vars
if
self
.
parent
.
is_function
:
return
self
.
parent
.
vars
return
None
def
declare
(
self
,
name
:
str
,
val
:
Optional
[
str
]
=
None
)
->
Optional
[
str
]:
if
self
.
exists_local
(
name
):
# If the variable already exists in the current function or global scope, we don't need to declare it again.
# This is simply an assignment.
return
None
vdict
,
prefix
=
self
.
vars
,
""
if
(
root_vars
:
=
self
.
is_root
())
is
not
None
:
vdict
,
prefix
=
root_vars
,
"auto "
# Root scope declarations can use `auto`.
vdict
[
name
]
=
VarDecl
(
VarKind
.
LOCAL
,
val
)
return
prefix
# noinspection PyPep8Naming
class
BlockVisitor
(
NodeVisitor
):
...
...
@@ -340,44 +403,94 @@ class BlockVisitor(NodeVisitor):
yield
f"auto
{
node
.
name
}
"
yield
args
yield
"{"
inner
=
BlockVisitor
(
Scope
(
self
.
_scope
,
vars
=
{
arg
.
arg
:
VarKind
.
LOCAL
for
arg
in
node
.
args
.
args
}
))
inner
=
BlockVisitor
(
self
.
_scope
.
function
(
))
for
child
in
node
.
body
:
yield
from
inner
.
visit
(
child
)
# Python uses module- and function- level scoping. Blocks, like conditionals and loops, do not form scopes
# on their own. Variables are still accessible in the remainder of the parent function or in the global
# scope if outside a function.
# This is different from C++, where scope is tied to any code block. To emulate this behavior, we need to
# declare all variables in the first inner scope of a function.
# For example,
# ```py
# def f():
# if True:
# x = 1
# print(x)
# ```
# is translated to
# ```cpp
# auto f() {
# decltype(1) x;
# if (true) {
# x = 1;
# }
# print(x);
# }
# ```
# `decltype` allows for proper typing (`auto` can't be used for variables with values later assigned, since
# this would require real type inference, akin to what Rust does).
# This is only done, though, for *nested* blocks of a function. Root-level variables are declared with
# `auto`:
# ```py
# x = 1
# def f():
# y = 2
# ```
# is translated to
# ```cpp
# auto x = 1;
# auto f() {
# auto y = 2;
# }
# ```
child_visitor
=
BlockVisitor
(
inner
.
_scope
.
child
())
# We need to do this in two-passes. This unfortunately breaks our nice generator state-machine architecture.
# Fair enough.
[
*
child_code
]
=
child_visitor
.
visit
(
child
)
# Hoist inner variables to the root scope.
for
var
,
decl
in
child_visitor
.
_scope
.
vars
.
items
():
if
decl
.
kind
==
VarKind
.
LOCAL
:
# Nested declarations become `decltype` declarations.
yield
f"decltype(
{
decl
.
val
}
)
{
var
}
;"
elif
decl
.
kind
in
(
VarKind
.
GLOBAL
,
VarKind
.
NONLOCAL
):
# `global` and `nonlocal` just get hoisted as-is.
inner
.
_scope
.
vars
[
var
]
=
decl
yield
from
child_code
# Yeet back the child node code.
yield
"}"
def
visit_Global
(
self
,
node
:
ast
.
Global
)
->
Iterable
[
str
]:
for
name
in
map
(
self
.
fix_name
,
node
.
names
):
self
.
_scope
.
vars
[
name
]
=
Var
Kind
.
GLOBAL
self
.
_scope
.
vars
[
name
]
=
Var
Decl
(
VarKind
.
GLOBAL
,
None
)
yield
""
def
visit_Nonlocal
(
self
,
node
:
ast
.
Nonlocal
)
->
Iterable
[
str
]:
for
name
in
map
(
self
.
fix_name
,
node
.
names
):
self
.
_scope
.
vars
[
name
]
=
Var
Kind
.
NONLOCAL
self
.
_scope
.
vars
[
name
]
=
Var
Decl
(
VarKind
.
NONLOCAL
,
None
)
yield
""
def
visit_If
(
self
,
node
:
ast
.
If
)
->
Iterable
[
str
]:
if
not
node
.
orelse
and
compare_ast
(
node
.
test
,
ast
.
parse
(
'__name__ == "__main__"'
,
mode
=
"eval"
).
body
):
yield
"int main() {"
for
child
in
node
.
body
:
yield
from
self
.
visit
(
child
)
yield
"}"
# Special case handling for Python's interesting way of defining an entry point.
# I mean, it's not *that* bad, it's just an attempt at retrofitting an "entry point" logic in a scripting
# language that, by essence, uses "the start of the file" as the implicit entry point, since files are
# read and executed line-by-line, contrary to usual structured languages that mark a distinction between
# declarations (functions, classes, modules, ...) and code.
# Also, for nitpickers, the C++ standard explicitly allows for omitting a `return` statement in the `main`.
# 0 is returned by default.
yield
"int main()"
yield
from
self
.
emit_block
(
node
.
body
)
return
yield
"if ("
yield
from
ExpressionVisitor
().
visit
(
node
.
test
)
yield
") {"
for
child
in
node
.
body
:
yield
from
self
.
visit
(
child
)
yield
"}"
yield
")"
yield
from
self
.
emit_block
(
node
.
body
)
if
node
.
orelse
:
yield
"else "
if
isinstance
(
node
.
orelse
,
ast
.
If
):
yield
from
self
.
visit
(
node
.
orelse
)
else
:
yield
"{"
for
child
in
node
.
orelse
:
yield
from
self
.
visit
(
child
)
yield
"}"
yield
from
self
.
emit_block
(
node
.
orelse
)
def
visit_Return
(
self
,
node
:
ast
.
Return
)
->
Iterable
[
str
]:
yield
"return "
...
...
@@ -388,21 +501,19 @@ class BlockVisitor(NodeVisitor):
def
visit_While
(
self
,
node
:
ast
.
While
)
->
Iterable
[
str
]:
yield
"while ("
yield
from
ExpressionVisitor
().
visit
(
node
.
test
)
yield
") {"
for
child
in
node
.
body
:
yield
from
self
.
visit
(
child
)
yield
"}"
yield
")"
yield
from
self
.
emit_block
(
node
.
body
)
if
node
.
orelse
:
raise
NotImplementedError
(
node
,
"orelse"
)
def
visit_lvalue
(
self
,
lvalue
:
ast
.
expr
)
->
Iterable
[
str
]:
def
visit_lvalue
(
self
,
lvalue
:
ast
.
expr
,
val
:
Optional
[
ast
.
AST
]
=
None
)
->
Iterable
[
str
]:
if
isinstance
(
lvalue
,
ast
.
Tuple
):
yield
f"std::tie(
{
', '
.
join
(
flatmap
(
ExpressionVisitor
().
visit
,
lvalue
.
elts
))
}
)"
elif
isinstance
(
lvalue
,
ast
.
Name
):
name
=
self
.
fix_name
(
lvalue
.
id
)
if
name
not
in
self
.
_scope
.
vars
:
self
.
_scope
.
vars
[
name
]
=
name
yield
"auto "
#
if name not in self._scope.vars:
if
not
self
.
_scope
.
exists_local
(
name
):
yield
self
.
_scope
.
declare
(
name
,
" "
.
join
(
ExpressionVisitor
().
visit
(
val
))
if
val
else
None
)
yield
name
elif
isinstance
(
lvalue
,
ast
.
Subscript
):
yield
from
ExpressionVisitor
().
visit
(
lvalue
)
...
...
@@ -412,7 +523,7 @@ class BlockVisitor(NodeVisitor):
def
visit_Assign
(
self
,
node
:
ast
.
Assign
)
->
Iterable
[
str
]:
if
len
(
node
.
targets
)
!=
1
:
raise
NotImplementedError
(
node
)
yield
from
self
.
visit_lvalue
(
node
.
targets
[
0
])
yield
from
self
.
visit_lvalue
(
node
.
targets
[
0
]
,
node
.
value
)
yield
" = "
yield
from
ExpressionVisitor
().
visit
(
node
.
value
)
yield
";"
...
...
@@ -420,7 +531,7 @@ class BlockVisitor(NodeVisitor):
def
visit_AnnAssign
(
self
,
node
:
ast
.
AnnAssign
)
->
Iterable
[
str
]:
if
node
.
value
is
None
:
raise
NotImplementedError
(
node
,
"empty value"
)
yield
from
self
.
visit_lvalue
(
node
.
target
)
yield
from
self
.
visit_lvalue
(
node
.
target
,
node
.
value
)
yield
" = "
yield
from
ExpressionVisitor
().
visit
(
node
.
value
)
yield
";"
...
...
@@ -436,9 +547,20 @@ class BlockVisitor(NodeVisitor):
raise
NotImplementedError
(
node
)
yield
f"for (auto
{
node
.
target
.
id
}
: "
yield
from
ExpressionVisitor
().
visit
(
node
.
iter
)
yield
") {"
for
child
in
node
.
body
:
yield
from
self
.
visit
(
child
)
yield
"}"
yield
")"
yield
from
self
.
emit_block
(
node
.
body
)
if
node
.
orelse
:
raise
NotImplementedError
(
node
,
"orelse"
)
def
block
(
self
)
->
"BlockVisitor"
:
# See the comments in visit_FunctionDef.
# A Python code block does not introduce a new scope, so we create a new `Scope` object that shares the same
# variables as the parent scope.
return
BlockVisitor
(
self
.
_scope
.
child_share
())
def
emit_block
(
self
,
items
:
List
[
ast
.
stmt
])
->
Iterable
[
str
]:
yield
"{"
block
=
self
.
block
()
for
child
in
items
:
yield
from
block
.
visit
(
child
)
yield
"}"
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment