Rewritten July 2025
Declaration Only:
{scope} MAP# varname {,vartype {,varsize}} {,@origin}
{scope} MAP# varname {,usertype} {,@origin}
Declaration and Initial Assignment:
{scope} MAP# varname {,vartype {,varsize}} {,initialvalue}
{scope} MAP# varname {,usertype} {,initialvalue}
MAP statements explicitly define (declare and initialize) variables in terms of size and data type as well as position in memory relative to other variables. The latter capability is particularly useful for defining compound structures such as file records so as to match precisely with a physical byte layout on an external medium. All variables are implicitly initialized to zero or null, although you can optionally specify a different initial value.
Attributes
scope
Optional indicator of the variable's scope, both in space and in time. If omitted, the scope is determined by the context -- global/static if outside of a function or procedure; local/automatic if inside. See Variable Scope for details.
MAP#
# is a single digit 1 though 9 (i.e. MAP1 thru MAP9) indicating the relative level of the variable within a hierarchy. MAP1 is the top, MAP9 the bottom. Ordinary independent variables that are not part of compound structures are normally declared with MAP1, although may be grouped under a heading just for aesthetic purposes, e.g.
MAP1 TEMP'WORK'VARS
MAP2 COUNTER,B,2
MAP2 AMOUNT,F,6
MAP2 SUBTOT,F,6
MAP2 LOOPVAR,I,2
MAP2 TITLE$,S,30
The first variable declared must be at the top level (MAP1), and any lower level variables (MAP2 thru MAP9) must have a higher-level parent which does not have an explicit type. In the example above, TEMP'WORK'VARS has no explicit type or size, making it effectively X,46 based on the total size of the lower level variables beneath it.
MAP1 (level 1, i.e. top-level) variables are positioned on even byte boundaries, subject to override by the ALIGN# PRAGMAs. Lower-level MAP statements are aligned on byte boundaries (no inter-variable spacing), and may be addressed individually or as a group via the higher level variable(s).
A single MAP1 group can only consist of lower-level MAP statements; any other kind of statement effectively terminates the group, requiring that the next MAP statement, if any, be at MAP1 level. See initialvalue below.
Note that as with all other parts of ASB source code, blank spaces and indentation have no significance. While it is customary to indent at the MAP# level increase, for aesthetic and readability purposes, it is only the level numbers themselves that matter to the arrangement.
varname
Variable names must start with an alphabetic letter, followed by zero or more alphanumeric characters plus underlines and apostrophes, up to a maximum of 260 characters. Unlike language keywords, variable names are case sensitive, unless the /CI compiler switch is used. An optional $ suffix may be appended, typically used to denote string variables. Note that while the $ suffix forces an implicitly defined variable to be a string, it has no effect on the type of an explicitly MAPped variable, whose type is defined by the explicit or implicit vartype attribute.
vartype
A single character indicating one of the built-in Scalar Types (F,B,I,S,X). Must be blank if the MAP statement is followed by another MAP statement at a lower level as in the MAP1 TEMP'WORK'VARS example above.
varsize
Literal numeric size of the variable in bytes. Must be one of the valid sizes in the Scalar Types table. Note that dynamic length variables (X,0 or S,0), while not illegal at levels MAP2 and below, should never be included within a compound structure intended to be referenced as a whole, like a record structure. See Dynamically Sized Variables for more details.
usertype
A user-defined type which encompasses both a type and a size. A simple example:
deftype BOOLEAN = I,2
...
map1 Taxable, BOOLEAN ! same as map1 Taxable, I, 2
A more complex example:
defstruct ST_CUST
map2 CusNum,b,4
map2 CusName,s,30
map2 Taxable,BOOLEAN
endstruct
map1 CUS, ST_CUST
See DEFTYPE and DEFSTRUCT for more details.
@origin
This optional attribute allows you to alter the alignment of variables in memory. Instead of following the prior variable—see note about alignment under MAP# above—in memory, you can specify that it starts at the same position as a previously declared variable (i.e. overlays it; the mechanism is effectively equivalent to the union construct in the C language). For example:
map1 CONTACT ! Byte position
map2 NAME,S,30 ! 1
map2 WORLD'PHONE, @US'PHONE ! 31
map3 COUNTRY'CODE,S,2 ! 31
map3 W'NUMBER,B,14 ! 33
map2 US'PHONE ! 31
map3 ONEPLUS,S,1 ! 31
map3 AREA,S,3 ! 32
map3 US'NUMBER,S,6 ! 35
map2 POSTCODE,S,7 ! 47
In the above structure the variable WORLD'PHONE and its two encapsulated children are overlaid by the variable US'PHONE and its three children. Not only do WORLD'PHONE and US'PHONE have different layouts, they are different in overall size (16 vs 10 bytes). The next implicitly positioned variable (POSTCODE) follows the larger of the two preceding structures, i.e. it directly follows W'NUMBER (which occupies thru position 46), not US'NUMBER (which occupies thru position 40).
initialvalue
An optional initial value to assign the variable other than the default 0 or null. Note that adding an initialvalue attribute is similar in concept to inserting an assignment statement immediately following the declaration. For example:
MAP1 TAX'RATE,F,6, 39.
is conceptually the same as...
MAP1 TAX'RATE,F,6
TAX'RATE = 39.7
... EXCEPT that assignment statements like the above are not allowed within a group of MAP statements below level 1.
Note that unlike Declaration Only MAP statements, which are really just declarations for the compiler, as opposed to statements to be executed at runtime, the initialvalue will only take effect if the statement is executed at runtime. For example:
GOTO START
...
MAP1 WHO,S,10,"Amigo"
...
START:
PRINT "Hola "; WHO; "!"
The above code snippet will display only "Hola!" instead of "Hola Amigo!", because the GOTO START jumped over the embedded assignment of the initial value to the variable WHO, which needs to happen at runtime. However, the compiler did see the variable declaration and knows that WHO is a string. Otherwise you would either get an unmapped variable compile error, or WHO would be treated implicitly as a floating point variable and display as "0".
Examples
MAP1 CALORIES'CONSUMED,B,3
MAP1 CALORIES'BURNED,B,3
The above example defines two top-level 3-byte binary variables which are logically independent but in fact will be adjacent in memory (subject to alignment fill characters).
MAP1 TIME'REC
MAP2 ID,S,4
MAP2 WORKDAYS(7)
MAP3 DTX
MAP4 MON,B,1
MAP4 DAY,B,1
MAP4 YR,B,1
MAP3 INOUTS(2)
MAP4 IN,B,3
MAP4 OUT,B,3
MAP2 RATE,F,6
MAP1 TIME'REC$,S,115,@TIME'REC
The second example above illustrates a more complex MAP structure which includes multi-level arrays and an overlay. The actual layout in memory and the syntax for accessing the individual variables is as follows:
Memory Offsets |
Variable |
Aliases |
|
Memory Offsets |
Variable |
Aliases |
|---|---|---|---|---|---|---|
0 |
ID |
TIME'REC, TIME'REC$ |
|
12 |
|
|
1 |
|
|
|
13 |
|
|
2 |
|
|
|
14 |
IN(1,2) |
INOUTS(1,2) |
3 |
|
|
|
15 |
|
|
4 |
|
|
|
16 |
|
|
5 |
MON(1) |
WORKDAYS(1), DTX(1) |
|
17 |
OUT(1,2) |
|
6 |
DAY(1) |
|
|
18 |
|
|
7 |
YR(1) |
|
|
19 |
|
|
8 |
IN(1,1) |
INOUTS(1,1) |
|
20 |
MON(2) |
WORKDAYS(2), DTX(2) |
9 |
|
|
|
21..34 |
... |
|
10 |
|
|
|
35 |
MON(3) |
WORKDAYS(3), DTX(3) |
11 |
OUT(1,1) |
|
|
36..109 |
... |
|
Note the way the array addressing syntax percolates down through the levels. For example, the variable OUT appears in the map as a scalar, but it is a child of the array INOUTS(2), which is itself a child of the array WORKDAYS(7), which makes OUT an array with two subscripts, as if it were mapped as OUT(7,2), except that the individual elements, are not contiguous with each other. (Only the elements corresponding to the top level first subscript are contiguous.)
The example also illustrates overlaying an entire map structure, TIME'REC, with another variable, TIME'REC$, can be a different type and size. The variable TIME'REC, like all top-level compound variables, is itself an unformatted (data type X) variable whose length is determined by the sum of all its lower-level dependent variables (115 bytes in this case). So the entire structure can be accessed via the single unformatted variable TIME'REC (useful for manipulating the structure as a whole, such as performing record-level file operations). By means of the @ overlay mechanism, we can define additional variables that occupy the same space and thus provide alternate views (or aliases) of the same data. In this case, the variable TIME'REC$,S,115 is overlaid on top of TIME'REC. (TIME'REC and TIME'REC$ are different variables, although in this case they happen to occupy the same space.) Overlaying a string on an unformatted/structured variable can be useful for some advanced techniques. You might also use the same technique in cases where a file contains multiple record layouts, or where you want to manipulate the data at a higher or lower level. For example, we could also overlay TIME'REC with an array of 1 byte elements to simplify operating on it at the byte level.
Note that when overlaying, the variables do not have to be the same overall size. The next position in memory (i.e. the location for the next MAP1 variable) will be determined by the larger of the variables overlaid on each other.
See Also
History
2015 August, A-Shell 6.1.1416, compiler edit 748: MAP2+ statements are now flagged as illegal if not preceded by a finalized MAP1 statement. Unfortunately, ASB does not make it clear when a MAP1 statement is finalized, other than by the next MAP1 statement. But it is never quite clear if there will be another MAP statement since they are technically legal anywhere in a program. In retrospect, the language could have used an "ENDMAP" statement, although you could effectively create your own just by defining a dummy MAP1 statement at the end of your last multi-level MAP structure.
The ambiguity did not really have much significance in earlier versions of A-ShellBASIC, except perhaps in certain kinds of nested overlays, where one variable was overlaying another whose position was still in flux because it was part of a higher level array whose element members had not been finalized. But it became more of a problem with the introduction of the language features such as DEFSTRUCT, SIZEOF(), .OFFSIZ(), etc.
As of now, the rule is that any reference to a previous variable, except in a MAP statement initializer—and when the referenced variable has been finalized, causes the current MAP1 level to be finalized. If you attempt to follow that with a MAP2 or higher, it will be triggered as an illegal MAP level.
This may cause compile errors in programs that previously compiled, but it will prevent the kind of problem fixed in compiler edit 747 from escalating into a runtime problem. If you are not sure whether you have used the technique described, recompilation with this version or higher is highly recommended.
Subtopics