diff --git a/mps/tool/mpsclasses.py b/mps/tool/mpsclasses.py new file mode 100755 index 00000000000..5710e4e4bf8 --- /dev/null +++ b/mps/tool/mpsclasses.py @@ -0,0 +1,297 @@ +#!/usr/bin/python + +# mpsclasses.py: show the MPS class hierarchy +# $Id$ +# Copyright (c) 2007 Ravenbrook Limited. See end of file for license. + +# This file parses MPS C source for MPS class definitions +# (DEFINE_CLASS() et al), and prints out the class hierarchy. +# +# USAGE: +# +# Invoke it with the list of MPS source files, like this: +# cd code +# ../tool/mpsclasses.py *.c +# +# Example output line (to stdout): +# : : : : : AWLPoolClass (POOL) <<< AbstractCollectPoolClass +++ Pool:Format +# +# This means: +# the MPS class "AWLPoolClass" is defined +# using the DEFINE_POOL_CLASS macro (not DEFINE_CLASS) +# its parent is AbstractCollectPoolClass +# it uses the PoolClassMixInFormat mixin. + + +# VERSIONS +# -- first checkin -- +# Version 06, Version 07 +# tidied and commented +# +# Version 05 +# sorted: shallowest first, then alphabetically +# +# Version 04 +# shows forest of classes, one class per line + +import re +import fileinput + +# examples: +""" + DEFINE_CLASS(RankBufClass, class) + { + INHERIT_CLASS(class, SegBufClass); + ... + } + + DEFINE_POOL_CLASS(AWLPoolClass, this) + { + INHERIT_CLASS(this, AbstractCollectPoolClass); + PoolClassMixInFormat(this); + ... + } +""" + +# Grammar: +# C -> D I? M? E +# + + +class GramTerm(object): + """a GramTerm holds information about a terminal in the grammar""" + def __init__(self, name, patt, show): + super(GramTerm, self).__init__() + self.name = name + self.patt = patt + self.show = show + + +# ___D___ -- DEFINE_(_)CLASS ( name +# +patt_D = re.compile( r""" + DEFINE_ + (?P [A-Z]*) [_]? # family + CLASS + \s* [(] + \s* (?P [A-Za-z_][A-Za-z0-9_]* ) # name +""", re.VERBOSE) + +def show_D(match): + print ("%s (%s)" % (match.group("name", "family"))) + +term_D = GramTerm("D", patt_D, show_D) + + +# ___I___ -- INHERIT_CLASS ( this , parentname +# +patt_I = re.compile( r""" + INHERIT_CLASS + \s* [(] + \s* ( [A-Za-z_][A-Za-z0-9_]* ) # this + \s* [,] + \s* (?P [A-Za-z_][A-Za-z0-9_]* ) # parentname +""", re.VERBOSE) + +def show_I(match): + print " <<< %s" % (match.group("parentname")) + +term_I = GramTerm("I", patt_I, show_I) + + +# ___M___ -- ClassMixIn ( this ) ; +# +patt_M = re.compile( r""" + (?P [A-Za-z_][A-Za-z0-9_]*) # family + ClassMixIn + (?P [A-Za-z0-9_]*) # mixin + \s* [(] + \s* ( [A-Za-z_][A-Za-z0-9_]* ) # this + \s* [)] + \s* [;] +""", re.VERBOSE) + +def show_M(match): + print " +++ %s [%s]" % (match.group("mixin", "family")) + +term_M = GramTerm("M", patt_M, show_M) + + +# ___E___ -- } +# +patt_E = re.compile( r""" + [}] +""", re.VERBOSE) + +def show_E(match): + print "..." + +term_E = GramTerm("E", patt_E, show_E) + + +class NoMoreInput(Exception): + """NoMoreInput""" + +class MPSClass(object): + """represents an MPS Class, as created by DEFINE_CLASS() et al""" + def __init__(self, match_D): + """init from match_D""" + super(MPSClass, self).__init__() + self.name, self.family = match_D.group("name", "family") + self.parentname = None + self.mixinnames = [] + self.descendants = 0 + + def add_I(self, match_I): + """add_I: parentname to inherit from""" + assert(self.parentname == None) + self.parentname = match_I.group("parentname") + + def add_M(self, match_M): + """add_M: mixinname""" + self.mixinnames.append("%s:%s" % match_M.group("family", "mixin")) + + def show(self, prefix): + mix = "" + sep = " +++ " + for m in self.mixinnames: + mix = mix + sep + m + sep = ", " + print ("%s%s (%s) <<< %s%s" + % (prefix, self.name, self.family, self.parentname, mix)) + +def main(): + lines = fileinput.input() + tops = [] + CChildListfromName = {} + # (a cmp() function works even with very old Pythons) + ChildrenSort = lambda C1, C2: cmp( + [C1.descendants, C1.name.lower()], + [C2.descendants, C2.name.lower()]) + + def calc_descendants_tree(CT): + for child in CChildListfromName[CT.name]: + CT.descendants += calc_descendants_tree(child) + return CT.descendants + 1 + + def show_tree(CT, prefix): + CT.show(prefix) + CChildListfromName[CT.name].sort(ChildrenSort) + for child in CChildListfromName[CT.name]: + show_tree(child, prefix + ": ") + + try: + while True: + C = next_C(lines) + + # with C + #C.show() + + if C.name not in CChildListfromName: + CChildListfromName[C.name] = [] + if C.parentname == None: + tops.append(C) + else: + if C.parentname not in CChildListfromName: + CChildListfromName[C.parentname] = [] + CChildListfromName[C.parentname].append(C) + + C = None + + except NoMoreInput: + assert(C == None) + + tot = 0 + for CT in tops: + tot += calc_descendants_tree(CT) + + tops.sort(ChildrenSort) + for CT in tops: + show_tree(CT, "") + print + print "%d classes in total" % tot + +def next_C(lines): + """find the next class definition C""" + + # .DIME -- find next DEFINE_CLASS + for l in lines: + match_D = term_D.patt.search(l) + if match_D != None: + break + for t in (term_I, term_M): + match = t.patt.search(l) + if match != None: + print "ERROR parsing .DIME: got I or M: ", l + raise + else: + raise NoMoreInput() + # with match_D + #term_D.show(match_D) + C = MPSClass(match_D) + + # D.IME -- find next InheritFrom, Mixin, or End + for l in lines: + for t in (term_D, term_I, term_M, term_E): + match = t.patt.search(l) + if match != None: + break + else: + continue + # with t + #t.show(match) + if t == term_D: + print "ERROR parsing D.IME: got (another) D; expected I, M, or E" + raise + elif t == term_I: + C.add_I(match) + elif t == term_M: + C.add_M(match) + elif t == term_E: + return C + else: + raise NoMoreInput() + +if __name__ == "__main__": + main() + + +# C. COPYRIGHT AND LICENSE +# +# Copyright (C) 2007 Ravenbrook Limited . +# All rights reserved. This is an open source license. Contact +# Ravenbrook for commercial licensing options. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# 3. Redistributions in any form must be accompanied by information on how +# to obtain complete source code for this software and any accompanying +# software that uses this software. The source code must either be +# included in the distribution or be available for no more than the cost +# of distribution plus a nominal fee, and must be freely redistributable +# under reasonable conditions. For an executable file, complete source +# code means the source code for all modules it contains. It does not +# include source code for modules or files that typically accompany the +# major components of the operating system on which the executable file +# runs. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +# PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.