3
votes

I'm having trouble implementing an inner private enum class called: "LineCode" inside a class named Parser.

LineCode: Private Enum class that defines 6 types of general possible line of codes. I use Enum instantiation to send a Regex Pattern and compile it in the constructor, __init__, and then holds the Regex Matcher as a class variable.

Parser: Parses a programming language, irrelevant what language. Parser is using LineCode to identify the lines and proceed accordingly.

Problem: I can't access the enum members of __LineCode from a Static method. I wish to have a Static method inside __LineCode, "matchLineCode(line)" that receives a string from the Parser, it then iterates over the Enum members in the following logic:

  • If Match is found: Return the enum
  • If no more enums left: Return None

It doesn't seem trivial, I can't access the enum members to do this.

Attempts: I tried iterating over the enums using:

  1. __LineCode.__members__.values()
  2. Parser.__lineCode.__members__.values()

Both failed since it can't find __lineCode.

Ideally: LineCode class must be private, and not be visible to any other class importing the Parser. Parser must use the static method that LineCode class provides to return the Enum. I am willing to accept any solution that solves this issue or one that mimics this behavior.

I omitted some of the irrelevant Parser methods to improve readability. Code:

class Parser:
    class __LineCode(Enum):
        STATEMENT = ("^\s*(.*);\s*$")
        CODE_BLOCK = ("^\s*(.*)\s*\{\s*$")
        CODE_BLOCK_END = ("^\s*(.*)\s*\}\s*$")
        COMMENT_LINE = ("^\s*//\s*(.*)$")
        COMMENT_BLOCK = ("^\s*(?:/\*\*)\s*(.*)\s*$")
        COMMENT_BLOCK_END = ("^\s*(.*)\s*(?:\*/)\s*$")
        BLANK_LINE = ("^\s*$")

        def __init__(self, pattern):
            self.__matcher = re.compile(pattern)

        @property
        def matches(self, line):
            return self.__matcher.match(line)

        @property
        def lastMatch(self):
            try:
                return self.__matcher.groups(1)
            except:
                return None

        @staticmethod
        def matchLineCode(line):
            for lineType in **???**:
                if lineType.matches(line):
                    return lineType
            return None

    def __init__(self, source=None):
        self.__hasNext = False
        self.__instream = None
        if source:
            self.__instream = open(source)

    def advance(self):
        self.__hasNext = False
        while not self.__hasNext:
            line = self.__instream.readline()
            if line == "":  # If EOF
                self.__closeFile()
                return
            lineCode = self.__LineCode.matchLineCode(line)
            if lineCode is self.__LineCode.STATEMENT:
                pass
            elif lineCode is self.__LineCode.CODE_BLOCK:
                pass
            elif lineCode is self.__LineCode.CODE_BLOCK_END:
                pass
            elif lineCode is self.__LineCode.COMMENT_LINE:
                pass
            elif lineCode is self.__LineCode.COMMENT_BLOCK:
                pass
            elif lineCode is self.__LineCode.COMMENT_BLOCK:
                pass
            elif lineCode is self.__LineCode.BLANK_LINE:
                pass
            else:
                pass  # TODO Invalid file.

I already implemented it in Java, I want to reconstruct the same thing in Python:

private enum LineCode {
        STATEMENT("^(.*)" + Syntax.EOL + "\\s*$"), // statement line
        CODE_BLOCK("^(.*)" + Syntax.CODE_BLOCK + "\\s*$"), // code block open line
        CODE_BLOCK_END("^\\s*" + Syntax.CODE_BLOCK_END + "\\s*$"), // code block close line
        COMMENT_LINE("^\\s*" + Syntax.COMMENT + "(.*+)$"), // comment line
        BLANK_LINE("\\s*+$"); // blank line

        private final static int CONTENT_GROUP = 1;

        private Pattern pattern;
        private Matcher matcher;

        private LineCode(String regex) {
            pattern = Pattern.compile(regex);
        }

        boolean matches(String line) {
            matcher = pattern.matcher(line);
            return matcher.matches();
        }

        String lastMatch() {
            try {
                return matcher.group(CONTENT_GROUP);
            } catch (IndexOutOfBoundsException e) {
                return matcher.group();
            }
        }
    static LineCode matchLineCode(String line) throws    UnparsableLineException {
        for (LineCode lineType : LineCode.values())
            if (lineType.matches(line)) return lineType;
        throw new UnparsableLineException(line);
    }

Thanks.

1

1 Answers

5
votes

You could change the staticmethod to a classmethod, that way the first argument passed to matchLineCode would be the __lineCode class and you would be able to iterate over it


Edit

I've decided to add a more detailed explanation as to why the matchLineCode using the @staticmethod decorator was unable to see the __lineCode class. First I recommend you read some questions posted on SO that talk about the difference between static and class methods. The main difference is that the classmethod is aware of the Class where the method is defined, while the staticmethod is not. This does not mean that you are unable to see the __lineCode class from the staticmethod, it just means that you will have to do some more work to do so.

The way in which you organized your code, the class __lineCode is a class attribute of class Parser. In python, methods are always public, there are no private or protected class members as in Java. However, the double underscore at the beginning of a class attribute's name (or an instance's attribute's name) mean that the name will be mangled with the class name. This means that any function defined outside of the class Parser could access the __lineCode class as

Parser._Parser__lineCode

This means that using the @staticmethod decorator you could iterate over the __lineCode by doing

@staticmethod
def matchLineCode(line):
    for lineType in Parser._Parser__lineCode:
        if lineType.matches(line):
            return lineType
    return None

However, it is much more readable and, in my opinion, understandable to use the @classmethod decorator to allow the function to be aware of the __lineCode class.