Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion parser/grammar.y
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ optional_arglist_call:
decorator:
'@' dotted_name optional_arglist_call NEWLINE
{
fn := &ast.Name{ExprBase: ast.ExprBase{Pos: $<pos>$}, Id: ast.Identifier($2), Ctx: ast.Load}
fn := dottedName($<pos>2, $2)
if $3 == nil {
$$ = fn
} else {
Expand Down
4 changes: 4 additions & 0 deletions parser/grammar_data_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ var grammarTestData = []struct {
{"@dec(a,b,c=d,*args,**kwargs)\ndef fn():\n pass\n", "exec", "Module(body=[FunctionDef(name='fn', args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Call(func=Name(id='dec', ctx=Load()), args=[Name(id='a', ctx=Load()), Name(id='b', ctx=Load())], keywords=[keyword(arg='c', value=Name(id='d', ctx=Load()))], starargs=Name(id='args', ctx=Load()), kwargs=Name(id='kwargs', ctx=Load()))], returns=None)])", nil, ""},
{"@dec1\n@dec2()\n@dec3(a)\n@dec4(a,b)\ndef fn():\n pass\n", "exec", "Module(body=[FunctionDef(name='fn', args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Name(id='dec1', ctx=Load()), Call(func=Name(id='dec2', ctx=Load()), args=[], keywords=[], starargs=None, kwargs=None), Call(func=Name(id='dec3', ctx=Load()), args=[Name(id='a', ctx=Load())], keywords=[], starargs=None, kwargs=None), Call(func=Name(id='dec4', ctx=Load()), args=[Name(id='a', ctx=Load()), Name(id='b', ctx=Load())], keywords=[], starargs=None, kwargs=None)], returns=None)])", nil, ""},
{"@dec1\n@dec2()\n@dec3(a)\n@dec4(a,b)\nclass A(B):\n pass\n", "exec", "Module(body=[ClassDef(name='A', bases=[Name(id='B', ctx=Load())], keywords=[], starargs=None, kwargs=None, body=[Pass()], decorator_list=[Name(id='dec1', ctx=Load()), Call(func=Name(id='dec2', ctx=Load()), args=[], keywords=[], starargs=None, kwargs=None), Call(func=Name(id='dec3', ctx=Load()), args=[Name(id='a', ctx=Load())], keywords=[], starargs=None, kwargs=None), Call(func=Name(id='dec4', ctx=Load()), args=[Name(id='a', ctx=Load()), Name(id='b', ctx=Load())], keywords=[], starargs=None, kwargs=None)])])", nil, ""},
{"@a.b\ndef fn():\n pass\n", "exec", "Module(body=[FunctionDef(name='fn', args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Attribute(value=Name(id='a', ctx=Load()), attr='b', ctx=Load())], returns=None)])", nil, ""},
{"@a.b.c(d,e=f)\ndef fn():\n pass\n", "exec", "Module(body=[FunctionDef(name='fn', args=arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[Pass()], decorator_list=[Call(func=Attribute(value=Attribute(value=Name(id='a', ctx=Load()), attr='b', ctx=Load()), attr='c', ctx=Load()), args=[Name(id='d', ctx=Load())], keywords=[keyword(arg='e', value=Name(id='f', ctx=Load()))], starargs=None, kwargs=None)], returns=None)])", nil, ""},
{"@a.b\n@c.d()\n@e\nclass A(B):\n pass\n", "exec", "Module(body=[ClassDef(name='A', bases=[Name(id='B', ctx=Load())], keywords=[], starargs=None, kwargs=None, body=[Pass()], decorator_list=[Attribute(value=Name(id='a', ctx=Load()), attr='b', ctx=Load()), Call(func=Attribute(value=Name(id='c', ctx=Load()), attr='d', ctx=Load()), args=[], keywords=[], starargs=None, kwargs=None), Name(id='e', ctx=Load())])])", nil, ""},
{"@a.\ndef fn():\n pass\n", "exec", "", py.SyntaxError, "invalid syntax"},
{"", "single", "", py.SyntaxError, "unexpected EOF while parsing"},
{"\n", "single", "", py.SyntaxError, "unexpected EOF while parsing"},
{"pass\n", "single", "Interactive(body=[Pass()])", nil, ""},
Expand Down
22 changes: 22 additions & 0 deletions parser/make_grammar_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,28 @@ def fn():
class A(B):
pass
""", "exec"),
("""\
@a.b
def fn():
pass
""", "exec"),
("""\
@a.b.c(d,e=f)
def fn():
pass
""", "exec"),
("""\
@a.b
@c.d()
@e
class A(B):
pass
""", "exec"),
("""\
@a.
def fn():
pass
""", "exec", SyntaxError),

# single input
("", "single", SyntaxError),
Expand Down
18 changes: 18 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,21 @@

//go:generate goyacc -v y.output grammar.y
package parser

import (
"strings"

"github.com/go-python/gpython/ast"
)

// dottedName returns the expression for a dotted name such as "a.b.c": a
// Name for the first part and an Attribute for each further part, all at
// pos, as ast_for_dotted_name does in CPython.
func dottedName(pos ast.Pos, name string) ast.Expr {
parts := strings.Split(name, ".")
var expr ast.Expr = &ast.Name{ExprBase: ast.ExprBase{Pos: pos}, Id: ast.Identifier(parts[0]), Ctx: ast.Load}
for _, part := range parts[1:] {
expr = &ast.Attribute{ExprBase: ast.ExprBase{Pos: pos}, Value: expr, Attr: ast.Identifier(part), Ctx: ast.Load}
}
return expr
}
2 changes: 1 addition & 1 deletion parser/y.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

105 changes: 52 additions & 53 deletions vm/tests/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,11 @@ def notamethod(x):
self.assertRaises(TypeError, notamethod, 1)

doc="test_dotted"
# FIXME class decorator
# decorators = MiscDecorators()
# @decorators.author('Cleese')
# def foo(): return 42
# self.assertEqual(foo(), 42)
# self.assertEqual(foo.author, 'Cleese')
decorators = MiscDecorators()
@decorators.author('Cleese')
def foo(): return 42
self.assertEqual(foo(), 42)
self.assertEqual(foo.author, 'Cleese')

doc="test_argforms"
def noteargs(*args, **kwds):
Expand Down Expand Up @@ -198,7 +197,7 @@ def unimp(func):

for expr, exc in [ ("undef", NameError),
("nullval", TypeError),
("nullval.attr", NameError), # FIXME ("nullval.attr", AttributeError),
("nullval.attr", AttributeError),
("unimp", NotImplementedError)]:
codestr = "@%s\ndef f(): pass\nassert f() is None" % expr
code = compile(codestr, "test", "exec")
Expand Down Expand Up @@ -244,53 +243,53 @@ def foo(): return 42
# performed in the above order for each decorator, but we should
# iterate through the decorators in the reverse of the order they
# appear in the source.
# FIXME class decorator
# actions = []
#
# def make_decorator(tag):
# actions.append('makedec' + tag)
# def decorate(func):
# actions.append('calldec' + tag)
# return func
# return decorate
#
# class NameLookupTracer (object):
# def __init__(self, index):
# self.index = index
#
# def __getattr__(self, fname):
# if fname == 'make_decorator':
# opname, res = ('evalname', make_decorator)
# elif fname == 'arg':
# opname, res = ('evalargs', str(self.index))
# else:
# assert False, "Unknown attrname %s" % fname
# actions.append('%s%d' % (opname, self.index))
# return res
#
# c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])
#
# expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
# 'evalname2', 'evalargs2', 'makedec2',
# 'evalname3', 'evalargs3', 'makedec3',
# 'calldec3', 'calldec2', 'calldec1' ]
#
# actions = []
# @c1.make_decorator(c1.arg)
# @c2.make_decorator(c2.arg)
# @c3.make_decorator(c3.arg)
# def foo(): return 42
# self.assertEqual(foo(), 42)
#
# self.assertEqual(actions, expected_actions)

actions = []

def make_decorator(tag):
actions.append('makedec' + tag)
def decorate(func):
actions.append('calldec' + tag)
return func
return decorate

class NameLookupTracer (object):
def __init__(self, index):
self.index = index

def __getattr__(self, fname):
if fname == 'make_decorator':
opname, res = ('evalname', make_decorator)
elif fname == 'arg':
opname, res = ('evalargs', str(self.index))
else:
assert False, "Unknown attrname %s" % fname
actions.append('%s%d' % (opname, self.index))
return res

c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])

expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
'evalname2', 'evalargs2', 'makedec2',
'evalname3', 'evalargs3', 'makedec3',
'calldec3', 'calldec2', 'calldec1' ]

actions = []
@c1.make_decorator(c1.arg)
@c2.make_decorator(c2.arg)
@c3.make_decorator(c3.arg)
def foo(): return 42
self.assertEqual(foo(), 42)

self.assertEqual(actions, expected_actions)

# Test the equivalence claim in chapter 7 of the reference manual.
#
# # Test the equivalence claim in chapter 7 of the reference manual.
# #
# actions = []
# def bar(): return 42
# bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
# self.assertEqual(bar(), 42)
# self.assertEqual(actions, expected_actions)
actions = []
def bar(): return 42
bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
self.assertEqual(bar(), 42)
self.assertEqual(actions, expected_actions)

doc="test_simple"
def plain(x):
Expand Down
Loading