| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 1 | #------------------------------------------------------------------------------- | 
 | 2 | # Parser for ASDL [1] definition files. Reads in an ASDL description and parses | 
 | 3 | # it into an AST that describes it. | 
 | 4 | # | 
 | 5 | # The EBNF we're parsing here: Figure 1 of the paper [1]. Extended to support | 
 | 6 | # modules and attributes after a product. Words starting with Capital letters | 
 | 7 | # are terminals. Literal tokens are in "double quotes". Others are | 
 | 8 | # non-terminals. Id is either TokenId or ConstructorId. | 
 | 9 | # | 
 | 10 | # module        ::= "module" Id "{" [definitions] "}" | 
 | 11 | # definitions   ::= { TypeId "=" type } | 
 | 12 | # type          ::= product | sum | 
 | 13 | # product       ::= fields ["attributes" fields] | 
 | 14 | # fields        ::= "(" { field, "," } field ")" | 
 | 15 | # field         ::= TypeId ["?" | "*"] [Id] | 
 | 16 | # sum           ::= constructor { "|" constructor } ["attributes" fields] | 
 | 17 | # constructor   ::= ConstructorId [fields] | 
 | 18 | # | 
 | 19 | # [1] "The Zephyr Abstract Syntax Description Language" by Wang, et. al. See | 
 | 20 | #     http://asdl.sourceforge.net/ | 
 | 21 | #------------------------------------------------------------------------------- | 
 | 22 | from collections import namedtuple | 
 | 23 | import re | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 24 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 25 | __all__ = [ | 
 | 26 |     'builtin_types', 'parse', 'AST', 'Module', 'Type', 'Constructor', | 
 | 27 |     'Field', 'Sum', 'Product', 'VisitorBase', 'Check', 'check'] | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 28 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 29 | # The following classes define nodes into which the ASDL description is parsed. | 
 | 30 | # Note: this is a "meta-AST". ASDL files (such as Python.asdl) describe the AST | 
 | 31 | # structure used by a programming language. But ASDL files themselves need to be | 
 | 32 | # parsed. This module parses ASDL files and uses a simple AST to represent them. | 
 | 33 | # See the EBNF at the top of the file to understand the logical connection | 
 | 34 | # between the various node types. | 
| Martin v. Löwis | eae93b7 | 2006-02-28 00:12:47 +0000 | [diff] [blame] | 35 |  | 
| Raymond Hettinger | df1b699 | 2014-11-09 15:56:33 -0800 | [diff] [blame] | 36 | builtin_types = {'identifier', 'string', 'bytes', 'int', 'object', 'singleton'} | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 37 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 38 | class AST: | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 39 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 40 |         raise NotImplementedError | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 41 |  | 
 | 42 | class Module(AST): | 
| Benjamin Peterson | 6cb2b92 | 2011-03-12 18:28:16 -0600 | [diff] [blame] | 43 |     def __init__(self, name, dfns): | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 44 |         self.name = name | 
 | 45 |         self.dfns = dfns | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 46 |         self.types = {type.name: type.value for type in dfns} | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 47 |  | 
 | 48 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 49 |         return 'Module({0.name}, {0.dfns})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 50 |  | 
 | 51 | class Type(AST): | 
 | 52 |     def __init__(self, name, value): | 
 | 53 |         self.name = name | 
 | 54 |         self.value = value | 
 | 55 |  | 
 | 56 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 57 |         return 'Type({0.name}, {0.value})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 58 |  | 
 | 59 | class Constructor(AST): | 
 | 60 |     def __init__(self, name, fields=None): | 
 | 61 |         self.name = name | 
 | 62 |         self.fields = fields or [] | 
 | 63 |  | 
 | 64 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 65 |         return 'Constructor({0.name}, {0.fields})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 66 |  | 
 | 67 | class Field(AST): | 
| Benjamin Peterson | 87c8d87 | 2009-06-11 22:54:11 +0000 | [diff] [blame] | 68 |     def __init__(self, type, name=None, seq=False, opt=False): | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 69 |         self.type = type | 
 | 70 |         self.name = name | 
 | 71 |         self.seq = seq | 
 | 72 |         self.opt = opt | 
 | 73 |  | 
 | 74 |     def __repr__(self): | 
 | 75 |         if self.seq: | 
| Benjamin Peterson | 87c8d87 | 2009-06-11 22:54:11 +0000 | [diff] [blame] | 76 |             extra = ", seq=True" | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 77 |         elif self.opt: | 
| Benjamin Peterson | 87c8d87 | 2009-06-11 22:54:11 +0000 | [diff] [blame] | 78 |             extra = ", opt=True" | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 79 |         else: | 
 | 80 |             extra = "" | 
 | 81 |         if self.name is None: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 82 |             return 'Field({0.type}{1})'.format(self, extra) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 83 |         else: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 84 |             return 'Field({0.type}, {0.name}{1})'.format(self, extra) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 85 |  | 
 | 86 | class Sum(AST): | 
 | 87 |     def __init__(self, types, attributes=None): | 
 | 88 |         self.types = types | 
 | 89 |         self.attributes = attributes or [] | 
 | 90 |  | 
 | 91 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 92 |         if self.attributes: | 
 | 93 |             return 'Sum({0.types}, {0.attributes})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 94 |         else: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 95 |             return 'Sum({0.types})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 96 |  | 
 | 97 | class Product(AST): | 
| Benjamin Peterson | cda75be | 2013-03-18 10:48:58 -0700 | [diff] [blame] | 98 |     def __init__(self, fields, attributes=None): | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 99 |         self.fields = fields | 
| Benjamin Peterson | cda75be | 2013-03-18 10:48:58 -0700 | [diff] [blame] | 100 |         self.attributes = attributes or [] | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 101 |  | 
 | 102 |     def __repr__(self): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 103 |         if self.attributes: | 
 | 104 |             return 'Product({0.fields}, {0.attributes})'.format(self) | 
| Benjamin Peterson | cda75be | 2013-03-18 10:48:58 -0700 | [diff] [blame] | 105 |         else: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 106 |             return 'Product({0.fields})'.format(self) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 107 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 108 | # A generic visitor for the meta-AST that describes ASDL. This can be used by | 
 | 109 | # emitters. Note that this visitor does not provide a generic visit method, so a | 
 | 110 | # subclass needs to define visit methods from visitModule to as deep as the | 
 | 111 | # interesting node. | 
 | 112 | # We also define a Check visitor that makes sure the parsed ASDL is well-formed. | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 113 |  | 
| Guido van Rossum | 416b516 | 2014-07-08 16:22:48 -0700 | [diff] [blame] | 114 | class VisitorBase(object): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 115 |     """Generic tree visitor for ASTs.""" | 
 | 116 |     def __init__(self): | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 117 |         self.cache = {} | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 118 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 119 |     def visit(self, obj, *args): | 
 | 120 |         klass = obj.__class__ | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 121 |         meth = self.cache.get(klass) | 
 | 122 |         if meth is None: | 
 | 123 |             methname = "visit" + klass.__name__ | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 124 |             meth = getattr(self, methname, None) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 125 |             self.cache[klass] = meth | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 126 |         if meth: | 
 | 127 |             try: | 
 | 128 |                 meth(obj, *args) | 
 | 129 |             except Exception as e: | 
 | 130 |                 print("Error visiting %r: %s" % (obj, e)) | 
 | 131 |                 raise | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 132 |  | 
 | 133 | class Check(VisitorBase): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 134 |     """A visitor that checks a parsed ASDL tree for correctness. | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 135 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 136 |     Errors are printed and accumulated. | 
 | 137 |     """ | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 138 |     def __init__(self): | 
| Guido van Rossum | 416b516 | 2014-07-08 16:22:48 -0700 | [diff] [blame] | 139 |         super(Check, self).__init__() | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 140 |         self.cons = {} | 
 | 141 |         self.errors = 0 | 
 | 142 |         self.types = {} | 
 | 143 |  | 
 | 144 |     def visitModule(self, mod): | 
 | 145 |         for dfn in mod.dfns: | 
 | 146 |             self.visit(dfn) | 
 | 147 |  | 
 | 148 |     def visitType(self, type): | 
 | 149 |         self.visit(type.value, str(type.name)) | 
 | 150 |  | 
 | 151 |     def visitSum(self, sum, name): | 
 | 152 |         for t in sum.types: | 
 | 153 |             self.visit(t, name) | 
 | 154 |  | 
 | 155 |     def visitConstructor(self, cons, name): | 
 | 156 |         key = str(cons.name) | 
 | 157 |         conflict = self.cons.get(key) | 
 | 158 |         if conflict is None: | 
 | 159 |             self.cons[key] = name | 
 | 160 |         else: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 161 |             print('Redefinition of constructor {}'.format(key)) | 
 | 162 |             print('Defined in {} and {}'.format(conflict, name)) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 163 |             self.errors += 1 | 
 | 164 |         for f in cons.fields: | 
 | 165 |             self.visit(f, key) | 
 | 166 |  | 
 | 167 |     def visitField(self, field, name): | 
 | 168 |         key = str(field.type) | 
 | 169 |         l = self.types.setdefault(key, []) | 
 | 170 |         l.append(name) | 
 | 171 |  | 
 | 172 |     def visitProduct(self, prod, name): | 
 | 173 |         for f in prod.fields: | 
 | 174 |             self.visit(f, name) | 
 | 175 |  | 
 | 176 | def check(mod): | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 177 |     """Check the parsed ASDL tree for correctness. | 
 | 178 |  | 
 | 179 |     Return True if success. For failure, the errors are printed out and False | 
 | 180 |     is returned. | 
 | 181 |     """ | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 182 |     v = Check() | 
 | 183 |     v.visit(mod) | 
 | 184 |  | 
 | 185 |     for t in v.types: | 
| Fred Drake | 34e4f52 | 2006-12-29 04:42:48 +0000 | [diff] [blame] | 186 |         if t not in mod.types and not t in builtin_types: | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 187 |             v.errors += 1 | 
 | 188 |             uses = ", ".join(v.types[t]) | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 189 |             print('Undefined type {}, used in {}'.format(t, uses)) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 190 |     return not v.errors | 
 | 191 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 192 | # The ASDL parser itself comes next. The only interesting external interface | 
 | 193 | # here is the top-level parse function. | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 194 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 195 | def parse(filename): | 
 | 196 |     """Parse ASDL from the given file and return a Module node describing it.""" | 
 | 197 |     with open(filename) as f: | 
 | 198 |         parser = ASDLParser() | 
 | 199 |         return parser.parse(f.read()) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 200 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 201 | # Types for describing tokens in an ASDL specification. | 
 | 202 | class TokenKind: | 
 | 203 |     """TokenKind is provides a scope for enumerated token kinds.""" | 
 | 204 |     (ConstructorId, TypeId, Equals, Comma, Question, Pipe, Asterisk, | 
 | 205 |      LParen, RParen, LBrace, RBrace) = range(11) | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 206 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 207 |     operator_table = { | 
 | 208 |         '=': Equals, ',': Comma,    '?': Question, '|': Pipe,    '(': LParen, | 
 | 209 |         ')': RParen, '*': Asterisk, '{': LBrace,   '}': RBrace} | 
| Tim Peters | 536cf99 | 2005-12-25 23:18:31 +0000 | [diff] [blame] | 210 |  | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 211 | Token = namedtuple('Token', 'kind value lineno') | 
 | 212 |  | 
 | 213 | class ASDLSyntaxError(Exception): | 
 | 214 |     def __init__(self, msg, lineno=None): | 
 | 215 |         self.msg = msg | 
 | 216 |         self.lineno = lineno or '<unknown>' | 
 | 217 |  | 
 | 218 |     def __str__(self): | 
 | 219 |         return 'Syntax error on line {0.lineno}: {0.msg}'.format(self) | 
 | 220 |  | 
 | 221 | def tokenize_asdl(buf): | 
 | 222 |     """Tokenize the given buffer. Yield Token objects.""" | 
 | 223 |     for lineno, line in enumerate(buf.splitlines(), 1): | 
 | 224 |         for m in re.finditer(r'\s*(\w+|--.*|.)', line.strip()): | 
 | 225 |             c = m.group(1) | 
 | 226 |             if c[0].isalpha(): | 
 | 227 |                 # Some kind of identifier | 
 | 228 |                 if c[0].isupper(): | 
 | 229 |                     yield Token(TokenKind.ConstructorId, c, lineno) | 
 | 230 |                 else: | 
 | 231 |                     yield Token(TokenKind.TypeId, c, lineno) | 
 | 232 |             elif c[:2] == '--': | 
 | 233 |                 # Comment | 
 | 234 |                 break | 
 | 235 |             else: | 
 | 236 |                 # Operators | 
 | 237 |                 try: | 
 | 238 |                     op_kind = TokenKind.operator_table[c] | 
 | 239 |                 except KeyError: | 
 | 240 |                     raise ASDLSyntaxError('Invalid operator %s' % c, lineno) | 
 | 241 |                 yield Token(op_kind, c, lineno) | 
 | 242 |  | 
 | 243 | class ASDLParser: | 
 | 244 |     """Parser for ASDL files. | 
 | 245 |  | 
 | 246 |     Create, then call the parse method on a buffer containing ASDL. | 
 | 247 |     This is a simple recursive descent parser that uses tokenize_asdl for the | 
 | 248 |     lexing. | 
 | 249 |     """ | 
 | 250 |     def __init__(self): | 
 | 251 |         self._tokenizer = None | 
 | 252 |         self.cur_token = None | 
 | 253 |  | 
 | 254 |     def parse(self, buf): | 
 | 255 |         """Parse the ASDL in the buffer and return an AST with a Module root. | 
 | 256 |         """ | 
 | 257 |         self._tokenizer = tokenize_asdl(buf) | 
 | 258 |         self._advance() | 
 | 259 |         return self._parse_module() | 
 | 260 |  | 
 | 261 |     def _parse_module(self): | 
 | 262 |         if self._at_keyword('module'): | 
 | 263 |             self._advance() | 
| Jeremy Hylton | 3e0055f | 2005-10-20 19:59:25 +0000 | [diff] [blame] | 264 |         else: | 
| Eli Bendersky | 5e3d338 | 2014-05-09 17:58:22 -0700 | [diff] [blame] | 265 |             raise ASDLSyntaxError( | 
 | 266 |                 'Expected "module" (found {})'.format(self.cur_token.value), | 
 | 267 |                 self.cur_token.lineno) | 
 | 268 |         name = self._match(self._id_kinds) | 
 | 269 |         self._match(TokenKind.LBrace) | 
 | 270 |         defs = self._parse_definitions() | 
 | 271 |         self._match(TokenKind.RBrace) | 
 | 272 |         return Module(name, defs) | 
 | 273 |  | 
 | 274 |     def _parse_definitions(self): | 
 | 275 |         defs = [] | 
 | 276 |         while self.cur_token.kind == TokenKind.TypeId: | 
 | 277 |             typename = self._advance() | 
 | 278 |             self._match(TokenKind.Equals) | 
 | 279 |             type = self._parse_type() | 
 | 280 |             defs.append(Type(typename, type)) | 
 | 281 |         return defs | 
 | 282 |  | 
 | 283 |     def _parse_type(self): | 
 | 284 |         if self.cur_token.kind == TokenKind.LParen: | 
 | 285 |             # If we see a (, it's a product | 
 | 286 |             return self._parse_product() | 
 | 287 |         else: | 
 | 288 |             # Otherwise it's a sum. Look for ConstructorId | 
 | 289 |             sumlist = [Constructor(self._match(TokenKind.ConstructorId), | 
 | 290 |                                    self._parse_optional_fields())] | 
 | 291 |             while self.cur_token.kind  == TokenKind.Pipe: | 
 | 292 |                 # More constructors | 
 | 293 |                 self._advance() | 
 | 294 |                 sumlist.append(Constructor( | 
 | 295 |                                 self._match(TokenKind.ConstructorId), | 
 | 296 |                                 self._parse_optional_fields())) | 
 | 297 |             return Sum(sumlist, self._parse_optional_attributes()) | 
 | 298 |  | 
 | 299 |     def _parse_product(self): | 
 | 300 |         return Product(self._parse_fields(), self._parse_optional_attributes()) | 
 | 301 |  | 
 | 302 |     def _parse_fields(self): | 
 | 303 |         fields = [] | 
 | 304 |         self._match(TokenKind.LParen) | 
 | 305 |         while self.cur_token.kind == TokenKind.TypeId: | 
 | 306 |             typename = self._advance() | 
 | 307 |             is_seq, is_opt = self._parse_optional_field_quantifier() | 
 | 308 |             id = (self._advance() if self.cur_token.kind in self._id_kinds | 
 | 309 |                                   else None) | 
 | 310 |             fields.append(Field(typename, id, seq=is_seq, opt=is_opt)) | 
 | 311 |             if self.cur_token.kind == TokenKind.RParen: | 
 | 312 |                 break | 
 | 313 |             elif self.cur_token.kind == TokenKind.Comma: | 
 | 314 |                 self._advance() | 
 | 315 |         self._match(TokenKind.RParen) | 
 | 316 |         return fields | 
 | 317 |  | 
 | 318 |     def _parse_optional_fields(self): | 
 | 319 |         if self.cur_token.kind == TokenKind.LParen: | 
 | 320 |             return self._parse_fields() | 
 | 321 |         else: | 
 | 322 |             return None | 
 | 323 |  | 
 | 324 |     def _parse_optional_attributes(self): | 
 | 325 |         if self._at_keyword('attributes'): | 
 | 326 |             self._advance() | 
 | 327 |             return self._parse_fields() | 
 | 328 |         else: | 
 | 329 |             return None | 
 | 330 |  | 
 | 331 |     def _parse_optional_field_quantifier(self): | 
 | 332 |         is_seq, is_opt = False, False | 
 | 333 |         if self.cur_token.kind == TokenKind.Asterisk: | 
 | 334 |             is_seq = True | 
 | 335 |             self._advance() | 
 | 336 |         elif self.cur_token.kind == TokenKind.Question: | 
 | 337 |             is_opt = True | 
 | 338 |             self._advance() | 
 | 339 |         return is_seq, is_opt | 
 | 340 |  | 
 | 341 |     def _advance(self): | 
 | 342 |         """ Return the value of the current token and read the next one into | 
 | 343 |             self.cur_token. | 
 | 344 |         """ | 
 | 345 |         cur_val = None if self.cur_token is None else self.cur_token.value | 
 | 346 |         try: | 
 | 347 |             self.cur_token = next(self._tokenizer) | 
 | 348 |         except StopIteration: | 
 | 349 |             self.cur_token = None | 
 | 350 |         return cur_val | 
 | 351 |  | 
 | 352 |     _id_kinds = (TokenKind.ConstructorId, TokenKind.TypeId) | 
 | 353 |  | 
 | 354 |     def _match(self, kind): | 
 | 355 |         """The 'match' primitive of RD parsers. | 
 | 356 |  | 
 | 357 |         * Verifies that the current token is of the given kind (kind can | 
 | 358 |           be a tuple, in which the kind must match one of its members). | 
 | 359 |         * Returns the value of the current token | 
 | 360 |         * Reads in the next token | 
 | 361 |         """ | 
 | 362 |         if (isinstance(kind, tuple) and self.cur_token.kind in kind or | 
 | 363 |             self.cur_token.kind == kind | 
 | 364 |             ): | 
 | 365 |             value = self.cur_token.value | 
 | 366 |             self._advance() | 
 | 367 |             return value | 
 | 368 |         else: | 
 | 369 |             raise ASDLSyntaxError( | 
 | 370 |                 'Unmatched {} (found {})'.format(kind, self.cur_token.kind), | 
 | 371 |                 self.cur_token.lineno) | 
 | 372 |  | 
 | 373 |     def _at_keyword(self, keyword): | 
 | 374 |         return (self.cur_token.kind == TokenKind.TypeId and | 
 | 375 |                 self.cur_token.value == keyword) |