Why version() ?
Denis Koroskin
2korden at gmail.com
Tue Feb 10 15:21:21 PST 2009
On Wed, 11 Feb 2009 01:33:12 +0300, Walter Bright <newshound1 at digitalmars.com> wrote:
> Steven Schveighoffer wrote:
>> "Walter Bright" wrote
>>> 2. version (A || B) can be done as:
>>>
>>> version (A) version=AorB;
>>> version (B) version=AorB;
>> And it would be much nicer NOT to have to do this.
>
> If you find yourself doing that a lot in the code, then I suggest a
> better solution is to rethink exactly which versions are being produced,
> and come up with a set of version identifiers, one for each actual
> version.
>
>> I think your original position of "let's not get into #if hell" has
>> absolutely (and I mean *absolutely*) no merit as an argument against
>> this one change.
>
> The defense submits as Exhibit A just one snippet from Hans Boehm gc's
> os_dep.c (note all it uses in #if expressions is ||):
>
> ===============================
> # if defined(NEED_FIND_LIMIT) || defined(UNIX_LIKE)
>
> # ifdef __STDC__
> typedef void (*handler)(int);
> # else
> typedef void (*handler)();
> # endif
>
> # if defined(SUNOS5SIGS) || defined(IRIX5) || defined(OSF1) \
> || defined(HURD) || defined(NETBSD)
> static struct sigaction old_segv_act;
> # if defined(IRIX5) || defined(HPUX) \
> || defined(HURD) || defined(NETBSD)
> static struct sigaction old_bus_act;
> # endif
> # else
> static handler old_segv_handler, old_bus_handler;
> # endif
>
> # ifdef __STDC__
> void GC_set_and_save_fault_handler(handler h)
> # else
> void GC_set_and_save_fault_handler(h)
> handler h;
> # endif
> {
> # if defined(SUNOS5SIGS) || defined(IRIX5) \
> || defined(OSF1) || defined(HURD) || defined(NETBSD)
> struct sigaction act;
>
> act.sa_handler = h;
> # if 0 /* Was necessary for Solaris 2.3 and very temporary
> */
> /* NetBSD bugs.
> */
> act.sa_flags = SA_RESTART | SA_NODEFER;
> # else
> act.sa_flags = SA_RESTART;
> # endif
>
> (void) sigemptyset(&act.sa_mask);
> # ifdef GC_IRIX_THREADS
> /* Older versions have a bug related to retrieving and
> */
> /* and setting a handler at the same time.
> */
> (void) sigaction(SIGSEGV, 0, &old_segv_act);
> (void) sigaction(SIGSEGV, &act, 0);
> (void) sigaction(SIGBUS, 0, &old_bus_act);
> (void) sigaction(SIGBUS, &act, 0);
> # else
> (void) sigaction(SIGSEGV, &act, &old_segv_act);
> # if defined(IRIX5) \
> || defined(HPUX) || defined(HURD) || defined(NETBSD)
> /* Under Irix 5.x or HP/UX, we may get SIGBUS.
> */
> /* Pthreads doesn't exist under Irix 5.x, so we
> */
> /* don't have to worry in the threads case.
> */
> (void) sigaction(SIGBUS, &act, &old_bus_act);
> # endif
> # endif /* GC_IRIX_THREADS */
> # else
> old_segv_handler = signal(SIGSEGV, h);
> # ifdef SIGBUS
> old_bus_handler = signal(SIGBUS, h);
> # endif
> # endif
> }
> # endif /* NEED_FIND_LIMIT || UNIX_LIKE */
> ===============================
>
Ok, I've imagined that Boehm GC is written in D and went into a trouble of rewriting the #if/else/endif stuf into versioning blocks:
version(NEED_FIND_LIMIT) version = NEED_FIND_LIMIT_or_UNIT_LIKE;
version(UNIX_LIKE) version = NEED_FIND_LIMIT_or_UNIT_LIKE;
version(NEED_FIND_LIMIT_or_UNIT_LIKE) {
version (__STDC__) {
typedef void (*handler)(int);
} else {
typedef void (*handler)();
}
version (SUNOS5SIGS) version = SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD;
version (IRIX5) version = SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD;
version (OSF1) version = SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD;
version (HURD) version = SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD;
version (NETBSD) version = SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD;
version (IRIX5) version = IRIX5_or_HPUX_or_HURD_or_NETBSD;
version (HPUX) version = IRIX5_or_HPUX_or_HURD_or_NETBSD;
version (HURD) version = IRIX5_or_HPUX_or_HURD_or_NETBSD;
version (NETBSD) version = IRIX5_or_HPUX_or_HURD_or_NETBSD;
version (SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD) {
static struct sigaction old_segv_act;
version (IRIX5_or_HPUX_or_HURD_or_NETBSD) {
static struct sigaction old_bus_act;
}
} else {
static handler old_segv_handler, old_bus_handler;
}
version (__STDC__) {
void GC_set_and_save_fault_handler(handler h)
} else {
void GC_set_and_save_fault_handler(h)
handler h;
}
{
version(SUNOS5SIGS_or_IRIX5_or_OSF1_or_HURD_or_NETBSD) {
struct sigaction act;
act.sa_handler = h;
version(false) { /* Was necessary for Solaris 2.3 and very temporary */
/* NetBSD bugs. */
act.sa_flags = SA_RESTART | SA_NODEFER;
} else {
act.sa_flags = SA_RESTART;
}
(void) sigemptyset(&act.sa_mask);
version(GC_IRIX_THREADS) {
/* Older versions have a bug related to retrieving and */
/* and setting a handler at the same time. */
(void) sigaction(SIGSEGV, 0, &old_segv_act);
(void) sigaction(SIGSEGV, &act, 0);
(void) sigaction(SIGBUS, 0, &old_bus_act);
(void) sigaction(SIGBUS, &act, 0);
} else {
(void) sigaction(SIGSEGV, &act, &old_segv_act);
version (IRIX5_or_HPUX_or_HURD_or_NETBSD) {
/* Under Irix 5.x or HP/UX, we may get SIGBUS. */
/* Pthreads doesn't exist under Irix 5.x, so we */
/* don't have to worry in the threads case. */
(void) sigaction(SIGBUS, &act, &old_bus_act);
}
}
} else {
old_segv_handler = signal(SIGSEGV, h);
version (SIGBUS) {
old_bus_handler = signal(SIGBUS, h);
}
}
}
}
Does it look any better? No way! Besides, C/C++/C# style directives so much more powerful that I'd happily exchange version them just to avoid code duplication hell in my source code files.
> The rest of that file is ALL like that. That one little innocuous change
> opens the door to hell <g>.
>
> (Disclaimer: I do not mean to throw tomatoes specifically at Hans here,
> I know Hans personally and I am in awe of his programming knowledge
> skill. It's just that many developers have worked on that gc, each one
> layering on another gob of conditional compilation. This result is
> typical of C code that's been maintained for years, and you'll find a
> similar mess in my own code. I didn't want to use my own code as the
> example of hell because that is too easily dismissed as a personal
> failing of mine, and that professionals wouldn't do that. But they do. C
> lends itself to and encourages this kind of programming.)
>
>> Think of code that is versioned around architecture that would look
>> horrendous if you have to do version statements that have all different
>> combinations of stuff. If I have 5 different architectures to support,
>> I don't want to have to define a module that has 2^5 different version
>> combinations.
>
> Is your code really intended to actually build 32 different versions?
> May I suggest instead building the 5 architecture versions, and using
> runtime switches for the variants? Or perhaps abstracting the particular
> features out into separate modules?
>
>
>> When I add another architecture, *gasp* I have to double the statements
>> (to do them now with and without version(F) ), and now I have to do
>> another 2^5 statements for the version(F) block. Wheee!
>
> You're right, that is untenable. What I suggest instead is creating a
> module:
>
> module PersonalityForF;
>
> with all the oddities for F exported as constants, aliases, types and
> functions. This makes it easier for the maintenance developer who needs
> to do G, he knows he's just got to write a PersonalityForG module rather
> than edit the source code for dozens of other modules that have embedded
> version stuff. It also eliminates the risk of breakage of those other
> modules for the other platforms. (When I do a port of my projects to new
> platform F, I often inadvertently break A and C because the code for A,
> C and F are all mixed up together.)
>
> I'm currently working on the Mac port, and am encountering exactly these
> kinds of problems. One bright spot is I abstracted all the platform
> specific library reading/writing code out into a separate file rather
> than using any conditional compilation. This has worked out extremely
> well. No "os_dep.c" disaster file.
>
>
The story is not about different functionality on different platforms but rather about a common code which is 98% the same on all the platforms and is different in *small* details. For example, I'd like to make my library D1 and D2 compatible. Do you suggest me to maintain 2 different libraries? This is ridiculous, and that's why there is no Tango2 release yet - there is *no* point in supporting such a large library as Tango (or DWT) for two language versions without a sane versioning mechanism.
>
>>> 5. Why can't one 'version out' syntax that is not recognized by the
>>> compiler?
>>>
>>> The problem is that supporting this requires semantic analysis in
>>> order to successfully lex and parse the source code. Breaking this
>>> will make the lexing and parsing an order of magnitude harder for
>>> third party tools to do. If you need to 'comment out' a section of
>>> syntactically invalid code, use the /+ ... +/ nesting comment.
>> Just so you know, this is not a solution. We all know that the main
>> reason people ask for this is to have code that can compile with D1 or
>> D2 using versioning. /+ .. +/ doesn't help there.
>
> I know, and I have the same issues with Phobos. See my reply to Derek.
More information about the Digitalmars-d
mailing list