In essence your project was good. There are a few remarks to be made:
* There are more theories (religions?) about how maintainable programs
should be made.
Right, paradigms are somewhat religious. But as with every religion, I only believe what matches my own experience.
* The Pascal language implemented by FPC is a mixed procedural/OOP
language. Procedural programming has its strengths and FPC makes use of
the potential to mix procedural and OOP.
Experience tells me that projects have reached a dead end, when they contain enough unmanageable parts which nobody can explain, and which nobody dares to touch any more. Such parts typically reside in procedural code, rarely in OOP code.
Show quoted text 5 lines
* Global variables cause problems with parallelization, but that does
not mean the choice for a global variable is a bad idea. Variables like
current_module is used so extensively that they are prime candidates to
be global. In my opinion you have been following the OOP bible a bit too
strict here.
In the concrete case of FPC, the bad use of global variables (for context sensitive purposes) has nothing to do with religion. Current_module is one of the exceptions, which I consider a state variable, eligible for a threadvar. Further variables, which have to be changed together with current_module, are only used for optimizations, and can be replaced by tmodule members.
My (very raw) measurements didn't indicate any noticeable speed penalty, when further variables (even current_scanner) are replaced by indirect references to members of tmodule. Like with source files, I assume that nowadays existing caches (in both hard- and software) invalidate old rules, that the use of shadow and buffer variables can speed up execution a lot. This can be different for other (non-x86) machines, without such sophisticated caching, where the execution time still is predictable from a static analysis of the code (by counting fixed clock cycles per instruction).
* OOP was your goal. Your goal should have been parallelization, OOP
should have been your tool. There are more tools, like threadvars.
Threadvars have the same problematic properties like global variables: the must be initialized and switched properly. Every such variable affects the complexity of an application, where it adds a very new dimension, not only a percentage. My experience and design principles are based on automatons and states, so that OOP is not a must, but fits nicely into such a model.
It has been said meny times in this discussion: In a compiler there are
dependencies between a lot of datastrcutures. Handling this effectively
is a challenge.
Yes and no. The number and polymorphism of data structures is not a problem per se. But...
Show quoted text 6 lines
FPC's approach in many places is to put abstract
interdependent objects in a single unit, so these objects can use each
other wherever they want. We then add functionality in other units that
derive from the abstract units. Tsym, Tdef, Tsymtable are in a single
unit. All kinds of symbols, definitions, and symbtables have their own
unit.
...the strictly hierarchical OPL unit dependency system requires much coding discipline, and also requires hacks for certain constellations <sigh>.
Show quoted text 5 lines
I consider this design a strong one, the problem of cyclic dependencies
in the compiler is well controllable. It helps maintenance: I know many
C++ projects where all .cpp files over time started to include all .h
files. In FPC, if you notice you can't access current_module from the
current unit, you know you are doing something wrong.
ACK.
You seemed to have trouble with this model, rather than embrace it and
use it to your advantage, you started to break things apart and change
the dependencies. As expected this caused you a dependency nightmare,
Where do you see a dependency nightmare?
introducing what you tried to eliminate with the better design: You
wanted clear interfaces between certain parts of the compiler, but you
did end up breaking interfaces external tools can use, and fixing them
became beyond any hope.
Which interfaces?
When there ever existed something like such an interface, it has been broken since long, as can be seen on the comments and copies in ppudump.
The globals unit has its role. Many symbols are there for a good reason.
Please specify.
When the variables in the globals unit are initialized in other units, which are not included by an external tool, they only can contain garbage.
There can be good reason for moving symbols out of it, if you do so,
first try to understand why that symbol is there, and avoid changes in
unit dependencies at all costs.
You mean something like copying declarations and procedures into external tools, to bypass the inclusion of unwanted units?
Show quoted text 11 lines
Above all: You can get far with theory. FPC has a practical and
pragmatic code base but I dare to say it has a theoretically sound base
design. New
theory can be a solution for practical problems but implementation of it
requires of new practical compromises to be made. For performance,
memory use or historic reasons, or maybe even because the theory can't
catch 100% of the possible situations (there are even some goto
statements in the compiler).
Nevertheless, you have gotten a lot of knowledge of the compiler, and
this project makes me optimistic you can become a good compiler developer.
:-)
DoDi