Skip to content

Commit 656fb73

Browse files
author
whitequark
committedMay 4, 2015
45% coverage.
All expressions except lambdas and a few forms of calls and subscripts are handled. The omissions are due to LL conflicts.
1 parent da78a09 commit 656fb73

File tree

4 files changed

+332
-33
lines changed

4 files changed

+332
-33
lines changed
 

Diff for: ‎pyparser/ast.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ class Attribute(expr, ast.Attribute):
160160
:ivar value: (node) left-hand side
161161
:ivar attr: (string) attribute name
162162
"""
163+
_locs = expr._locs + ('dot_loc', 'attr_loc')
163164
class BinOp(expr, ast.BinOp):
164165
"""
165166
A binary operation, e.g. ``x + y``.
@@ -316,10 +317,11 @@ class UnaryOp(expr, ast.UnaryOp):
316317
"""
317318
class Yield(expr, ast.Yield):
318319
"""
319-
A yield expression, e.g. ``yield x``.
320+
A yield expression, e.g. ``(yield x)``.
320321
321322
:ivar value: (node) yielded value
322323
"""
324+
_locs = expr._locs + ('keyword_loc',)
323325

324326
# expr_context
325327
# AugLoad
@@ -337,7 +339,7 @@ class keyword(commonloc, ast.keyword):
337339
:ivar value: (node) value
338340
:ivar equals_loc: location of ``=``
339341
"""
340-
_locs = commonloc._locs + ('equals_loc',)
342+
_locs = commonloc._locs + ('arg_loc', 'equals_loc')
341343

342344
class mod(commonloc):
343345
"""Base class for modules (groups of statements)."""

Diff for: ‎pyparser/coverage/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ def instrument():
2929
if not in_grammar:
3030
continue
3131

32+
if token.kind == '.': # skip ast.List
33+
lex.next()
34+
3235
if token.kind == 'ident' and \
3336
token.value in ('action', 'Eps', 'Tok', 'Loc', 'Rule', 'Expect',
3437
'Seq', 'SeqN', 'Alt', 'Opt', 'Star', 'Plus', 'List',

Diff for: ‎pyparser/parser.py

+42-25
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,9 @@ def rule(parser):
199199
results.append(result)
200200
return rule
201201

202+
class commalist(list):
203+
__slots__ = ('trailing_comma',)
204+
202205
def List(inner_rule, separator_tok, trailing, leading=True, loc=None):
203206
if not trailing:
204207
@action(Seq(inner_rule, Star(SeqN(1, Tok(separator_tok), inner_rule))), loc=loc)
@@ -213,7 +216,7 @@ def outer_rule(parser, first, rest):
213216
separator_rule = Tok(separator_tok)
214217
@llrule(loc, inner_rule.expected)
215218
def rule(parser):
216-
results = []
219+
results = commalist()
217220

218221
if leading:
219222
result = inner_rule(parser)
@@ -225,12 +228,12 @@ def rule(parser):
225228
while True:
226229
result = separator_rule(parser)
227230
if result is unmatched:
228-
#results.trailing = True
231+
results.trailing_comma = False
229232
return results
230233

231234
result = inner_rule(parser)
232235
if result is unmatched:
233-
#results.trailing = False
236+
results.trailing_comma = True
234237
return results
235238
else:
236239
results.append(result)
@@ -271,9 +274,15 @@ def BeginEnd(begin_tok, inner_rule, end_tok, empty=None, loc=None):
271274
def rule(parser, begin_loc, node, end_loc):
272275
if node is None:
273276
node = empty()
274-
node.ctx = None
275-
node.begin_loc, node.end_loc, node.loc = \
276-
begin_loc, end_loc, begin_loc.join(end_loc)
277+
278+
# Collection nodes don't have loc yet. If a node has loc at this
279+
# point, it means it's an expression passed in parentheses.
280+
if node.loc is None and type(node) in [
281+
ast.List, ast.Dict, ast.Tuple, ast.Repr,
282+
ast.ListComp, ast.GeneratorExp,
283+
ast.Call, ast.Subscript]:
284+
node.begin_loc, node.end_loc, node.loc = \
285+
begin_loc, end_loc, begin_loc.join(end_loc)
277286
return node
278287
return rule
279288

@@ -838,6 +847,7 @@ def power(self, atom, trailers, factor_opt):
838847
trailer.value = atom
839848
elif isinstance(trailer, ast.Call):
840849
trailer.func = atom
850+
trailer.loc = atom.loc.join(trailer.loc)
841851
atom = trailer
842852
if factor_opt:
843853
op_loc, factor = factor_opt
@@ -862,14 +872,15 @@ def atom_3(self, begin_tok, data_tok, end_tok):
862872

863873
@action(Rule('testlist1'))
864874
def atom_4(self, expr):
865-
return ast.Repr(value=expr, loc=expr.loc)
875+
return ast.Repr(value=expr, loc=None)
866876

867877
atom = Alt(BeginEnd('(', Opt(Alt(Rule('yield_expr'), Rule('testlist_gexp'))), ')',
868-
empty=lambda: ast.Tuple(elts=[])),
878+
empty=lambda: ast.Tuple(elts=[], ctx=None, loc=None)),
869879
BeginEnd('[', Opt(Rule('listmaker')), ']',
870-
empty=lambda: ast.List(elts=[])),
880+
empty=lambda: ast.List(elts=[], ctx=None, loc=None)),
871881
BeginEnd('{', Opt(Rule('dictmaker')), '}',
872-
empty=lambda: ast.Dict(keys=[], values=[], colon_locs=[])),
882+
empty=lambda: ast.Dict(keys=[], values=[], colon_locs=[],
883+
ctx=None, loc=None)),
873884
BeginEnd('`', atom_4, '`'),
874885
atom_1, atom_2, atom_3)
875886
"""atom: ('(' [yield_expr|testlist_gexp] ')' |
@@ -890,11 +901,11 @@ def list_gen_action(self, lhs, rhs):
890901

891902
@action(Rule('list_for'))
892903
def listmaker_1(self, compose):
893-
return ast.ListComp(generators=compose([]))
904+
return ast.ListComp(generators=compose([]), loc=None)
894905

895906
@action(List(Rule('test'), ',', trailing=True, leading=False))
896907
def listmaker_2(self, elts):
897-
return ast.List(elts=elts, ctx=None)
908+
return ast.List(elts=elts, ctx=None, loc=None)
898909

899910
listmaker = action(
900911
Seq(Rule('test'),
@@ -904,14 +915,14 @@ def listmaker_2(self, elts):
904915

905916
@action(Rule('gen_for'))
906917
def testlist_gexp_1(self, compose):
907-
return ast.GeneratorExp(generators=compose([]))
918+
return ast.GeneratorExp(generators=compose([]), loc=None)
908919

909-
@action(List(Rule('test'), ',', trailing=True))
920+
@action(List(Rule('test'), ',', trailing=True, leading=False))
910921
def testlist_gexp_2(self, elts):
911-
if elts == [] and not elts.trailing:
922+
if elts == [] and not elts.trailing_comma:
912923
return None
913924
else:
914-
return ast.Tuple(elts=elts, ctx=None)
925+
return ast.Tuple(elts=elts, ctx=None, loc=None)
915926

916927
testlist_gexp = action(
917928
Seq(Rule('test'), Alt(testlist_gexp_1, testlist_gexp_2))) \
@@ -929,7 +940,8 @@ def lambdef(self, lambda_loc, args_opt, colon_loc, body):
929940
@action(Seq(Loc('.'), Tok('ident')))
930941
def trailer_1(self, dot_loc, ident_tok):
931942
return ast.Attribute(attr=ident_tok.value, ctx=None,
932-
loc=dot_loc.join(ident_tok.loc), dot_loc=dot_loc)
943+
loc=dot_loc.join(ident_tok.loc),
944+
attr_loc=ident_tok.loc, dot_loc=dot_loc)
933945

934946
trailer = Alt(BeginEnd('(', Rule('arglist'), ')'),
935947
BeginEnd('[', Rule('subscriptlist'), ']'),
@@ -940,11 +952,11 @@ def trailer_1(self, dot_loc, ident_tok):
940952
def subscriptlist(self, subscripts):
941953
"""subscriptlist: subscript (',' subscript)* [',']"""
942954
if len(subscripts) == 1:
943-
return ast.Subscript(slice=subscripts[0], ctx=None)
955+
return ast.Subscript(slice=subscripts[0], ctx=None, loc=None)
944956
else:
945957
extslice = ast.ExtSlice(dims=subscripts,
946958
loc=subscripts[0].loc.join(subscripts[-1].loc))
947-
return ast.Subscript(slice=extslice, ctx=None)
959+
return ast.Subscript(slice=extslice, ctx=None, loc=None)
948960

949961
@action(Seq(Loc('.'), Loc('.'), Loc('.')))
950962
def subscript_1(self, dot_1_loc, dot_2_loc, dot_3_loc):
@@ -970,6 +982,7 @@ def subscript_3(self, lower_opt, colon_loc, upper_opt, step_opt):
970982
loc=loc, bound_colon_loc=colon_loc, step_colon_loc=step_colon_loc)
971983

972984
subscript_3_inner = Expect(Seq(Opt(Rule('test')), Opt(Rule('sliceop'))))
985+
973986
def subscript(self):
974987
"""subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]"""
975988
# This requires manual disambiguation of `test | [test] ...`
@@ -987,6 +1000,8 @@ def subscript(self):
9871000

9881001
result_2 = self.subscript_3_inner()
9891002
return self.subscript_3(result, result_1.loc, *result_2)
1003+
subscript.expected = \
1004+
lambda parser: parser.subscript_1.expected(parser) + parser.test.expected(parser)
9901005

9911006
sliceop = Seq(Loc(':'), Opt(Rule('test')))
9921007
"""sliceop: ':' [test]"""
@@ -1006,7 +1021,8 @@ def dictmaker(self, elts):
10061021
"""dictmaker: test ':' test (',' test ':' test)* [',']"""
10071022
return ast.Dict(keys=list(map(lambda x: x[0], elts)),
10081023
values=list(map(lambda x: x[2], elts)),
1009-
colon_locs=list(map(lambda x: x[1], elts)))
1024+
colon_locs=list(map(lambda x: x[1], elts)),
1025+
loc=None)
10101026

10111027
@action(Seq(Loc('class'), Tok('ident'),
10121028
Opt(BeginEnd('(', Rule('testlist'), ')')),
@@ -1035,12 +1051,12 @@ def arglist_1(self, star_loc, stararg, postargs, kwarg_opt):
10351051
raise diagnostic.DiagnosticException(error)
10361052

10371053
return ast.Call(starargs=stararg, kwargs=kwarg, keywords=postargs,
1038-
star_loc=star_loc, dstar_loc=dstar_loc)
1054+
star_loc=star_loc, dstar_loc=dstar_loc, loc=None)
10391055

10401056
@action(Seq(Loc('**'), Rule('test')))
10411057
def arglist_2(self, dstar_loc, kwarg):
10421058
return ast.Call(starargs=None, kwargs=kwarg, keywords=[],
1043-
star_loc=None, dstar_loc=dstar_loc)
1059+
star_loc=None, dstar_loc=dstar_loc, loc=None)
10441060

10451061
arglist_3 = Alt(arglist_1, arglist_2)
10461062

@@ -1066,8 +1082,8 @@ def arglist(self):
10661082

10671083
call = self.arglist_3()
10681084
if call is unmatched:
1069-
call = ast.Call(args=args, keywords=keywords, starargs=[], kwargs=[],
1070-
star_loc=None, dstar_loc=None)
1085+
call = ast.Call(args=args, keywords=keywords, starargs=None, kwargs=None,
1086+
star_loc=None, dstar_loc=None, loc=None)
10711087
else:
10721088
call.args = args
10731089
call.keywords = keywords + call.keywords
@@ -1106,7 +1122,8 @@ def argument(self):
11061122
"error", "keyword must be an identifier", {}, lhs.loc)
11071123
raise diagnostic.DiagnosticException(error)
11081124
return ast.keyword(arg=lhs.id, value=rhs,
1109-
loc=lhs.loc.join(rhs.loc), equals_loc=equals_loc)
1125+
loc=lhs.loc.join(rhs.loc),
1126+
arg_loc=lhs.loc, equals_loc=equals_loc)
11101127
else:
11111128
return lhs
11121129
argument.expected = test.expected

Diff for: ‎pyparser/test/test_parser.py

+283-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ def tearDownModule():
1313

1414
class ParserTestCase(unittest.TestCase):
1515

16+
maxDiff = None
17+
1618
def parser_for(self, code, version=(2, 6)):
1719
code = code.replace("·", "\n")
1820

@@ -36,7 +38,8 @@ def flatten_ast(self, node):
3638
self.assertTrue(attr in node._locs,
3739
"%s not in %s._locs" % (attr, repr(node)))
3840
for loc in node._locs:
39-
self.assertTrue(loc in node.__dict__)
41+
self.assertTrue(loc in node.__dict__,
42+
"%s not in %s._locs" % (loc, repr(node)))
4043

4144
flat_node = { 'ty': unicode(type(node).__name__) }
4245
for field in node._fields:
@@ -122,6 +125,17 @@ def assertDiagnosesUnexpected(self, code, err_token, loc):
122125
self.assertDiagnoses(code,
123126
("error", "unexpected {actual}: expected {expected}", {'actual': err_token}, loc))
124127

128+
# Fixtures
129+
130+
ast_1 = {'ty': 'Num', 'n': 1}
131+
ast_2 = {'ty': 'Num', 'n': 2}
132+
ast_3 = {'ty': 'Num', 'n': 3}
133+
134+
ast_x = {'ty': 'Name', 'id': 'x', 'ctx': None}
135+
ast_y = {'ty': 'Name', 'id': 'y', 'ctx': None}
136+
ast_z = {'ty': 'Name', 'id': 'z', 'ctx': None}
137+
ast_t = {'ty': 'Name', 'id': 't', 'ctx': None}
138+
125139
#
126140
# LITERALS
127141
#
@@ -162,8 +176,6 @@ def test_ident(self):
162176
# OPERATORS
163177
#
164178

165-
ast_1 = {'ty': 'Num', 'n': 1}
166-
167179
def test_unary(self):
168180
self.assertParsesExpr(
169181
{'ty': 'UnaryOp', 'op': {'ty': 'UAdd'}, 'operand': self.ast_1},
@@ -373,11 +385,276 @@ def test_boolop_multi(self):
373385
" ~~ op_locs.1")
374386

375387
#
376-
# STATEMENTS
388+
# COMPOUND LITERALS
377389
#
378390

379-
ast_x = {'ty': 'Name', 'id': 'x', 'ctx': None}
380-
ast_y = {'ty': 'Name', 'id': 'y', 'ctx': None}
391+
def test_tuple(self):
392+
self.assertParsesExpr(
393+
{'ty': 'Tuple', 'elts': [], 'ctx': None},
394+
"()",
395+
"^ begin_loc"
396+
" ^ end_loc"
397+
"~~ loc")
398+
399+
self.assertParsesExpr(
400+
{'ty': 'Tuple', 'elts': [self.ast_1], 'ctx': None},
401+
"(1,)",
402+
"~~~~ loc")
403+
404+
self.assertParsesExpr(
405+
{'ty': 'Tuple', 'elts': [self.ast_1, self.ast_1], 'ctx': None},
406+
"(1,1)",
407+
"~~~~~ loc")
408+
409+
self.assertParsesExpr(
410+
self.ast_1,
411+
"(1)",
412+
" ~ loc")
413+
414+
def test_list(self):
415+
self.assertParsesExpr(
416+
{'ty': 'List', 'elts': [], 'ctx': None},
417+
"[]",
418+
"^ begin_loc"
419+
" ^ end_loc"
420+
"~~ loc")
421+
422+
self.assertParsesExpr(
423+
{'ty': 'List', 'elts': [self.ast_1], 'ctx': None},
424+
"[1]",
425+
"~~~ loc")
426+
427+
self.assertParsesExpr(
428+
{'ty': 'List', 'elts': [self.ast_1, self.ast_1], 'ctx': None},
429+
"[1,1]",
430+
"~~~~~ loc")
431+
432+
def test_dict(self):
433+
self.assertParsesExpr(
434+
{'ty': 'Dict', 'keys': [], 'values': []},
435+
"{}",
436+
"^ begin_loc"
437+
" ^ end_loc"
438+
"~~ loc")
439+
440+
self.assertParsesExpr(
441+
{'ty': 'Dict', 'keys': [self.ast_x], 'values': [self.ast_1]},
442+
"{x: 1}",
443+
"^ begin_loc"
444+
" ^ end_loc"
445+
" ^ colon_locs.0"
446+
"~~~~~~ loc")
447+
448+
def test_repr(self):
449+
self.assertParsesExpr(
450+
{'ty': 'Repr', 'value': self.ast_1},
451+
"`1`",
452+
"^ begin_loc"
453+
" ^ end_loc"
454+
"~~~ loc")
455+
456+
#
457+
# GENERATOR AND CONDITIONAL EXPRESSIONS
458+
#
459+
460+
def test_list_comp(self):
461+
self.assertParsesExpr(
462+
{'ty': 'ListComp', 'elt': self.ast_x, 'generators': [
463+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y, 'ifs': []}
464+
]},
465+
"[x for y in z]",
466+
"^ begin_loc"
467+
" ~~~ generators.0.for_loc"
468+
" ~~ generators.0.in_loc"
469+
" ~~~~~~~~~~ generators.0.loc"
470+
" ^ end_loc"
471+
"~~~~~~~~~~~~~~ loc")
472+
473+
self.assertParsesExpr(
474+
{'ty': 'ListComp', 'elt': self.ast_x, 'generators': [
475+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y,
476+
'ifs': [self.ast_t]}
477+
]},
478+
"[x for y in z if t]",
479+
" ~~ generators.0.if_locs.0"
480+
" ~~~~~~~~~~~~~~~ generators.0.loc"
481+
"~~~~~~~~~~~~~~~~~~~ loc")
482+
483+
self.assertParsesExpr(
484+
{'ty': 'ListComp', 'elt': self.ast_x, 'generators': [
485+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y,
486+
'ifs': [self.ast_x]},
487+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_t, 'ifs': []}
488+
]},
489+
"[x for y in z if x for t in z]",
490+
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ loc")
491+
492+
def test_gen_comp(self):
493+
self.assertParsesExpr(
494+
{'ty': 'GeneratorExp', 'elt': self.ast_x, 'generators': [
495+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y, 'ifs': []}
496+
]},
497+
"(x for y in z)",
498+
"^ begin_loc"
499+
" ~~~ generators.0.for_loc"
500+
" ~~ generators.0.in_loc"
501+
" ~~~~~~~~~~ generators.0.loc"
502+
" ^ end_loc"
503+
"~~~~~~~~~~~~~~ loc")
504+
505+
self.assertParsesExpr(
506+
{'ty': 'GeneratorExp', 'elt': self.ast_x, 'generators': [
507+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y,
508+
'ifs': [self.ast_t]}
509+
]},
510+
"(x for y in z if t)",
511+
" ~~ generators.0.if_locs.0"
512+
" ~~~~~~~~~~~~~~~ generators.0.loc"
513+
"~~~~~~~~~~~~~~~~~~~ loc")
514+
515+
self.assertParsesExpr(
516+
{'ty': 'GeneratorExp', 'elt': self.ast_x, 'generators': [
517+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_y,
518+
'ifs': [self.ast_x]},
519+
{'ty': 'comprehension', 'iter': self.ast_z, 'target': self.ast_t, 'ifs': []}
520+
]},
521+
"(x for y in z if x for t in z)",
522+
"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ loc")
523+
524+
def test_yield_expr(self):
525+
self.assertParsesExpr(
526+
{'ty': 'Yield', 'value': self.ast_1},
527+
"(yield 1)",
528+
" ~~~~~ keyword_loc"
529+
" ~~~~~~~ loc")
530+
531+
def test_if_expr(self):
532+
self.assertParsesExpr(
533+
{'ty': 'IfExp', 'body': self.ast_x, 'test': self.ast_y, 'orelse': self.ast_z},
534+
"x if y else z",
535+
" ~~ if_loc"
536+
" ~~~~ else_loc"
537+
"~~~~~~~~~~~~~ loc")
538+
539+
#
540+
# CALLS, ATTRIBUTES AND SUBSCRIPTS
541+
#
542+
543+
def test_call(self):
544+
self.assertParsesExpr(
545+
{'ty': 'Call', 'func': self.ast_x, 'starargs': None, 'kwargs': None,
546+
'args': [], 'keywords': []},
547+
"x()",
548+
" ^ begin_loc"
549+
" ^ end_loc"
550+
"~~~ loc")
551+
552+
self.assertParsesExpr(
553+
{'ty': 'Call', 'func': self.ast_x, 'starargs': None, 'kwargs': None,
554+
'args': [self.ast_y], 'keywords': [
555+
{ 'ty': 'keyword', 'arg': 'z', 'value': self.ast_z}
556+
]},
557+
"x(y, z=z)",
558+
" ^ keywords.0.arg_loc"
559+
" ^ keywords.0.equals_loc"
560+
" ~~~ keywords.0.loc"
561+
"~~~~~~~~~ loc")
562+
563+
self.assertParsesExpr(
564+
{'ty': 'Call', 'func': self.ast_x, 'starargs': self.ast_y, 'kwargs': None,
565+
'args': [], 'keywords': []},
566+
"x(*y)",
567+
" ^ star_loc"
568+
"~~~~~ loc")
569+
570+
# self.assertParsesExpr(
571+
# {'ty': 'Call', 'func': self.ast_x, 'starargs': self.ast_y, 'kwargs': self.ast_z,
572+
# 'args': [], 'keywords': []},
573+
# "x(*y, **z)",
574+
# " ^ star_loc"
575+
# " ^^ dstar_loc"
576+
# "~~~~~~~~~~ loc")
577+
578+
# self.assertParsesExpr(
579+
# {'ty': 'Call', 'func': self.ast_x, 'starargs': self.ast_y, 'kwargs': self.ast_z,
580+
# 'args': [self.ast_t], 'keywords': []},
581+
# "x(*y, t, **z)",
582+
# " ^ star_loc"
583+
# " ^^ dstar_loc"
584+
# "~~~~~~~~~~~~~ loc")
585+
586+
self.assertParsesExpr(
587+
{'ty': 'Call', 'func': self.ast_x, 'starargs': None, 'kwargs': self.ast_z,
588+
'args': [], 'keywords': []},
589+
"x(**z)",
590+
" ^^ dstar_loc"
591+
"~~~~~~ loc")
592+
593+
def test_subscript(self):
594+
self.assertParsesExpr(
595+
{'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
596+
'slice': {'ty': 'Index', 'value': self.ast_1}},
597+
"x[1]",
598+
" ^ begin_loc"
599+
" ^ slice.loc"
600+
" ^ end_loc"
601+
"~~~~ loc")
602+
603+
self.assertParsesExpr(
604+
{'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
605+
'slice': {'ty': 'ExtSlice', 'dims': [
606+
{'ty': 'Index', 'value': self.ast_1},
607+
{'ty': 'Index', 'value': self.ast_2},
608+
]}},
609+
"x[1, 2]",
610+
" ~~~~ slice.loc"
611+
"~~~~~~~ loc")
612+
613+
# self.assertParsesExpr(
614+
# {'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
615+
# 'slice': {'ty': 'Slice', 'lower': self.ast_1, 'upper': None, 'step': None}},
616+
# "x[:1]",
617+
# " ^ slice.bound_colon_loc"
618+
# " ~~ slice.loc"
619+
# "~~~~~ loc")
620+
621+
self.assertParsesExpr(
622+
{'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
623+
'slice': {'ty': 'Slice', 'lower': self.ast_1, 'upper': self.ast_2, 'step': None}},
624+
"x[1:2]",
625+
" ^ slice.bound_colon_loc"
626+
" ~~~ slice.loc"
627+
"~~~~~~ loc")
628+
629+
self.assertParsesExpr(
630+
{'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
631+
'slice': {'ty': 'Slice', 'lower': self.ast_1, 'upper': self.ast_2, 'step': None}},
632+
"x[1:2:]",
633+
" ^ slice.bound_colon_loc"
634+
" ^ slice.step_colon_loc"
635+
" ~~~~ slice.loc"
636+
"~~~~~~~ loc")
637+
638+
self.assertParsesExpr(
639+
{'ty': 'Subscript', 'value': self.ast_x, 'ctx': None,
640+
'slice': {'ty': 'Slice', 'lower': self.ast_1, 'upper': self.ast_2, 'step': self.ast_3}},
641+
"x[1:2:3]",
642+
" ^ slice.bound_colon_loc"
643+
" ^ slice.step_colon_loc"
644+
" ~~~~~ slice.loc"
645+
"~~~~~~~~ loc")
646+
647+
def test_attribute(self):
648+
self.assertParsesExpr(
649+
{'ty': 'Attribute', 'value': self.ast_x, 'attr': 'zz', 'ctx': None},
650+
"x.zz",
651+
" ^ dot_loc"
652+
" ~~ attr_loc"
653+
"~~~~ loc")
654+
655+
#
656+
# STATEMENTS
657+
#
381658

382659
def test_assign(self):
383660
self.assertParsesSuite(

0 commit comments

Comments
 (0)
Please sign in to comment.