I like (free)pascal very much. But one of the most anoying things is the limitations due to circular unit references.
Why do we still need this? Inside a unit we can have foward declaration which can be used to
make two classes to reference each other. Why can't this work accross units? The compiler can stop iterating thru the
units when he raches a unit he has already paresed and behave like he does with forward declarations?
I know that i can use base classes but i have to cast the the base classes to the real classes in the implematation section
anyway.
The advantage of this way is that you only import something that is fairly completely defined (except a few things like inline, and changing of procedure attributes in the implementation). You never have to assume what an identifier means (and backtrack if wrong when you parse the other source) of have workarounds or assumptions in the language to disambiguate this. (and think not just of work to compile correct code, but also the effort to get clear and correct error messages)
And I never saw it as a problem to begin with. I like a nice tight declaration without code in it (C++ and Java classes look messy to me, I can live with it, but consider it suboptimal), and ordering the uses clauses quickly becomes a second nature. I only hit the limit when I'm deliberately experimenting during refactoring rarely or never during normal coding.
Anyway, once you start down a road, regardless what other languages do, you are stuck to that model, or totally redesign and rewrite the way multiple sources are combined to one program, which is one of the hardest part of the program.
Exploring what works and what not, would be more a job for a new and small and focussed pascal compiler that can move more agilely than a twenty+ year old behemoth like Free Pascal with a zillion features that might need fixing and rethinking.
Note for allowing interface sections with multiple implementation sections we already have inc files.
The multi-platform support is even built on that.
Personally
- I can live with how it is now.
- I use include files when appropriate.
- If you have many circular references on the same platform your architecture is probably wrong and we can probably set you on a better track when we see some code..
Problem solved.
Personally
- I can live with how it is now.
- I use include files when appropriate.
- If you have many circular references on the same platform your architecture is probably wrong and we can probably set you on a better track when we see some code..
The advantage of this way is that you only import something that is fairly completely defined (except a few things like inline, and changing of procedure attributes in the implementation). You never have to assume what an identifier means (and backtrack if wrong when you parse the other source) of have workarounds or assumptions in the language to disambiguate this. (and think not just of work to compile correct code, but also the effort to get clear and correct error messages)
But it stays completely defined. It's just that two classes from two units can reference each other. Like it can be done with implicit forward declarations inside a unit allready.
To have two classes reference each other you have to put them in one unit which breaks the concept of clean and simple units.
Or you have to user base classes which makes you use casts in the implementation part which breaks the concept of type safety.
Or you use helper classes.
So why not do it in a straightforward and simple way and enable circular unit references?
Yes, but this inconvenience always forces you to find some way around the "circular unit reference problem". So you always have to build some kind of hack!
Shouldn't this be quite easy to implement? The compiler just has to stop parsing a unit that he already knows. So unit A can use classes of unit B and vice versa.
I'm not stopping you to try to come with a patch, but I would first research the consequences first.
Mutual reference is not "clean" by definition, and breaking it is actually clean. Doing it straightforward for simple cases is actually the "hack". Some languages allow it for ease of use, but it is trouble from a language design view.
Anything that goes over unit borders and is not quite defined is usually a nightmare. And soon the bugs for corner cases with this functionality will start to pile up. (somebody will try to do a 3-unit cycle etc) Anybody taking this on better be prepared to monitor it for a long time.
As an example of a corner case, think of things like
type TFoo = class anOhterFoo : TOtherFoo; property bah : integer read anohterfoo.bah; end;
After TOtherfoo is defined in the other unit, you need to make sure that it has a "bah". Moreover you can't generate code till you have the full definition of TOtherFoo. Which can then have a circular reference much larger than 2 units, which already gives me a headache THINKING of it, let alone the implementation.
So pretty everything like it is done with forward declarations now.Well, as Marco implicitly explained this is not the case:
The parser just has to insert an implicit forward declaration when it reaches an undefined type while parsing the interfaces of the used units.
So pretty everything like it is done with forward declarations now.Well, as Marco implicitly explained this is not the case:
The parser just has to insert an implicit forward declaration when it reaches an undefined type while parsing the interfaces of the used units.
Suppose unitA has a type or procedure declared, like say, TPoint. And unitB re-declares TPoint. Now, the parser can only resolve its use based on unit order..
What you propose does not work with forward declarations, probably not even with a multi-pass compiler. At parse time the compiler does not know which scope you mean: A or B.
This is already a common scenario in the existing code base, where clashes already occur and need to be resolved by explicit scoping: unitB.Tpoint, unitA.Tpoint.
Maybe a record is not a proper example but the same goes for classes, procedures and functions.
Pascal was designed with top-down/bottom-up design (https://en.wikipedia.org/wiki/Top-down_and_bottom-up_design) in mind. I like this way of working. Circular reference is a consequence of that design, and I think it is good because it forces you to think better solutions (better because they're more encapsulated and errors will have less propagation. I hope you understand me).Indeed. It is also - often, not always - a warning for bad design: insufficient code separation. Most circular references can be factored out.
My 2 cents.
Yes, but this is another issue. I unfortunately do not see what this has to do with my topic?
Yes, but this is another issue. I unfortunately do not see what this has to do with my topic?
Your title is more generic (all types) than the discussion (reference class types only, later limited also to a subset of options (e.g. not supporting the property case)).
Anyway, while with a lot of limitations some simple cases could be maybe done, it is a ton of work for the cases that are simple to solve in the first place.
If you or someone else could lead me to the relevant places in the compiler/parser sources i will give it a try.Well, there is "advanced documentation" in the sense that it is documented how the compiler itself can be compiled with debug info. So you can debug the compiler under fpc itself. So you can get any information you want following program flow. O:-)
And as asked earlier: Is there an up to date wiki/doku of the inner structure/working of the compiler, which could help me understand the sources?
If you or someone else could lead me to the relevant places in the compiler/parser sources i will give it a try.
And as asked earlier: Is there an up to date wiki/doku of the inner structure/working of the compiler, which could help me understand the sources?