The major goal of the architecture is flexibility, to enable the development of new features and to enable the user to customize the parsers to his needs. The main devices to reach this goal are:

  • Separation of concerns
  • Programming against interfaces
  • Dependency injection
  • Classes as identifiers

Separtion of Concerns

The classes are rather small to encapsulate a single concern.

The syntax tracker is the most complex example. It focuses on the parsing algorithm, while it delegates the representation of tokens and execptions to dedicated classes. The collecting of tokens and exeptions is done by tracker classes. The tracker objects are finally accessed by a formatter class to produce the highlighted output.

Concerns represented by one class each:

  • Parsing
  • Representation of a token
  • Representation of an exception
  • Tracking tokens
  • Tracking exceptions
  • Formatting the report

Programming against Interfaces

Whereever two classes cooperate, there is an interface between them. A class can have multiple interfaces, if it cooperates with multiple other classes. All this interfaces are defined as PHP interfaces, that are stored into the folder Classes/Interfaces.

A class should not depend on other classes to cooperate, but on interfaces. It is free to cooperate with every class that implements the matching interface. Each class can be exchanged by a customized class, as long as the customized class provides the interfaces, that the given classes can talk to.

An example usage of this interfaces are the mock objects of the unit tests. While testing a single class it is decoupled from other classes, by using mock objects, that implement the interface to test against.

Dependency Injection

Dependency injection is related to programming against interfaces. If a class must not depend on other classes, it must not create classes by the keyword new itself. Instead objects, that implement the required interface, are injected.

For sure a place is needed where all this dependency injection is done, where the objects are created and wired up. This is done in the main application classes that are stored in the folder Main/. You can think of an application class as a kind of configuration, that composes objects according to your taste. You write a new one of this main configuration classes, to compose your own application or to alter an existing one.

Classes as Identifieres

An exception from the rule, to not use the keyword new, are the tokens and exceptions. Each class is designed to serve as an identifier. You can think of them as constants. The object is created by the keyword new as you mean exactly it’s class as identifier, not the interface. They are final.

Nonetheless there is flexibilty. The exceptions and tokens are created by parsers and you can exchange the parser creating them. That means you can exchange the part, that contains the new keywords.

You can create your own exceptions and tokens by writing new classes. It’s just a few lines each, because they inherit almoust all from abstract classes. The freedom to easily add new tokens and exceptions is one reason, why they are not implemented as constants, apart from the additional functionality a class offers.