summaryrefslogtreecommitdiffstats
path: root/Parser/asdl.py
diff options
context:
space:
mode:
Diffstat (limited to 'Parser/asdl.py')
-rw-r--r--Parser/asdl.py573
1 files changed, 305 insertions, 268 deletions
diff --git a/Parser/asdl.py b/Parser/asdl.py
index 62f5c19..1ddc3f8 100644
--- a/Parser/asdl.py
+++ b/Parser/asdl.py
@@ -1,53 +1,243 @@
-#-------------------------------------------------------------------------------
-# Parser for ASDL [1] definition files. Reads in an ASDL description and parses
-# it into an AST that describes it.
-#
-# The EBNF we're parsing here: Figure 1 of the paper [1]. Extended to support
-# modules and attributes after a product. Words starting with Capital letters
-# are terminals. Literal tokens are in "double quotes". Others are
-# non-terminals. Id is either TokenId or ConstructorId.
-#
-# module ::= "module" Id "{" [definitions] "}"
-# definitions ::= { TypeId "=" type }
-# type ::= product | sum
-# product ::= fields ["attributes" fields]
-# fields ::= "(" { field, "," } field ")"
-# field ::= TypeId ["?" | "*"] [Id]
-# sum ::= constructor { "|" constructor } ["attributes" fields]
-# constructor ::= ConstructorId [fields]
-#
-# [1] "The Zephyr Abstract Syntax Description Language" by Wang, et. al. See
-# http://asdl.sourceforge.net/
-#-------------------------------------------------------------------------------
-from collections import namedtuple
-import re
-
-__all__ = [
- 'builtin_types', 'parse', 'AST', 'Module', 'Type', 'Constructor',
- 'Field', 'Sum', 'Product', 'VisitorBase', 'Check', 'check']
-
-# The following classes define nodes into which the ASDL description is parsed.
-# Note: this is a "meta-AST". ASDL files (such as Python.asdl) describe the AST
-# structure used by a programming language. But ASDL files themselves need to be
-# parsed. This module parses ASDL files and uses a simple AST to represent them.
-# See the EBNF at the top of the file to understand the logical connection
-# between the various node types.
-
-builtin_types = {'identifier', 'string', 'bytes', 'int', 'object', 'singleton',
- 'constant'}
-
-class AST:
+"""An implementation of the Zephyr Abstract Syntax Definition Language.
+
+See http://asdl.sourceforge.net/ and
+http://www.cs.princeton.edu/research/techreps/TR-554-97
+
+Only supports top level module decl, not view. I'm guessing that view
+is intended to support the browser and I'm not interested in the
+browser.
+
+Changes for Python: Add support for module versions
+"""
+
+import os
+import traceback
+
+import spark
+
+class Token(object):
+ # spark seems to dispatch in the parser based on a token's
+ # type attribute
+ def __init__(self, type, lineno):
+ self.type = type
+ self.lineno = lineno
+
+ def __str__(self):
+ return self.type
+
def __repr__(self):
- raise NotImplementedError
+ return str(self)
+
+class Id(Token):
+ def __init__(self, value, lineno):
+ self.type = 'Id'
+ self.value = value
+ self.lineno = lineno
+
+ def __str__(self):
+ return self.value
+
+class String(Token):
+ def __init__(self, value, lineno):
+ self.type = 'String'
+ self.value = value
+ self.lineno = lineno
+
+class ASDLSyntaxError(Exception):
+
+ def __init__(self, lineno, token=None, msg=None):
+ self.lineno = lineno
+ self.token = token
+ self.msg = msg
+
+ def __str__(self):
+ if self.msg is None:
+ return "Error at '%s', line %d" % (self.token, self.lineno)
+ else:
+ return "%s, line %d" % (self.msg, self.lineno)
+
+class ASDLScanner(spark.GenericScanner, object):
+
+ def tokenize(self, input):
+ self.rv = []
+ self.lineno = 1
+ super(ASDLScanner, self).tokenize(input)
+ return self.rv
+
+ def t_id(self, s):
+ r"[\w\.]+"
+ # XXX doesn't distinguish upper vs. lower, which is
+ # significant for ASDL.
+ self.rv.append(Id(s, self.lineno))
+
+ def t_string(self, s):
+ r'"[^"]*"'
+ self.rv.append(String(s, self.lineno))
+
+ def t_xxx(self, s): # not sure what this production means
+ r"<="
+ self.rv.append(Token(s, self.lineno))
+
+ def t_punctuation(self, s):
+ r"[\{\}\*\=\|\(\)\,\?\:]"
+ self.rv.append(Token(s, self.lineno))
+
+ def t_comment(self, s):
+ r"\-\-[^\n]*"
+ pass
+
+ def t_newline(self, s):
+ r"\n"
+ self.lineno += 1
+
+ def t_whitespace(self, s):
+ r"[ \t]+"
+ pass
+
+ def t_default(self, s):
+ r" . +"
+ raise ValueError, "unmatched input: %s" % `s`
+
+class ASDLParser(spark.GenericParser, object):
+ def __init__(self):
+ super(ASDLParser, self).__init__("module")
+
+ def typestring(self, tok):
+ return tok.type
+
+ def error(self, tok):
+ raise ASDLSyntaxError(tok.lineno, tok)
+
+ def p_module_0(self, (module, name, version, _0, _1)):
+ " module ::= Id Id version { } "
+ if module.value != "module":
+ raise ASDLSyntaxError(module.lineno,
+ msg="expected 'module', found %s" % module)
+ return Module(name, None, version)
+
+ def p_module(self, (module, name, version, _0, definitions, _1)):
+ " module ::= Id Id version { definitions } "
+ if module.value != "module":
+ raise ASDLSyntaxError(module.lineno,
+ msg="expected 'module', found %s" % module)
+ return Module(name, definitions, version)
+
+ def p_version(self, (version, V)):
+ "version ::= Id String"
+ if version.value != "version":
+ raise ASDLSyntaxError(version.lineno,
+ msg="expected 'version', found %s" % version)
+ return V
+
+ def p_definition_0(self, (definition,)):
+ " definitions ::= definition "
+ return definition
+
+ def p_definition_1(self, (definitions, definition)):
+ " definitions ::= definition definitions "
+ return definitions + definition
+
+ def p_definition(self, (id, _, type)):
+ " definition ::= Id = type "
+ return [Type(id, type)]
+
+ def p_type_0(self, (product,)):
+ " type ::= product "
+ return product
+
+ def p_type_1(self, (sum,)):
+ " type ::= sum "
+ return Sum(sum)
+
+ def p_type_2(self, (sum, id, _0, attributes, _1)):
+ " type ::= sum Id ( fields ) "
+ if id.value != "attributes":
+ raise ASDLSyntaxError(id.lineno,
+ msg="expected attributes, found %s" % id)
+ if attributes:
+ attributes.reverse()
+ return Sum(sum, attributes)
+
+ def p_product(self, (_0, fields, _1)):
+ " product ::= ( fields ) "
+ # XXX can't I just construct things in the right order?
+ fields.reverse()
+ return Product(fields)
+
+ def p_sum_0(self, (constructor,)):
+ " sum ::= constructor "
+ return [constructor]
+
+ def p_sum_1(self, (constructor, _, sum)):
+ " sum ::= constructor | sum "
+ return [constructor] + sum
+
+ def p_sum_2(self, (constructor, _, sum)):
+ " sum ::= constructor | sum "
+ return [constructor] + sum
+
+ def p_constructor_0(self, (id,)):
+ " constructor ::= Id "
+ return Constructor(id)
+
+ def p_constructor_1(self, (id, _0, fields, _1)):
+ " constructor ::= Id ( fields ) "
+ # XXX can't I just construct things in the right order?
+ fields.reverse()
+ return Constructor(id, fields)
+
+ def p_fields_0(self, (field,)):
+ " fields ::= field "
+ return [field]
+
+ def p_fields_1(self, (field, _, fields)):
+ " fields ::= field , fields "
+ return fields + [field]
+
+ def p_field_0(self, (type,)):
+ " field ::= Id "
+ return Field(type)
+
+ def p_field_1(self, (type, name)):
+ " field ::= Id Id "
+ return Field(type, name)
+
+ def p_field_2(self, (type, _, name)):
+ " field ::= Id * Id "
+ return Field(type, name, seq=True)
+
+ def p_field_3(self, (type, _, name)):
+ " field ::= Id ? Id "
+ return Field(type, name, opt=True)
+
+ def p_field_4(self, (type, _)):
+ " field ::= Id * "
+ return Field(type, seq=True)
+
+ def p_field_5(self, (type, _)):
+ " field ::= Id ? "
+ return Field(type, opt=True)
+
+builtin_types = ("identifier", "string", "int", "bool", "object")
+
+# below is a collection of classes to capture the AST of an AST :-)
+# not sure if any of the methods are useful yet, but I'm adding them
+# piecemeal as they seem helpful
+
+class AST(object):
+ pass # a marker class
class Module(AST):
- def __init__(self, name, dfns):
+ def __init__(self, name, dfns, version):
self.name = name
self.dfns = dfns
- self.types = {type.name: type.value for type in dfns}
+ self.version = version
+ self.types = {} # maps type name to value (from dfns)
+ for type in dfns:
+ self.types[type.name.value] = type.value
def __repr__(self):
- return 'Module({0.name}, {0.dfns})'.format(self)
+ return "Module(%s, %s)" % (self.name, self.dfns)
class Type(AST):
def __init__(self, name, value):
@@ -55,7 +245,7 @@ class Type(AST):
self.value = value
def __repr__(self):
- return 'Type({0.name}, {0.value})'.format(self)
+ return "Type(%s, %s)" % (self.name, self.value)
class Constructor(AST):
def __init__(self, name, fields=None):
@@ -63,7 +253,7 @@ class Constructor(AST):
self.fields = fields or []
def __repr__(self):
- return 'Constructor({0.name}, {0.fields})'.format(self)
+ return "Constructor(%s, %s)" % (self.name, self.fields)
class Field(AST):
def __init__(self, type, name=None, seq=False, opt=False):
@@ -80,9 +270,9 @@ class Field(AST):
else:
extra = ""
if self.name is None:
- return 'Field({0.type}{1})'.format(self, extra)
+ return "Field(%s%s)" % (self.type, extra)
else:
- return 'Field({0.type}, {0.name}{1})'.format(self, extra)
+ return "Field(%s, %s%s)" % (self.type, self.name, extra)
class Sum(AST):
def __init__(self, types, attributes=None):
@@ -90,54 +280,56 @@ class Sum(AST):
self.attributes = attributes or []
def __repr__(self):
- if self.attributes:
- return 'Sum({0.types}, {0.attributes})'.format(self)
+ if self.attributes is None:
+ return "Sum(%s)" % self.types
else:
- return 'Sum({0.types})'.format(self)
+ return "Sum(%s, %s)" % (self.types, self.attributes)
class Product(AST):
- def __init__(self, fields, attributes=None):
+ def __init__(self, fields):
self.fields = fields
- self.attributes = attributes or []
def __repr__(self):
- if self.attributes:
- return 'Product({0.fields}, {0.attributes})'.format(self)
- else:
- return 'Product({0.fields})'.format(self)
-
-# A generic visitor for the meta-AST that describes ASDL. This can be used by
-# emitters. Note that this visitor does not provide a generic visit method, so a
-# subclass needs to define visit methods from visitModule to as deep as the
-# interesting node.
-# We also define a Check visitor that makes sure the parsed ASDL is well-formed.
+ return "Product(%s)" % self.fields
class VisitorBase(object):
- """Generic tree visitor for ASTs."""
- def __init__(self):
+
+ def __init__(self, skip=False):
self.cache = {}
+ self.skip = skip
- def visit(self, obj, *args):
- klass = obj.__class__
+ def visit(self, object, *args):
+ meth = self._dispatch(object)
+ if meth is None:
+ return
+ try:
+ meth(object, *args)
+ except Exception, err:
+ print "Error visiting", repr(object)
+ print err
+ traceback.print_exc()
+ # XXX hack
+ if hasattr(self, 'file'):
+ self.file.flush()
+ os._exit(1)
+
+ def _dispatch(self, object):
+ assert isinstance(object, AST), repr(object)
+ klass = object.__class__
meth = self.cache.get(klass)
if meth is None:
methname = "visit" + klass.__name__
- meth = getattr(self, methname, None)
+ if self.skip:
+ meth = getattr(self, methname, None)
+ else:
+ meth = getattr(self, methname)
self.cache[klass] = meth
- if meth:
- try:
- meth(obj, *args)
- except Exception as e:
- print("Error visiting %r: %s" % (obj, e))
- raise
+ return meth
class Check(VisitorBase):
- """A visitor that checks a parsed ASDL tree for correctness.
- Errors are printed and accumulated.
- """
def __init__(self):
- super(Check, self).__init__()
+ super(Check, self).__init__(skip=True)
self.cons = {}
self.errors = 0
self.types = {}
@@ -159,8 +351,8 @@ class Check(VisitorBase):
if conflict is None:
self.cons[key] = name
else:
- print('Redefinition of constructor {}'.format(key))
- print('Defined in {} and {}'.format(conflict, name))
+ print "Redefinition of constructor %s" % key
+ print "Defined in %s and %s" % (conflict, name)
self.errors += 1
for f in cons.fields:
self.visit(f, key)
@@ -175,11 +367,6 @@ class Check(VisitorBase):
self.visit(f, name)
def check(mod):
- """Check the parsed ASDL tree for correctness.
-
- Return True if success. For failure, the errors are printed out and False
- is returned.
- """
v = Check()
v.visit(mod)
@@ -187,190 +374,40 @@ def check(mod):
if t not in mod.types and not t in builtin_types:
v.errors += 1
uses = ", ".join(v.types[t])
- print('Undefined type {}, used in {}'.format(t, uses))
- return not v.errors
-
-# The ASDL parser itself comes next. The only interesting external interface
-# here is the top-level parse function.
-
-def parse(filename):
- """Parse ASDL from the given file and return a Module node describing it."""
- with open(filename) as f:
- parser = ASDLParser()
- return parser.parse(f.read())
-
-# Types for describing tokens in an ASDL specification.
-class TokenKind:
- """TokenKind is provides a scope for enumerated token kinds."""
- (ConstructorId, TypeId, Equals, Comma, Question, Pipe, Asterisk,
- LParen, RParen, LBrace, RBrace) = range(11)
+ print "Undefined type %s, used in %s" % (t, uses)
- operator_table = {
- '=': Equals, ',': Comma, '?': Question, '|': Pipe, '(': LParen,
- ')': RParen, '*': Asterisk, '{': LBrace, '}': RBrace}
-
-Token = namedtuple('Token', 'kind value lineno')
-
-class ASDLSyntaxError(Exception):
- def __init__(self, msg, lineno=None):
- self.msg = msg
- self.lineno = lineno or '<unknown>'
-
- def __str__(self):
- return 'Syntax error on line {0.lineno}: {0.msg}'.format(self)
-
-def tokenize_asdl(buf):
- """Tokenize the given buffer. Yield Token objects."""
- for lineno, line in enumerate(buf.splitlines(), 1):
- for m in re.finditer(r'\s*(\w+|--.*|.)', line.strip()):
- c = m.group(1)
- if c[0].isalpha():
- # Some kind of identifier
- if c[0].isupper():
- yield Token(TokenKind.ConstructorId, c, lineno)
- else:
- yield Token(TokenKind.TypeId, c, lineno)
- elif c[:2] == '--':
- # Comment
- break
- else:
- # Operators
- try:
- op_kind = TokenKind.operator_table[c]
- except KeyError:
- raise ASDLSyntaxError('Invalid operator %s' % c, lineno)
- yield Token(op_kind, c, lineno)
-
-class ASDLParser:
- """Parser for ASDL files.
-
- Create, then call the parse method on a buffer containing ASDL.
- This is a simple recursive descent parser that uses tokenize_asdl for the
- lexing.
- """
- def __init__(self):
- self._tokenizer = None
- self.cur_token = None
-
- def parse(self, buf):
- """Parse the ASDL in the buffer and return an AST with a Module root.
- """
- self._tokenizer = tokenize_asdl(buf)
- self._advance()
- return self._parse_module()
-
- def _parse_module(self):
- if self._at_keyword('module'):
- self._advance()
- else:
- raise ASDLSyntaxError(
- 'Expected "module" (found {})'.format(self.cur_token.value),
- self.cur_token.lineno)
- name = self._match(self._id_kinds)
- self._match(TokenKind.LBrace)
- defs = self._parse_definitions()
- self._match(TokenKind.RBrace)
- return Module(name, defs)
-
- def _parse_definitions(self):
- defs = []
- while self.cur_token.kind == TokenKind.TypeId:
- typename = self._advance()
- self._match(TokenKind.Equals)
- type = self._parse_type()
- defs.append(Type(typename, type))
- return defs
-
- def _parse_type(self):
- if self.cur_token.kind == TokenKind.LParen:
- # If we see a (, it's a product
- return self._parse_product()
- else:
- # Otherwise it's a sum. Look for ConstructorId
- sumlist = [Constructor(self._match(TokenKind.ConstructorId),
- self._parse_optional_fields())]
- while self.cur_token.kind == TokenKind.Pipe:
- # More constructors
- self._advance()
- sumlist.append(Constructor(
- self._match(TokenKind.ConstructorId),
- self._parse_optional_fields()))
- return Sum(sumlist, self._parse_optional_attributes())
-
- def _parse_product(self):
- return Product(self._parse_fields(), self._parse_optional_attributes())
-
- def _parse_fields(self):
- fields = []
- self._match(TokenKind.LParen)
- while self.cur_token.kind == TokenKind.TypeId:
- typename = self._advance()
- is_seq, is_opt = self._parse_optional_field_quantifier()
- id = (self._advance() if self.cur_token.kind in self._id_kinds
- else None)
- fields.append(Field(typename, id, seq=is_seq, opt=is_opt))
- if self.cur_token.kind == TokenKind.RParen:
- break
- elif self.cur_token.kind == TokenKind.Comma:
- self._advance()
- self._match(TokenKind.RParen)
- return fields
-
- def _parse_optional_fields(self):
- if self.cur_token.kind == TokenKind.LParen:
- return self._parse_fields()
- else:
- return None
+ return not v.errors
- def _parse_optional_attributes(self):
- if self._at_keyword('attributes'):
- self._advance()
- return self._parse_fields()
+def parse(file):
+ scanner = ASDLScanner()
+ parser = ASDLParser()
+
+ buf = open(file).read()
+ tokens = scanner.tokenize(buf)
+ try:
+ return parser.parse(tokens)
+ except ASDLSyntaxError, err:
+ print err
+ lines = buf.split("\n")
+ print lines[err.lineno - 1] # lines starts at 0, files at 1
+
+if __name__ == "__main__":
+ import glob
+ import sys
+
+ if len(sys.argv) > 1:
+ files = sys.argv[1:]
+ else:
+ testdir = "tests"
+ files = glob.glob(testdir + "/*.asdl")
+
+ for file in files:
+ print file
+ mod = parse(file)
+ print "module", mod.name
+ print len(mod.dfns), "definitions"
+ if not check(mod):
+ print "Check failed"
else:
- return None
-
- def _parse_optional_field_quantifier(self):
- is_seq, is_opt = False, False
- if self.cur_token.kind == TokenKind.Asterisk:
- is_seq = True
- self._advance()
- elif self.cur_token.kind == TokenKind.Question:
- is_opt = True
- self._advance()
- return is_seq, is_opt
-
- def _advance(self):
- """ Return the value of the current token and read the next one into
- self.cur_token.
- """
- cur_val = None if self.cur_token is None else self.cur_token.value
- try:
- self.cur_token = next(self._tokenizer)
- except StopIteration:
- self.cur_token = None
- return cur_val
-
- _id_kinds = (TokenKind.ConstructorId, TokenKind.TypeId)
-
- def _match(self, kind):
- """The 'match' primitive of RD parsers.
-
- * Verifies that the current token is of the given kind (kind can
- be a tuple, in which the kind must match one of its members).
- * Returns the value of the current token
- * Reads in the next token
- """
- if (isinstance(kind, tuple) and self.cur_token.kind in kind or
- self.cur_token.kind == kind
- ):
- value = self.cur_token.value
- self._advance()
- return value
- else:
- raise ASDLSyntaxError(
- 'Unmatched {} (found {})'.format(kind, self.cur_token.kind),
- self.cur_token.lineno)
-
- def _at_keyword(self, keyword):
- return (self.cur_token.kind == TokenKind.TypeId and
- self.cur_token.value == keyword)
+ for dfn in mod.dfns:
+ print dfn.type