6.22.1.2 Where are locals visible by name?

Basically, the answer is that locals are visible where you would expect it in block-structured languages, and sometimes a little longer. If you want to restrict the scope of a local, enclose its definition in SCOPE...ENDSCOPE.

scope ( compilation  – scope ; run-time  –  ) gforth-0.2 “scope”
endscope ( compilation scope – ; run-time  –  ) gforth-0.2 “endscope”

These words behave like control structure words, so you can use them with CS-PICK and CS-ROLL to restrict the scope in arbitrary ways.

If you want a more exact answer to the visibility question, here’s the basic principle: A local is visible in all places that can only be reached through the definition of the local30. In other words, it is not visible in places that can be reached without going through the definition of the local. E.g., locals defined in IF...ENDIF are visible until the ENDIF, locals defined in BEGIN...UNTIL are visible after the UNTIL (until, e.g., a subsequent ENDSCOPE).

The reasoning behind this solution is: We want to have the locals visible as long as it is meaningful. The user can always make the visibility shorter by using explicit scoping. In a place that can only be reached through the definition of a local, the meaning of a local name is clear. In other places it is not: How is the local initialized at the control flow path that does not contain the definition? Which local is meant, if the same name is defined twice in two independent control flow paths?

This should be enough detail for nearly all users, so you can skip the rest of this section. If you really must know all the gory details and options, read on.

In order to implement this rule, the compiler has to know which places are unreachable. It knows this automatically after AHEAD, AGAIN, EXIT and LEAVE; in other cases (e.g., after most THROWs), you can use the word UNREACHABLE to tell the compiler that the control flow never reaches that place. If UNREACHABLE is not used where it could, the only consequence is that the visibility of some locals is more limited than the rule above says. If UNREACHABLE is used where it should not (i.e., if you lie to the compiler), buggy code will be produced.

UNREACHABLE ( ) gforth-0.2 “UNREACHABLE”

Another problem with this rule is that at BEGIN, the compiler does not know which locals will be visible on the incoming back-edge. All problems discussed in the following are due to this ignorance of the compiler (we discuss the problems using BEGIN loops as examples; the discussion also applies to ?DO and other loops). Perhaps the most insidious example is:

AHEAD
BEGIN
  x
[ 1 CS-ROLL ] THEN
  {: x :}
  ...
UNTIL

This should be legal according to the visibility rule. The use of x can only be reached through the definition; but that appears textually below the use.

From this example it is clear that the visibility rules cannot be fully implemented without major headaches. Our implementation treats common cases as advertised and the exceptions are treated in a safe way: The compiler makes a reasonable guess about the locals visible after a BEGIN; if it is too pessimistic, the user will get a spurious error about the local not being defined; if the compiler is too optimistic, it will notice this later and issue a warning. In the case above the compiler would complain about x being undefined at its use. You can see from the obscure examples in this section that it takes quite unusual control structures to get the compiler into trouble, and even then it will often do fine.

If the BEGIN is reachable from above, the most optimistic guess is that all locals visible before the BEGIN will also be visible after the BEGIN. This guess is valid for all loops that are entered only through the BEGIN, in particular, for normal BEGIN...WHILE...REPEAT and BEGIN...UNTIL loops and it is implemented in our compiler. When the branch to the BEGIN is finally generated by AGAIN or UNTIL, the compiler checks the guess and warns the user if it was too optimistic:

IF
  {: x :}
BEGIN
  \ x ? 
[ 1 cs-roll ] THEN
  ...
UNTIL

Here, x lives only until the BEGIN, but the compiler optimistically assumes that it lives until the THEN. It notices this difference when it compiles the UNTIL and issues a warning. The user can avoid the warning, and make sure that x is not used in the wrong area by using explicit scoping:

IF
  SCOPE
  {: x :}
  ENDSCOPE
BEGIN
[ 1 cs-roll ] THEN
  ...
UNTIL

Since the guess is optimistic, there will be no spurious error messages about undefined locals.

If the BEGIN is not reachable from above (e.g., after AHEAD or EXIT), the compiler cannot even make an optimistic guess, as the locals visible after the BEGIN may be defined later.

It pessimistically assumes that all locals are visible that were visible at the latest place outside any control structure (i.e., where nothing is on the control-flow stack). This means that in:

: foo
  IF {: z :} THEN
  {: x :}
  AHEAD
    BEGIN
      ( * )
    [ 1 CS-ROLL ] THEN
    {: y :}
    ...
  UNTIL ;

At the place marked with ( * ), x is visible, but y is not (although, according to the reachability rule it should); z is not and should not be visible there.

However, you can use ASSUME-LIVE to make the compiler assume that the same locals are visible at the BEGIN as at the point where the top control-flow stack item was created.

ASSUME-LIVE ( orig – orig  ) gforth-0.2 “ASSUME-LIVE”

E.g.,

IF
  {: x :}
  AHEAD
    ASSUME-LIVE
    BEGIN
      x
    [ 1 CS-ROLL ] THEN
    ...
  UNTIL
THEN

Here x would not be visible at the use of x, because its definition is inside a control structure, but by using ASSUME-LIVE the programmer tells the compiler that the locals visible at the AHEAD should be visible at the BEGIN.

Other cases where the locals are defined before the BEGIN can be handled by inserting an appropriate CS-ROLL before the ASSUME-LIVE (and changing the control-flow stack manipulation behind the ASSUME-LIVE).

Cases where locals are defined after the BEGIN (but should be visible immediately after the BEGIN) can only be handled by rearranging the loop. E.g., the “most insidious” example above can be arranged into:

BEGIN
  {: x :}
  ... 0=
WHILE
  x
REPEAT

Footnotes

(30)

In compiler construction terminology, all places dominated by the definition of the local.