2

I am writing a recursive descent parser.

Let ParseContext specifies the context for parsing. Pseudocode:

class ParseContext {
    Logger logger; // logger for error/warning messages
    Locale locale; // locale for error/warning messages
}

Classes derived from ParseContext may specify additional parse options, such as the version of the grammar or whether warnings should be considered as errors.

Now define Parser.

1.

class Parser {
    Parser(ParseContext context) {
        this.context = context;
    }
    abstract function parse(String text) return Object;
}

or

2.

class Parser {
    Parser() {
    }
    abstract function parse(ParseContext context, String text) return Object;
}

Which of the above Parser classes is a know pattern and which is a know antipattern? Or maybe, neither is in the set of known antipatterns? The first seems to be an instance of dependency injection pattern. Does it imply that the other is an antipattern?

It seems that it is unusual to call .parse with several different context arguments. Does it imply that it makes no sense to refactor "2" to be a constructor option instead (as in "1")?

What are (dis)advantages of the either?

  • You want to make a logical decision on setter injection or constructor injection because both are valid. I prefer Ctor injection due to instantiation requirements where the IOC container will fail immediately when resolving dependencies. – StackOverFowl Aug 5 '18 at 17:03
  • @Programmer The choice is not between constructor injection and setter injection, but between constructor injection and no injection at all (but passing it as a non-setter method argument) – porton Aug 5 '18 at 17:53
  • 1
    Sometimes you want both - passing a global or root context into the constructor and an optional sub-context to the query/parse method. – HorusKol Aug 5 '18 at 23:03
3

Neither is an anti-pattern, the first is the better choice though. It is the parser class as a whole (potentially all of its methods) that depends on the context. The example shows a class with only one method but you could have several and it would be noisy and unnecessary to pass the context to each.

2

A recursive descent parser is characterized by recursive functions. There's no need to involve any classes (except of course in languages like Java where everything has to be placed into a class).

However, you will often pass some context through all these functions. This is usually an explicit function argument (your solution 2). There's nothing wrong with this, but it quickly gets tedious if there are many arguments.

Alternatively, you can turn the recursive functions into recursive methods. Now the invocant object (“this”) can be used to store context that is needed in all parsing methods (your solution 1, except that Parser and ParseContext are the same). As a consequence, this context should be provided in the parsing object constructor (though I usually keep that constructor private).

This is indicated when you have some context that is really needed by all or nearly all parsing functions. There are some scenarios that can complicate this.

  • If some context is scoped to the duration of one parsing function, you might be tempted to change the context state of the current object. The simpler solution is to create a new parsing object for the inner scope.

    An example of scoped data could be a symbol table for local variables in a programming language parser, or locale information when parsing a tree-structured document (such as HTML), or indentation information in a layout-sensitive language – although layout information might be best passed as a function argument, not as parser object state.

  • Quite often the language being parsed is composed of multiple sub-languages, e.g. a statement-grammar and an expression-grammar in a programming language, or a DTD parser as part of an XML parser. These sub-languages may require different kinds of context.

    In simple cases additional context can simply be provided as extra method parameters. If the required sub-context is different and not just a superset, consider creating multiple parser classes that cooperate. You would then create a sub-parser object for parsing a sub-language.

0

Contrary to the above answers, there is a reason to pass context as .parse() method argument rather than through the constructor.

Consider the following Python code (from a real open source project):

class ZeroOrMorePredicate(PredicateParser):
    def __init__(self, predicate, child):
        super().__init__(predicate)
        self.child = child

    def parse(self, parse_context, graph, node):
        iter = graph.objects(node, self.predicate)
        return [self.child.parse(parse_context, graph, elt) for elt in iter]

This parser parses a set of RDF predicates related with a given node (no need to know what are predicates and nodes to understand the idea) and returns the results of parsing predicates as a list.

I remind that def defines methods in Python, particularly def __init__ is a constructor.

If I refactor to pass context through constructors, the users of this class would need to pass the context twice: in the constructor of ZeroOrMorePredicate and in the constructor of the child sub-parser.

This way the refactoring suggested in two other answers a little complicates the life of the users of the class. So, I may probably decide to ignore other two answers and don't refactor as the advise.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.