RR's Ramblings

@, Ada 202x, and Janus/Ada
January 4, 2017
by Randy Brukardt

Janus/Ada has used the at-sign '@' as an Ada extension for conditional compilation since the original version of Janus/Ada in 1981. But the ARG (the committee in charge of the evolution of Ada) has decided to use @ for a new Ada feature in the next version of Ada. In order to support that version of Ada, we'll have to do something with the existing use.

A refresher: The Janus/Ada conditional compilation feature operates lexically. Specifically, the @ is treated as a space if the /C option is used on the command line, and as a comment symbol ("--") if the /E option is used on the command line. (If neither option is used, the compiler will run in no-extensions mode, and a use of @ will be illegal.)

Since the processing is lexical, any Ada element can be removed from the source using the @, including pragmas, context clauses (with clauses), and global declarations, none of which can be removed by the use of a conditional with a static Boolean value (the traditional Ada way of conditionally compiling code). Given our initial focus on supporting very small host machines (initially, CP/M-80 machines with as little as 48K of memory [imagine trying to run anything in that little memory today!]), we needed to be able to remove anything unneeded from the production products.

Thus, even today, the Janus/Ada source code heavily uses the @ extension to mark debugging code. For instance, many Janus/Ada units have lines like:

     pragma Debug (Off); pragma RangeCheck (Off); pragma Arithcheck (Off);
   @ pragma Debug (On); pragma RangeCheck (On); pragma Arithcheck (On);

which turns on debugging (walkbacks), range checks, and overflow checking only when conditional compilation is on (/C is used) [note that checking is off by default]; and

   @ with J2Trace, Target_IO;

which includes the Janus2 Trace module and (text) I/O for target types only if conditional compilation is in use. (It's not usually necessary to allow customers to do their own compiler tracing.)

We chose @ for this purpose because it is not used for any purpose by the Ada language, and because it is short and highly visible.

This past summer, the ARG approved a proposal(*) to use @ to represent the assignment target in the source expression of an assignment. The basic idea is to use @ to represent the target of an assignment so that it does not need to be repeated (possibly with a subtle mistake) in the source expression. (You can see the technical details in clause 5.2.1 of the draft RM.)

(*) This choice was very controversial. At one point, I counted well over 30 proposals for this syntax over a 3 month period. Many other proposals were considered before or since. Most likely, the number of serious proposals considered is approaching 100. Ultimately, some subset of ARG members found each other proposal to have a fatal flaw (a different subset for each proposal). Hardly anyone considered @ their first choice, but no one found a fatal flaw for it, either. Thus it became the choice.

For example, some of the code in the web log analyzer used on the www.ada-auth.org site looks like:

  if Is_View then
     Data.Total.View_Count := Data.Total.View_Count + Amount;
     Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
       (Quarter_Item (Month)).View_Count :=
     Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
       (Quarter_Item (Month)).View_Count + Amount;
     if not Is_Scanner then
        Data.Total.Non_Scanner_View_Count := Data.Total.Non_Scanner_View_Count + Amount;
        Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
           (Quarter_Item (Month)).Non_Scanner_View_Count :=
        Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
           (Quarter_Item (Month)).Non_Scanner_View_Count + Amount;
     end if;
  end if;

The target names are so complex because of the need to allocate memory in chunks, as the arrays are just too large to usefully keep in memory (at least on a 32-bit machine), given the large range of months and items [for instance, consider users (that is, source IPs), most of which only get used a handful of times during a short period of time; most of the data is empty]. Since they're so complex, it would be very easy to make a mistake and have the two items subtly different. Obviously, if that happens, the data gathered would be garbage.

Using the new proposal, @ can be used in place of the complex target name in the source expression, with the meaning of the target object before it is assigned. The above code then looks like:

  if Is_View then
     Data.Total.View_Count := @ + Amount;
     Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
       (Quarter_Item (Month)).View_Count := @ + Amount;
     if not Is_Scanner then
        Data.Total.Non_Scanner_View_Count := @ + Amount;
        Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
           (Quarter_Item (Month)).Non_Scanner_View_Count := @ + Amount;
     end if;
  end if;

It's obvious that this is easier to read and there is much less chance of a mistake in this code.

The advantage of using @ this way rather than defining some kludgy new assignment operation should be obvious: it will work in all contexts. Unlike ":=+" (or maybe ":+"), @ will work in attributes like Max and in user function calls. For instance, if we wanted to bound the count above to avoid overflow, we could have used the Max attribute:

  Data.Total.View_Count := Natural'Max(@ + Amount, COUNT_LIMIT);
  Quarter_Item_Data (Data.By_Month (Year, Quarter_Num (Month)))
    (Quarter_Item (Month)).View_Count := Natural'Max(@ + Amount, COUNT_LIMIT);

The ARG choose @ for this purpose for a number of reasons. First, @ is short (the shortest possible), which is important for a shorthand. Second, @ is highly visible for a single character. One would not want to miss the target name in an expression, as it obviously changes the meaning a lot. Third, it doesn't already have an Ada meaning, so it is available without complication (other than for Janus/Ada!). And it is even mnemonic: what else would one use to represent the Assignment Target (AT) than the AT sign? (Honestly, that reason was considered more of a joke than a serious reason.)

The first three reasons are suspiously similar to the reasons that we chose @ to use in Janus/Ada. Obviously, great minds think the same way. (Or maybe they're just obvious. ☺)

In any case, we'll have to do something with the existing Janus/Ada feature in order to implement Ada 202x. (The current plan is that 'x' here would be '0', but of course that could change in the future.) Since the existing Janus/Ada feature is lexical, it does not seem possible that the features could co-exist. There seem to be a variety of options:

  • Permanently remove the Janus/Ada extension, meaning that @ would have its Ada 202x meaning only in Janus/Ada. This would eliminate the capability (not supported by Ada) of removing debugging units from an application.
  • Change the character(s) used by the Janus/Ada extension. One idea would be to make @@ the conditional compilation characters (they'd never appear that way in Ada 202x code). This is probably the best option if only our code is using the @ conditional compilation extension. (It's not a huge problem to use a program to change all of our code in a batch; it probably would take a day or two to check out all of the code, change it, and check it back in after checking that it compiled properly.)
  • Support the current @ meaning on the /c or /e flags, and the new meaning if neither is given. The problem with that idea is that we'd be requiring to use one or the other features – that seems annoying, especially for the existing code using @.
  • Support the current @ meaning if it is the first non-whitespace character on a line, and the new meaning elsewhere in a line. The problem here is that likely a few existing uses are in the middle of a line (those are likely to be rare). Moreover, it isn't implausible that the new form might start a line if the target name is long enough:
      Quarter_Item_Data (Data.By_Month (Year, Month)).View_Count :=
         @ + Amount;
    Perhaps this would be better written with := first, but the compiler shouldn't be insisting on that.

There probably are other possibilities as well.

We'll have to choose one of these at some point in the future. (Considering how much shorter some code can be using this feature, and that fact that GNAT has already implemented it, we'd like to do that sooner rather than later.) I'd like to hear your thoughts on this choice, especially as we have no way to know whether you have any code that would be impacted by a change. Send your thoughts to me at randy@rrsoftware.com.

Copyright © 2017 RR Software, Inc.
Use of this site constitutes your acceptance of these terms of use.