Scope
Scope is what keeps the loop label in one routine from clashing with the loop in the next, and lets a sound library have an init that isn’t your init.
Every name you define (a label, a constant or a variable) belongs to where you wrote it:
- inside a
.proc, it is private to that procedure; - inside a
.scope, or in a library, it is reached through that scope’s name, for examplesound:init; - at the top level, it goes into the current scope, which is
Mainunless you have changed it.
You mostly don’t have to think about it. When you use a name, the compiler searches outward from where you are, so a procedure can use a constant from its own scope, or a machine constant from App, without writing the full path. Spell the path out only when a bare name would be ambiguous: sound:init, or App:sound:init from the root.
Throughout this page, “name” means any of the three: a label, a constant or a variable.
The name tree
Names live in a tree. A fully qualified name is the path through it, joined with :, so a constant something in .proc test in the default scope is App:Main:test:something.
Appis the root. It can’t be changed. It holds the target machine’s constants, such as the VERA register names, which the project file sets for you.- Scopes sit directly under
Appand do not nest inside each other. The default isMain. Each one is a flat namespace, and it is the unit a library uses to keep its names to itself. A scope is opened by.scopeor attached to a segment. - Procedures form a tree inside the current scope. Each
.prochas its own names, and a.procinside a.procnests. Names defined outside any.procgo into an anonymous procedure that doesn’t show up in qualified names.
.proc test
.const something $1234 ; App:Main:test:something
.endproc
Resolving a name
To resolve a name the compiler:
- Looks for an exact match at the current level, the procedure or the scope.
- Looks in the child namespaces for the bare name. This is how a name can reach a sibling procedure.
- Otherwise repeats one level up, ending at
App.
The search is case sensitive; if nothing matches, the build fails.
A partially qualified name is resolved the same way, matching the end of the path. Leaving out a middle section makes it a wildcard: App::counter matches a counter in any one scope, and is an error if more than one matches.
Switching scope
.scope opens a scope; .endscope returns to the enclosing procedure’s scope. A named scope is global, so opening the same name again later continues it.
.proc test
.const something $12
lda #something ; $12
.scope newscope
.const something $34
lda #something ; $34
.endscope
lda #something ; $12
lda #newscope:something ; $34
lda #App:newscope:something ; $34
.endproc
Procedure names
A .proc defines two names for you.
The procedure’s own name resolves to its first instruction, so jsr clear_screen and jmp clear_screen work. It is defined in the enclosing scope, so from elsewhere you write clear_screen, or App:Main:clear_screen in full.
endproc is the address just past the procedure’s last byte. It belongs to the procedure’s own namespace: inside the procedure it is the bare name endproc; from outside it is greeting:endproc. Every .proc gets its own, so they never collide, and a nested .proc has an endproc separate from the one around it. Put data straight after .endproc and read it through endproc, and it stays correct if the code changes size.
.proc greeting
ldx #0
.loop:
lda endproc, x ; the text that follows this proc
beq done
jsr $ffd2 ; CHROUT
inx
bne loop
.done:
rts
.endproc
.byte "HELLO", 0 ; greeting:endproc points here
Viewing names
Set the displayVariables compile option to true to list every name and its value in the build output.