Skip to content
This repository was archived by the owner on Mar 23, 2023. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 195 additions & 5 deletions compiler/stmt.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,13 +378,11 @@ def visit_Import(self, node):
self.block.bind_var(self.writer, asname, mod.expr)

def visit_ImportFrom(self, node):
# Wildcard imports are not yet supported.
self._write_py_context(node.lineno)
for alias in node.names:
if alias.name == '*':
msg = 'wildcard member import is not implemented: from %s import %s' % (
node.module, alias.name)
raise util.ParseError(node, msg)
self._write_py_context(node.lineno)
self._import_wildcard(node)
return
if node.module.startswith(_NATIVE_MODULE_PREFIX):
values = [alias.name for alias in node.names]
with self._import_native(node.module, values) as mod:
Expand Down Expand Up @@ -667,6 +665,55 @@ def _assign_target(self, target, value):
msg = 'assignment target not yet implemented: ' + type(target).__name__
raise util.ParseError(target, msg)

def _bind_module_members(self, module, members):
with self.block.alloc_temp() as member, \
self.block.alloc_temp() as members_iterator, \
self.block.alloc_temp() as member_name:

self.writer.write_checked_call2(members_iterator, 'πg.Iter(πF, {})', members.expr)

loop = self.block.push_loop()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason the loop stack exists is to support Python break and continue statements. Since you're generating a fixed block of code, you don't need to push a new loop.

That said, maybe it's easier since it gives you a start and end label to work with? My concern is that the loop stack is currently only used to manage Python loops and I'm not 100% sure there won't be some weird interaction between this code and Python loops.

self.writer.write_label(loop.start_label)

tmpl = textwrap.dedent("""\
if $member_name, πE = πg.Next(πF, $members_iterator); πE != nil {
\tisStop, exc := πg.IsInstance(πF, πE.ToObject(), πg.StopIterationType.ToObject())
\tif exc != nil {
\t\tπE = exc
\t\tcontinue
\t}
\tif !isStop {
\t\tcontinue
\t}
\tπE = nil
\tπF.RestoreExc(nil, nil)
\tgoto Label$end_label
}""")
self.writer.write_tmpl(tmpl,
member_name=member_name.expr,
members_iterator=members_iterator.expr,
end_label=loop.end_label)

with self.block.resolve_name(self.writer, 'getattr') as getattr_method, \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just call πg.GetAttr() instead of fetching the getattr Python object.

self.block.alloc_temp('[]*πg.Object') as getattr_args:
self.writer.write('{} = πF.MakeArgs(2)'.format(getattr_args.expr))
self.writer.write('{}[0] = {}'.format(getattr_args.expr, module.expr))
self.writer.write(
'{}[1] = {}'.format(getattr_args.expr, member_name.expr))
self.writer.write_checked_call2(
member,
'{}.Call(πF, {}, nil)', getattr_method.expr, getattr_args.expr)
self.writer.write('πF.FreeArgs({})'.format(getattr_args.expr))

self.writer.write_checked_call1(
'πF.Globals().SetItem(πF, {}, {})',
member_name.expr, member.expr)

self.writer.write('goto Label{}'.format(loop.start_label))

self.writer.write_label(loop.end_label)
self.block.pop_loop()

def _build_assign_target(self, target, assigns):
if isinstance(target, (ast.Tuple, ast.List)):
children = []
Expand All @@ -679,6 +726,136 @@ def _build_assign_target(self, target, assigns):
tmpl = 'πg.TieTarget{Target: &$temp}'
return string.Template(tmpl).substitute(temp=temp.name)

def _extract_module_members(self, module, members):
with self.block.alloc_temp('[]*πg.Object') as hasattr_args, \
self.block.resolve_name(self.writer, 'hasattr') as hasattr_method, \
self.block.alloc_temp() as hasattr_response, \
self.block.alloc_temp('bool') as is_true:

self.writer.write('{} = πF.MakeArgs(2)'.format(hasattr_args.expr))
self.writer.write('{}[0] = {}'.format(hasattr_args.expr, module.expr))
self.writer.write(
'{}[1] = {}.ToObject()'.format(hasattr_args.expr, self.block.intern('__all__')))
self.writer.write_checked_call2(
hasattr_response,
'{}.Call(πF, {}, nil)', hasattr_method.expr, hasattr_args.expr)
self.writer.write('πF.FreeArgs({})'.format(hasattr_args.expr))

if_label = self.block.genlabel()
else_label = self.block.genlabel()
endif_label = self.block.genlabel()

self.writer.write_tmpl(textwrap.dedent("""\
if $is_true, πE = πg.IsTrue(πF, $condition); πE != nil {
\tcontinue
}
if $is_true {
\tgoto Label$if_label
} else {
\tgoto Label$else_label
}"""),
is_true=is_true.name, condition=hasattr_response.expr,
if_label=if_label, else_label=else_label)

self.writer.write_label(if_label)
self._extract_module_members_from_all(module, members)
self.writer.write('goto Label{}'.format(endif_label))
self.writer.write_label(else_label)
self._extract_module_members_from_dict(module, members)
self.writer.write_label(endif_label)

def _extract_module_members_from_all(self, module, members):
self.writer.write_checked_call2(
members, 'πg.GetAttr(πF, {}, {}, nil)',
module.expr, self.block.intern('__all__'))

def _extract_module_members_from_dict(self, module, members):
with self.block.alloc_temp() as dict_attr, \
self.block.alloc_temp() as key, \
self.block.alloc_temp() as keys, \
self.block.alloc_temp() as keys_iterator, \
self.block.alloc_temp() as keys_method:

self.writer.write_checked_call2(
dict_attr, 'πg.GetAttr(πF, {}, {}, nil)',
module.expr, self.block.intern('__dict__'))
self.writer.write_checked_call2(
keys_method, 'πg.GetAttr(πF, {}, {}, nil)',
dict_attr.expr, self.block.intern('keys'))
self.writer.write_checked_call2(keys, '{}.Call(πF, nil, nil)', keys_method.expr)
self.writer.write_checked_call2(keys_iterator, 'πg.Iter(πF, {})', keys.expr)

with self.block.alloc_temp('[]*πg.Object') as list_obj:
self.writer.write('{} = make([]*πg.Object, 0)'.format(list_obj.expr))
self.writer.write('{} = πg.NewList({}...).ToObject()'.format(members.expr, list_obj.expr))

loop = self.block.push_loop()
self.writer.write_label(loop.start_label)

tmpl = textwrap.dedent("""\
if $key, πE = πg.Next(πF, $keys_iterator); πE != nil {
\tisStop, exc := πg.IsInstance(πF, πE.ToObject(), πg.StopIterationType.ToObject())
\tif exc != nil {
\t\tπE = exc
\t\tcontinue
\t}
\tif !isStop {
\t\tcontinue
\t}
\tπE = nil
\tπF.RestoreExc(nil, nil)
\tgoto Label$end_label
}""")
self.writer.write_tmpl(tmpl,
key=key.expr,
keys_iterator=keys_iterator.expr,
end_label=loop.end_label)

with self.block.alloc_temp('bool') as is_true, \
self.block.alloc_temp('[]*πg.Object') as startswith_args, \
self.block.alloc_temp() as startswith_method, \
self.block.alloc_temp() as startswith_response:

self.writer.write_checked_call2(
startswith_method, 'πg.GetAttr(πF, {}, {}, nil)',
key.expr, self.block.intern('startswith'))

self.writer.write('{} = πF.MakeArgs(1)'.format(startswith_args.expr))
self.writer.write('{}[0] = {}.ToObject()'.format(
startswith_args.expr, self.block.intern('_')))
self.writer.write_checked_call2(
startswith_response,
'{}.Call(πF, {}, nil)', startswith_method.expr, startswith_args.expr)
self.writer.write('πF.FreeArgs({})'.format(startswith_args.expr))

start_loop = loop.start_label
self.writer.write_tmpl(textwrap.dedent("""\
if $is_true, πE = πg.IsTrue(πF, $condition); πE != nil {
\tcontinue
}
if $is_true {
\tgoto Label$start_loop
}"""),
is_true=is_true.name, condition=startswith_response.expr,
start_loop=start_loop)

with self.block.alloc_temp('[]*πg.Object') as append_args, \
self.block.alloc_temp() as append_method, \
self.block.alloc_temp() as append_response:
self.writer.write_checked_call2(
append_method, 'πg.GetAttr(πF, {}, {}, nil)',
members.expr, self.block.intern('append'))
self.writer.write('{} = πF.MakeArgs(1)'.format(append_args.expr))
self.writer.write('{}[0] = {}'.format(append_args.expr, key.expr))
self.writer.write_checked_call2(
append_response,
'{}.Call(πF, {}, nil)', append_method.expr, append_args.expr)
self.writer.write('πF.FreeArgs({})'.format(append_args.expr))

self.writer.write('goto Label{}'.format(loop.start_label))
self.writer.write_label(loop.end_label)
self.block.pop_loop()

def _import(self, name, index):
"""Returns an expression for a Module object returned from ImportModule.

Expand Down Expand Up @@ -748,6 +925,19 @@ def _import_native(self, name, values):
util.go_str(name), members.expr)
return mod

def _import_wildcard(self, node):
"""If __all__ is defined, attempts to bind everything from it,
falls back on __dict__ otherwise.

Note: items in __dict__ whose names start with '_' are skipped.
"""
module_name = node.module

with self.block.alloc_temp() as members, \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC members is a temporary that will be filled up, used to populate the globals dict and then thrown away. I would just populate the globals dict as I iterate over the imported module's members and avoid the temporary.

self._import(module_name, module_name.count('.')) as module:
self._extract_module_members(module, members)
self._bind_module_members(module, members)

def _tie_target(self, target, value):
if isinstance(target, ast.Name):
self._assign_target(target, value)
Expand Down
27 changes: 19 additions & 8 deletions compiler/stmt_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,14 +366,25 @@ def testImportFromFutureParseError(self):
self.assertRaisesRegexp(util.ParseError, want_regexp,
stmt.import_from_future, node)

def testImportWildcardMemberRaises(self):
regexp = r'wildcard member import is not implemented: from foo import *'
self.assertRaisesRegexp(util.ParseError, regexp, _ParseAndVisit,
'from foo import *')
regexp = (r'wildcard member import is not '
r'implemented: from __go__.foo import *')
self.assertRaisesRegexp(util.ParseError, regexp, _ParseAndVisit,
'from __go__.foo import *')
def testImportWildcard(self):
# Between 'compile' and 'sre_compile', only 'compile' is declared in __all__,
# so 'sre_compile' should not be visible.
result = _GrumpRun(textwrap.dedent("""\
from re import *
print compile"""))
self.assertEqual(0, result[0])
self.assertIn('<function compile at',result[1])

self.assertEqual((0, 'False\n'), _GrumpRun(textwrap.dedent("""\
from re import *
print 'sre_compile' in globals()""")))

# 'time' does not define __all__
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like an implementation detail that could change. Maybe we need to create a couple dummy test modules for the purposes of testing this functionality.

result = _GrumpRun(textwrap.dedent("""\
from time import *
print sleep"""))
self.assertEqual(0, result[0])
self.assertIn('<function sleep at', result[1])

def testVisitFuture(self):
testcases = [
Expand Down