Next Previous Contents

## 7. Scopes

ca65 implements several sorts of scopes for symbols.

## 7.1 Global scope

All (non cheap local) symbols that are declared outside of any nested scopes are in global scope.

## 7.2 Cheap locals

A special scope is the scope for cheap local symbols. It lasts from one non local symbol to the next one, without any provisions made by the programmer. All other scopes differ in usage but use the same concept internally.

## 7.3 Generic nested scopes

A nested scoped for generic use is started with `.SCOPE` and closed with `.ENDSCOPE`. The scope can have a name, in which case it is accessible from the outside by using explicit scopes. If the scope does not have a name, all symbols created within the scope are local to the scope, and aren't accessible from the outside.

A nested scope can access symbols from the local or from enclosing scopes by name without using explicit scope names. In some cases there may be ambiguities, for example if there is a reference to a local symbol that is not yet defined, but a symbol with the same name exists in outer scopes:

> `

>             .scope  outer
>                     foo     = 2
>                     .scope  inner
>                             lda     #foo
>                             foo     = 3
>                     .endscope
>             .endscope

> `

In the example above, the `lda` instruction will load the value 3 into the accumulator, because `foo` is redefined in the scope. However:

> `

>             .scope  outer
>                     foo     = $1234
>                     .scope  inner
>                             lda     foo,x
>                             foo     = $12
>                     .endscope
>             .endscope

> `

Here, `lda` will still load from `$12,x`, but since it is unknown to the assembler that `foo` is a zeropage symbol when translating the instruction, absolute mode is used instead. In fact, the assembler will not use absolute mode by default, but it will search through the enclosing scopes for a symbol with the given name. If one is found, the address size of this symbol is used. This may lead to errors:

> `

>             .scope  outer
>                     foo     = $12
>                     .scope  inner
>                             lda     foo,x
>                             foo     = $1234
>                     .endscope
>             .endscope

> `

In this case, when the assembler sees the symbol `foo` in the `lda` instruction, it will search for an already defined symbol `foo`. It will find `foo` in scope `outer`, and a close look reveals that it is a zeropage symbol. So the assembler will use zeropage addressing mode. If `foo` is redefined later in scope `inner`, the assembler tries to change the address in the `lda` instruction already translated, but since the new value needs absolute addressing mode, this fails, and an error message "Range error" is output.

Of course the most simple solution for the problem is to move the definition of `foo` in scope `inner` upwards, so it precedes its use. There may be rare cases when this cannot be done. In these cases, you can use one of the address size override operators:

> `

>             .scope  outer
>                     foo     = $12
>                     .scope  inner
>                             lda     a:foo,x
>                             foo     = $1234
>                     .endscope
>             .endscope

> `

This will cause the `lda` instruction to be translated using absolute addressing mode, which means changing the symbol reference later does not cause any errors.

## 7.4 Nested procedures

A nested procedure is created by use of `.PROC`. It differs from a `.SCOPE` in that it must have a name, and a it will introduce a symbol with this name in the enclosing scope. So

> `

>             .proc   foo
>                     ...
>             .endproc

> `

is actually the same as

> `

>             foo:
>             .scope  foo
>                     ...
>             .endscope

> `

This is the reason why a procedure must have a name. If you want a scope without a name, use `.SCOPE`.

Note: As you can see from the example above, scopes and symbols live in different namespaces. There can be a symbol named `foo` and a scope named `foo` without any conflicts (but see the section titled "Scope search order").

## 7.5 Structs, unions and enums

Structs, unions and enums are explained in a separate section, I do only cover them here, because if they are declared with a name, they open a nested scope, similar to `.SCOPE`. However, when no name is specified, the behaviour is different: In this case, no new scope will be opened, symbols declared within a struct, union, or enum declaration will then be added to the enclosing scope instead.

## 7.6 Explicit scope specification

Accessing symbols from other scopes is possible by using an explicit scope specification, provided that the scope where the symbol lives in has a name. The namespace token (`::`) is used to access other scopes:

> `

>             .scope  foo
>             bar:    .word   0
>             .endscope

>                     ...
>                     lda     foo::bar        ; Access foo in scope bar

> `

The only way to deny access to a scope from the outside is to declare a scope without a name (using the `.SCOPE` command).

A special syntax is used to specify the global scope: If a symbol or scope is preceded by the namespace token, the global scope is searched:

> `

>             bar     = 3

>             .scope  foo
>                     bar     = 2
>                     lda     #::bar  ; Access the global bar (which is 3)
>             .endscope

> `

## 7.7 Scope search order

The assembler searches for a scope in a similar way as for a symbol. First, it looks in the current scope, and then it walks up the enclosing scopes until the scope is found.

However, one important thing to note when using explicit scope syntax is, that a symbol may be accessed before it is defined, but a scope may not be used without a preceding definition. This means that in the following example:

> `

>             .scope  foo
>                     bar     = 3
>             .endscope

>             .scope  outer
>                     lda     #foo::bar  ; Will load 3, not 2!
>                     .scope  foo
>                             bar     = 2
>                     .endscope
>             .endscope

> `

the reference to the scope `foo` will use the global scope, and not the local one, because the local one is not visible at the point where it is referenced.

Things get more complex if a complete chain of scopes is specified:

> `

>             .scope  foo
>                     .scope  outer
>                             .scope  inner
>                                     bar = 1
>                             .endscope
>                     .endscope
>                     .scope  another
>                             .scope  nested
>                                     lda     #outer::inner::bar      ; 1
>                             .endscope
>                     .endscope
>             .endscope

>             .scope  outer
>                     .scope  inner
>                             bar = 2
>                     .endscope
>             .endscope

> `

When `outer::inner::bar` is referenced in the `lda` instruction, the assembler will first search in the local scope for a scope named `outer`. Since none is found, the enclosing scope (`another`) is checked. There is still no scope named `outer`, so scope `foo` is checked, and finally scope `outer` is found. Within this scope, `inner` is searched, and in this scope, the assembler looks for a symbol named `bar`.

Please note that once the anchor scope is found, all following scopes (`inner` in this case) are expected to be found exactly in this scope. The assembler will search the scope tree only for the first scope (if it is not anchored in the root scope). Starting from there on, there is no flexibility, so if the scope named `outer` found by the assembler does not contain a scope named `inner`, this would be an error, even if such a pair does exist (one level up in global scope).

Ambiguities that may be introduced by this search algorithm may be removed by anchoring the scope specification in the global scope. In the example above, if you want to access the "other" symbol `bar`, you would have to write:

> `

>             .scope  foo
>                     .scope  outer
>                             .scope  inner
>                                     bar = 1
>                             .endscope
>                     .endscope
>                     .scope  another
>                             .scope  nested
>                                     lda     #::outer::inner::bar    ; 2
>                             .endscope
>                     .endscope
>             .endscope

>             .scope  outer
>                     .scope  inner
>                             bar = 2
>                     .endscope
>             .endscope

> `

Next Previous Contents