asdl.py 11.5 KB
Newer Older
Jeremy Hylton's avatar
Jeremy Hylton committed
1 2 3
"""An implementation of the Zephyr Abstract Syntax Definition Language.

See http://asdl.sourceforge.net/ and
4
http://www.cs.princeton.edu/research/techreps/TR-554-97
Jeremy Hylton's avatar
Jeremy Hylton committed
5 6 7 8

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.
9 10

Changes for Python: Add support for module versions
Jeremy Hylton's avatar
Jeremy Hylton committed
11 12 13
"""

import os
14
import sys
Jeremy Hylton's avatar
Jeremy Hylton committed
15 16 17 18
import traceback

import spark

19 20 21 22
def output(string):
    sys.stdout.write(string + "\n")


Benjamin Peterson's avatar
Benjamin Peterson committed
23
class Token(object):
Jeremy Hylton's avatar
Jeremy Hylton committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
    # 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):
        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
Tim Peters's avatar
Tim Peters committed
44

45 46 47 48 49
class String(Token):
    def __init__(self, value, lineno):
        self.type = 'String'
        self.value = value
        self.lineno = lineno
Jeremy Hylton's avatar
Jeremy Hylton committed
50

51
class ASDLSyntaxError(Exception):
Jeremy Hylton's avatar
Jeremy Hylton committed
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

    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))
Tim Peters's avatar
Tim Peters committed
77

78 79 80
    def t_string(self, s):
        r'"[^"]*"'
        self.rv.append(String(s, self.lineno))
Jeremy Hylton's avatar
Jeremy Hylton committed
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103

    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" . +"
104
        raise ValueError("unmatched input: %r" % s)
Jeremy Hylton's avatar
Jeremy Hylton committed
105 106 107 108 109 110 111 112 113 114 115

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)

116
    def p_module_0(self, info):
117
        " module ::= Id Id version { } "
118
        module, name, version, _0, _1 = info
Jeremy Hylton's avatar
Jeremy Hylton committed
119 120 121
        if module.value != "module":
            raise ASDLSyntaxError(module.lineno,
                                  msg="expected 'module', found %s" % module)
122
        return Module(name, None, version)
Jeremy Hylton's avatar
Jeremy Hylton committed
123

124
    def p_module(self, info):
125
        " module ::= Id Id version { definitions } "
126
        module, name, version, _0, definitions, _1 = info
Jeremy Hylton's avatar
Jeremy Hylton committed
127 128 129
        if module.value != "module":
            raise ASDLSyntaxError(module.lineno,
                                  msg="expected 'module', found %s" % module)
130
        return Module(name, definitions, version)
Tim Peters's avatar
Tim Peters committed
131

132
    def p_version(self, info):
133
        "version ::= Id String"
134
        version, V = info
135 136
        if version.value != "version":
            raise ASDLSyntaxError(version.lineno,
137
                                  msg="expected 'version', found %" % version)
138
        return V
Jeremy Hylton's avatar
Jeremy Hylton committed
139

140
    def p_definition_0(self, definition):
Jeremy Hylton's avatar
Jeremy Hylton committed
141
        " definitions ::= definition "
142
        return definition[0]
Jeremy Hylton's avatar
Jeremy Hylton committed
143

144
    def p_definition_1(self, definitions):
Jeremy Hylton's avatar
Jeremy Hylton committed
145
        " definitions ::= definition definitions "
146
        return definitions[0] + definitions[1]
Jeremy Hylton's avatar
Jeremy Hylton committed
147

148
    def p_definition(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
149
        " definition ::= Id = type "
150
        id, _, type = info
Jeremy Hylton's avatar
Jeremy Hylton committed
151 152
        return [Type(id, type)]

153
    def p_type_0(self, product):
Jeremy Hylton's avatar
Jeremy Hylton committed
154
        " type ::= product "
155
        return product[0]
Jeremy Hylton's avatar
Jeremy Hylton committed
156

157
    def p_type_1(self, sum):
Jeremy Hylton's avatar
Jeremy Hylton committed
158
        " type ::= sum "
159
        return Sum(sum[0])
Jeremy Hylton's avatar
Jeremy Hylton committed
160

161
    def p_type_2(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
162
        " type ::= sum Id ( fields ) "
163
        sum, id, _0, attributes, _1 = info
Jeremy Hylton's avatar
Jeremy Hylton committed
164 165 166
        if id.value != "attributes":
            raise ASDLSyntaxError(id.lineno,
                                  msg="expected attributes, found %s" % id)
167 168
        if attributes:
            attributes.reverse()
Jeremy Hylton's avatar
Jeremy Hylton committed
169 170
        return Sum(sum, attributes)

171
    def p_product(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
172
        " product ::= ( fields ) "
173
        _0, fields, _1 = info
Jeremy Hylton's avatar
Jeremy Hylton committed
174
        # XXX can't I just construct things in the right order?
Tim Peters's avatar
Tim Peters committed
175
        fields.reverse()
Jeremy Hylton's avatar
Jeremy Hylton committed
176 177
        return Product(fields)

178
    def p_sum_0(self, constructor):
179
        " sum ::= constructor "
180
        return [constructor[0]]
Jeremy Hylton's avatar
Jeremy Hylton committed
181

182
    def p_sum_1(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
183
        " sum ::= constructor | sum "
184
        constructor, _, sum = info
Jeremy Hylton's avatar
Jeremy Hylton committed
185 186
        return [constructor] + sum

187
    def p_sum_2(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
188
        " sum ::= constructor | sum "
189
        constructor, _, sum = info
Jeremy Hylton's avatar
Jeremy Hylton committed
190 191
        return [constructor] + sum

192
    def p_constructor_0(self, id):
Jeremy Hylton's avatar
Jeremy Hylton committed
193
        " constructor ::= Id "
194
        return Constructor(id[0])
Jeremy Hylton's avatar
Jeremy Hylton committed
195

196
    def p_constructor_1(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
197
        " constructor ::= Id ( fields ) "
198
        id, _0, fields, _1 = info
Jeremy Hylton's avatar
Jeremy Hylton committed
199
        # XXX can't I just construct things in the right order?
Tim Peters's avatar
Tim Peters committed
200
        fields.reverse()
Jeremy Hylton's avatar
Jeremy Hylton committed
201 202
        return Constructor(id, fields)

203
    def p_fields_0(self, field):
Jeremy Hylton's avatar
Jeremy Hylton committed
204
        " fields ::= field "
205
        return [field[0]]
Jeremy Hylton's avatar
Jeremy Hylton committed
206

207
    def p_fields_1(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
208
        " fields ::= field , fields "
209
        field, _, fields = info
Jeremy Hylton's avatar
Jeremy Hylton committed
210 211
        return fields + [field]

212
    def p_field_0(self, type_):
Jeremy Hylton's avatar
Jeremy Hylton committed
213
        " field ::= Id "
214
        return Field(type_[0])
Jeremy Hylton's avatar
Jeremy Hylton committed
215

216
    def p_field_1(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
217
        " field ::= Id Id "
218
        type, name = info
Jeremy Hylton's avatar
Jeremy Hylton committed
219 220
        return Field(type, name)

221
    def p_field_2(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
222
        " field ::= Id * Id "
223
        type, _, name = info
Benjamin Peterson's avatar
Benjamin Peterson committed
224
        return Field(type, name, seq=True)
Jeremy Hylton's avatar
Jeremy Hylton committed
225

226
    def p_field_3(self, info):
Jeremy Hylton's avatar
Jeremy Hylton committed
227
        " field ::= Id ? Id "
228
        type, _, name = info
Benjamin Peterson's avatar
Benjamin Peterson committed
229
        return Field(type, name, opt=True)
Jeremy Hylton's avatar
Jeremy Hylton committed
230

231
    def p_field_4(self, type_):
Jeremy Hylton's avatar
Jeremy Hylton committed
232
        " field ::= Id * "
Benjamin Peterson's avatar
Benjamin Peterson committed
233
        return Field(type_[0], seq=True)
Jeremy Hylton's avatar
Jeremy Hylton committed
234

235
    def p_field_5(self, type_):
Jeremy Hylton's avatar
Jeremy Hylton committed
236
        " field ::= Id ? "
Benjamin Peterson's avatar
Benjamin Peterson committed
237
        return Field(type[0], opt=True)
Jeremy Hylton's avatar
Jeremy Hylton committed
238 239 240 241 242 243 244

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

Benjamin Peterson's avatar
Benjamin Peterson committed
245
class AST(object):
Jeremy Hylton's avatar
Jeremy Hylton committed
246 247 248
    pass # a marker class

class Module(AST):
249
    def __init__(self, name, dfns, version):
Jeremy Hylton's avatar
Jeremy Hylton committed
250 251
        self.name = name
        self.dfns = dfns
252
        self.version = version
Jeremy Hylton's avatar
Jeremy Hylton committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
        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(%s, %s)" % (self.name, self.dfns)

class Type(AST):
    def __init__(self, name, value):
        self.name = name
        self.value = value

    def __repr__(self):
        return "Type(%s, %s)" % (self.name, self.value)

class Constructor(AST):
    def __init__(self, name, fields=None):
        self.name = name
        self.fields = fields or []

    def __repr__(self):
        return "Constructor(%s, %s)" % (self.name, self.fields)

class Field(AST):
Benjamin Peterson's avatar
Benjamin Peterson committed
277
    def __init__(self, type, name=None, seq=False, opt=False):
Jeremy Hylton's avatar
Jeremy Hylton committed
278 279 280 281 282 283 284
        self.type = type
        self.name = name
        self.seq = seq
        self.opt = opt

    def __repr__(self):
        if self.seq:
Benjamin Peterson's avatar
Benjamin Peterson committed
285
            extra = ", seq=True"
Jeremy Hylton's avatar
Jeremy Hylton committed
286
        elif self.opt:
Benjamin Peterson's avatar
Benjamin Peterson committed
287
            extra = ", opt=True"
Jeremy Hylton's avatar
Jeremy Hylton committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
        else:
            extra = ""
        if self.name is None:
            return "Field(%s%s)" % (self.type, extra)
        else:
            return "Field(%s, %s%s)" % (self.type, self.name, extra)

class Sum(AST):
    def __init__(self, types, attributes=None):
        self.types = types
        self.attributes = attributes or []

    def __repr__(self):
        if self.attributes is None:
            return "Sum(%s)" % self.types
        else:
            return "Sum(%s, %s)" % (self.types, self.attributes)

class Product(AST):
    def __init__(self, fields):
        self.fields = fields

    def __repr__(self):
        return "Product(%s)" % self.fields

class VisitorBase(object):

Benjamin Peterson's avatar
Benjamin Peterson committed
315
    def __init__(self, skip=False):
Jeremy Hylton's avatar
Jeremy Hylton committed
316 317 318 319 320 321 322 323 324
        self.cache = {}
        self.skip = skip

    def visit(self, object, *args):
        meth = self._dispatch(object)
        if meth is None:
            return
        try:
            meth(object, *args)
325
        except Exception:
326 327
            output("Error visiting" + repr(object))
            output(str(sys.exc_info()[1]))
Jeremy Hylton's avatar
Jeremy Hylton committed
328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
            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__
            if self.skip:
                meth = getattr(self, methname, None)
            else:
                meth = getattr(self, methname)
            self.cache[klass] = meth
        return meth

class Check(VisitorBase):

    def __init__(self):
Benjamin Peterson's avatar
Benjamin Peterson committed
350
        super(Check, self).__init__(skip=True)
Jeremy Hylton's avatar
Jeremy Hylton committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
        self.cons = {}
        self.errors = 0
        self.types = {}

    def visitModule(self, mod):
        for dfn in mod.dfns:
            self.visit(dfn)

    def visitType(self, type):
        self.visit(type.value, str(type.name))

    def visitSum(self, sum, name):
        for t in sum.types:
            self.visit(t, name)

    def visitConstructor(self, cons, name):
        key = str(cons.name)
        conflict = self.cons.get(key)
        if conflict is None:
            self.cons[key] = name
        else:
372 373
            output("Redefinition of constructor %s" % key)
            output("Defined in %s and %s" % (conflict, name))
Jeremy Hylton's avatar
Jeremy Hylton committed
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
            self.errors += 1
        for f in cons.fields:
            self.visit(f, key)

    def visitField(self, field, name):
        key = str(field.type)
        l = self.types.setdefault(key, [])
        l.append(name)

    def visitProduct(self, prod, name):
        for f in prod.fields:
            self.visit(f, name)

def check(mod):
    v = Check()
    v.visit(mod)

    for t in v.types:
392
        if t not in mod.types and not t in builtin_types:
Jeremy Hylton's avatar
Jeremy Hylton committed
393 394
            v.errors += 1
            uses = ", ".join(v.types[t])
395
            output("Undefined type %s, used in %s" % (t, uses))
Tim Peters's avatar
Tim Peters committed
396

Jeremy Hylton's avatar
Jeremy Hylton committed
397 398 399 400 401 402 403 404 405 406
    return not v.errors

def parse(file):
    scanner = ASDLScanner()
    parser = ASDLParser()

    buf = open(file).read()
    tokens = scanner.tokenize(buf)
    try:
        return parser.parse(tokens)
407
    except ASDLSyntaxError:
408 409
        err = sys.exc_info()[1]
        output(str(err))
Jeremy Hylton's avatar
Jeremy Hylton committed
410
        lines = buf.split("\n")
411
        output(lines[err.lineno - 1]) # lines starts at 0, files at 1
Jeremy Hylton's avatar
Jeremy Hylton committed
412 413 414 415 416 417 418 419 420 421

if __name__ == "__main__":
    import glob
    import sys

    if len(sys.argv) > 1:
        files = sys.argv[1:]
    else:
        testdir = "tests"
        files = glob.glob(testdir + "/*.asdl")
Tim Peters's avatar
Tim Peters committed
422

Jeremy Hylton's avatar
Jeremy Hylton committed
423
    for file in files:
424
        output(file)
Jeremy Hylton's avatar
Jeremy Hylton committed
425
        mod = parse(file)
426 427
        if not mod:
            break
428 429
        output("module", mod.name)
        output(len(mod.dfns), "definitions")
Jeremy Hylton's avatar
Jeremy Hylton committed
430
        if not check(mod):
431
            output("Check failed")
Jeremy Hylton's avatar
Jeremy Hylton committed
432 433
        else:
            for dfn in mod.dfns:
434
                output(dfn.type)