diff options
author | Steven Knight <knight@baldmt.com> | 2003-05-09 20:34:37 (GMT) |
---|---|---|
committer | Steven Knight <knight@baldmt.com> | 2003-05-09 20:34:37 (GMT) |
commit | 3ff79698a1d206f9be1804376004777c6c7f7929 (patch) | |
tree | e34f3b4515444d2effd07c0caea091dc353ae4d0 /src/engine/SCons/Tool/JavaCommon.py | |
parent | d5aff94f2c90a162e2c36775b0fc80cabdc7ad64 (diff) | |
download | SCons-3ff79698a1d206f9be1804376004777c6c7f7929.zip SCons-3ff79698a1d206f9be1804376004777c6c7f7929.tar.gz SCons-3ff79698a1d206f9be1804376004777c6c7f7929.tar.bz2 |
Split the Java parser into a JavaCommon.py module.
Diffstat (limited to 'src/engine/SCons/Tool/JavaCommon.py')
-rw-r--r-- | src/engine/SCons/Tool/JavaCommon.py | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py new file mode 100644 index 0000000..3348bc0 --- /dev/null +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -0,0 +1,223 @@ +"""SCons.Tool.JavaCommon + +Stuff for processing Java. + +""" + +# +# __COPYRIGHT__ +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__" + +import os +import os.path +import re +import string + +java_parsing = 1 + +if java_parsing: + # Parse Java files for class names. + # + # This is a really cool parser from Charles Crain + # that finds appropriate class names in Java source. + + # A regular expression that will find, in a java file, + # any alphanumeric token (keyword, class name, specifier); open or + # close brackets; a single-line comment "//"; the multi-line comment + # begin and end tokens /* and */; single or double quotes; and + # single or double quotes preceeded by a backslash. + _reToken = re.compile(r'(//[^\r\n]*|\\[\'"]|[\'"\{\}]|[A-Za-z_][\w\.]*|' + + r'/\*|\*/)') + + class OuterState: + """The initial state for parsing a Java file for classes, + interfaces, and anonymous inner classes.""" + def __init__(self): + self.listClasses = [] + self.listOutputs = [] + self.stackBrackets = [] + self.brackets = 0 + self.nextAnon = 1 + self.package = None + + def __getClassState(self): + try: + return self.classState + except AttributeError: + ret = ClassState(self) + self.classState = ret + return ret + + def __getPackageState(self): + try: + return self.packageState + except AttributeError: + ret = PackageState(self) + self.packageState = ret + return ret + + def __getAnonClassState(self): + try: + return self.anonState + except AttributeError: + ret = SkipState(1, AnonClassState(self)) + self.anonState = ret + return ret + + def __getSkipState(self): + try: + return self.skipState + except AttributeError: + ret = SkipState(1, self) + self.skipState = ret + return ret + + def parseToken(self, token): + if token[:2] == '//': + pass # ignore comment + elif token == '/*': + return IgnoreState('*/', self) + elif token == '{': + self.brackets = self.brackets + 1 + elif token == '}': + self.brackets = self.brackets - 1 + if len(self.stackBrackets) and \ + self.brackets == self.stackBrackets[-1]: + self.listOutputs.append(string.join(self.listClasses, '$')) + self.listClasses.pop() + self.stackBrackets.pop() + elif token == '"' or token == "'": + return IgnoreState(token, self) + elif token == "new": + # anonymous inner class + if len(self.listClasses) > 0: + return self.__getAnonClassState() + return self.__getSkipState() # Skip the class name + elif token == 'class' or token == 'interface': + if len(self.listClasses) == 0: + self.nextAnon = 1 + self.stackBrackets.append(self.brackets) + return self.__getClassState() + elif token == 'package': + return self.__getPackageState() + return self + + def addAnonClass(self): + """Add an anonymous inner class""" + clazz = self.listClasses[0] + self.listOutputs.append('%s$%d' % (clazz, self.nextAnon)) + self.brackets = self.brackets + 1 + self.nextAnon = self.nextAnon + 1 + + def setPackage(self, package): + self.package = package + + class AnonClassState: + """A state that looks for anonymous inner classes.""" + def __init__(self, outer_state): + # outer_state is always an instance of OuterState + self.outer_state = outer_state + self.tokens_to_find = 2 + def parseToken(self, token): + # This is an anonymous class if and only if the next token is a bracket + if token == '{': + self.outer_state.addAnonClass() + return self.outer_state + + class SkipState: + """A state that will skip a specified number of tokens before + reverting to the previous state.""" + def __init__(self, tokens_to_skip, old_state): + self.tokens_to_skip = tokens_to_skip + self.old_state = old_state + def parseToken(self, token): + self.tokens_to_skip = self.tokens_to_skip - 1 + if self.tokens_to_skip < 1: + return self.old_state + return self + + class ClassState: + """A state we go into when we hit a class or interface keyword.""" + def __init__(self, outer_state): + # outer_state is always an instance of OuterState + self.outer_state = outer_state + def parseToken(self, token): + # the only token we get should be the name of the class. + self.outer_state.listClasses.append(token) + return self.outer_state + + class IgnoreState: + """A state that will ignore all tokens until it gets to a + specified token.""" + def __init__(self, ignore_until, old_state): + self.ignore_until = ignore_until + self.old_state = old_state + def parseToken(self, token): + if self.ignore_until == token: + return self.old_state + return self + + class PackageState: + """The state we enter when we encounter the package keyword. + We assume the next token will be the package name.""" + def __init__(self, outer_state): + # outer_state is always an instance of OuterState + self.outer_state = outer_state + def parseToken(self, token): + self.outer_state.setPackage(token) + return self.outer_state + + def parse_java_file(fn): + return parse_java(open(fn, 'r').read()) + + def parse_java(contents): + """Parse a .java file and return a double of package directory, + plus a list of .class files that compiling that .java file will + produce""" + package = None + initial = OuterState() + currstate = initial + for token in _reToken.findall(contents): + # The regex produces a bunch of groups, but only one will + # have anything in it. + currstate = currstate.parseToken(token) + if initial.package: + package = string.replace(initial.package, '.', os.sep) + return (package, initial.listOutputs) + +else: + # Don't actually parse Java files for class names. + # + # We might make this a configurable option in the future if + # Java-file parsing takes too long (although it shouldn't relative + # to how long the Java compiler itself seems to take...). + + def parse_java_file(fn): + """ "Parse" a .java file. + + This actually just splits the file name, so the assumption here + is that the file name matches the public class name, and that + the path to the file is the same as the package name. + """ + return os.path.split(file) |