SpectNet IDE

Visual Studio 2017/2019 integrated ZX Spectrum IDE for the Community

Z80 Assembler » Modules

As you have earlier learned, SpectNetIDE uses nested scopes for labels and symbols. When it resolves them to their values, the engine starts from the innermost scope and traverses all outer scopes until it finds the symbol value, or goes through all scopes without success — and it declares the sought symbol undefined. This type of resolution does not allow an outer scope to reach a symbol within a nested scope.

SpectNetIDE allows you to declare modules. A module is a logical scope for label and symbol resolution; it works entirely the same way as other scopes. However, a module offers something special: you can access labels and symbols declared within a module. Just as other lexical scopes, modules can be nested.

Module Declarations

A module is a section of code enclosed between the .module and .endmodule statements. Each module needs to have a name. You have three options to define the name:

; #1: Label
MyModule:
  .module
  ; ...
  .endmodule

; #2: Module ID
  .module OtherModule
  ; ...
  .endmodule

; #3: Label and ID
Module3:
  .module ThirdModule
  ; ...
  .endmodule

You can use either a label preceding the module declaration or an identifier after the .module token, or even both. The identifier takes precedence over the name, so the names of the three modules in this sample are MyModule, OtherModule, and ThirdModule, respectively.

SpectNetIDE offers syntax variations for .module and .endmodule. You can use the following tokens instead of .module: module, .scope, or scope. .endmodule has other variants, too: endmodule, .endscope, endscope, .moduleend, moduleend, .scopeend, scopeend. You can write all these tokens with entirely lowercase or uppercase characters.

Names and Labels

When you define a module without a name, the assembler raises an error. So, this code is invalid because of the missing module name:

.module
; ...
.endmodule

You can’t use a temporary name for the module, so both of these declarations raise an issue:

`TempModule:
  .module
  .endmodule

Module4:
  .module `Temp4
  .endmodule

Nonetheless, this construct is valid, as here the module uses its identifier for the name, and not its label:

`Temp5:
  .module Fifth
  .endmodule

If you need to address the start point of a module, you need to declare it with a label (of course, it may have an ID, too). You can refer to this label as the start of the module. However, you cannot use the module identifier for this purpose:

MyModule:
  .module
  ld hl,MyModule     ; OK, label name is used
EndOfMod:
  .endmodule

  .module OtherModule
  ld hl,OtherModule  ; Error, module name cannot be used as a label
  .endmodule

Module3:
  .module ThirdModule
  ld hl,Module3      ; OK, label name is used
  ld de,ThirdModule  ; Error, module name cannot be used as a label
  .endmodule

The label preceding the .endmodule statement belongs to the scope of the module.

Nested Modules

You can nest modules into each other:

.module Main
  ; ...
  .module Nested
  .endmodule
  ; ...
  .module OtherNested
  ; ...
    .module DeeplyNested
      ; ... 
    .endmodule
  ; ...
  .endmodule
  ; ...
.endmodule

The full name of modules reflects the hierarchy of their nesting. The modules in this sample have these full names in the order of their declaration:

Main
Main.Nested
Main.OtherNested
Main.OtherNested.DeeplyNested

The compiler does not allow two modules have the same full name. From this point of view, this naming is valid, even if we have two modules with the name Nested:

.module Main
  ; ...
  .module Nested
  .endmodule
  ; ...
  .module OtherNested
	; ...
	.module Nested ; OK!
	.endmodule
	; ...
  .endmodule
  ; ...
.endmodule

As you see, the full names are unique:

Main.Nested
Main.OtherNested
Main.OtherNested.Nested

Label and Symbol Resolution

With simple identifiers, the name resolution happens the way you already learned, from the innermost scope toward the outermost one. However, besides simple IDs, you can use scoped identifiers that have multiple segments.

Scoped identifiers have this syntax:

::? identifier (. identifier)*

When the compiler finds a scoped identifier, it uses a different resolution technique. Instead of starting from the innermost scope toward the outermost, it begins with the current module and traverses toward the nested modules:

.module Main
  MyValue .equ 1
  ld a,Main.MyValue        ; ld a,1
  
  .module Nested
    MyValue .equ 11
  .endmodule
  
  ld b,Nested.MyValue      ; ld b,11
  ld c,Main.Nested.MyValue ; ld c,11
  
  .module OtherNested
    MyValue .equ 12
    ld d,Nested.MyValue    ; ld d,121
  
    .module Nested
      MyValue .equ 121
      ld e,Nested.MyValue  ; ld e,121
      ld h,Main.MyValue    ; ld h,1
    .endmodule

    ld l,Nested.MyValue    ; ld l,121
  .endmodule
  
.endmodule

You can use the same name for modules on different hierarchy levels, such as in this case:

.module Main
  MyValue .equ 1
  ld a,Main.MyValue         ; ld a,1
  
  .module Nested
    MyValue .equ 11
    ld b,Nested.MyValue     ; ld b,111
	
    .module Nested
      MyValue .equ 111
      ld c,Nested.MyValue   ; ld c,111
    .endmodule

  .endmodule

.endmodule

.module Nested
  MyValue .equ 2
.endmodule

As you see, the name Nested is used in several modules:

Main
Main.Nested
Main.Nested.Nested
Nested

With the standard name resolution, we cannot access the MyValue symbol of the outer Nested module from within Main.Nested.Nested, as that module is in a different subtree. In such a situation we can use the global scope token, ::, in front of the scoped identifier. This token instructs the assembler to start identifier resolution from the global module.

Now, we can access that MyValue symbol:

.module Main
  MyValue .equ 1
  ld a,Main.MyValue         ; ld a,1
  
  .module Nested
    MyValue .equ 11
    ld b,Nested.MyValue     ; ld b,111
	
    .module Nested
      MyValue .equ 111
      ld c,Nested.MyValue   ; ld c,111
      ld d,::Nested.MyValue ; ld d,2 -- !!!
    .endmodule

  .endmodule

.endmodule

.module Nested
  MyValue .equ 2
.endmodule

Using Module-Local Names

You can hide symbols within modules using the module-local identifier notation. Every label and symbol that starts with @ is a private symbol within its module. It means that the particular symbol is visible only within its containing module (and the ones nested in the containing module). In this code, every local identifier can be accessed:

.module Main
  MyValue .equ 1
  ld a,Main.MyValue         ; ld a,1
  
  .module Nested
    @MyValue .equ 11
    ld b,Nested.MyValue     ; ld b,111
	
    .module @Nested
      MyValue .equ 111
      ld c,@MyValue         ; ld c,11
      ld d,::Nested.MyValue ; ld d,2
    .endmodule

    ld e,@MyValue           ; ld e,11
    ld h,@Nested.MyValue    ; ld h,111
  .endmodule

.endmodule

.module Nested
  MyValue .equ 2
.endmodule

However, module-local names cannot be used outside of their containing module, so this code snippet raises an error:

.module Main
  MyValue .equ 1
  ld a,Main.MyValue         
  
  .module Nested
	@MyValue .equ 11
	
	.module @Nested
	  MyValue .equ 111
	  ld c,Nested.@MyValue    ; Error
	.endmodule

  .endmodule

  ld d,Nested.@Nested.MyValue ; Error
.endmodule