Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
typon-compiler
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
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
typon
typon-compiler
Commits
0a0cdfd7
Commit
0a0cdfd7
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
4aa55569
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 @
0a0cdfd7
...
...
@@ -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 @
0a0cdfd7
...
...
@@ -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