==============================================================================
  AS1600 Quick-and-Dirty Documentation
==============================================================================

All of the other C and H files in the source directory (except as1600.c)
come from the public-domain retargetable Frankenstein Assembler by
Mark Zenier.  The Yacc description for the CP-1600 was written based
loosely on the existing as2650 description, and was written by yours
truly, Joe Zbiciak.

The Frankenstein Assembler is in the public domain.  My modifications
to the Frankenstein Assembler are hereby placed under GPL.  (Eventually,
I plan to clean up the 100s of warnings that GCC spews out for the 
code.  Eventually.)

Note:  General documentation for Frankenstein is in the file 'as1600.ps'.
The full, original source for the Frankenstein Assembler can be
downloaded from

    http://spatula-city.org/~im14u2c/intv/franky.zip

Have fun.  Questions, problems:  intvnut AT gmail.com (Joe Zbiciak)

QuickStart:

 -- To invoke the assembler on the source file "program.asm", producing
    the listing file "program.lst" and the object binary "program.rom",
    issue the following command:

        as1600 -o program.rom -l program.lst program.asm

    Any errors will be recorded in the assembly listing file.  The
    output is in Intellicart .ROM format.  To generate a BIN+CFG format
    output instead, run:

        as1600 -o program.bin -l program.lst program.asm

    This will generate "program.bin" and "program.cfg" instead of
    "program.rom".

Some additional quick notes not covered in the Postscript documentation:

 -- The string %% in the body of a macro expands to an integer representing
    the total number of macro expansions.  This makes it easy to define
    macro-local labels.

 -- As of Release 004, AS1600 supports the following new directives and
    operators: MACRO, REPEAT/RPT, ERR, STRLEN, ASC and LISTING.  See
    "Major New  Directives And Operators" below for REPEAT, ERR, STRLEN
    and ASC.   Macros are covered separately in the file "macros.txt".

 -- as1600 outputs either BIN+CFG or Intellicart ROM formats.  It
    can handle arbitrary memory maps as a result.

    NOTE:  Generating .ROM format directly gives you exact control
    over every memory range's memory attributes.  The .CFG format
    does not give exact control, although it is good enough for most
    purposes.

 -- To generate a BIN and CFG file from a .ROM, use the "rom2bin" utility.
    The rom2bin utility will determine the memory map from the .ROM
    file and generate an appropriate BIN and CFG file from the assembler
    output.

 -- To generate a .ROM from a BIN and CFG file, use the "bin2rom" utility.
    The .ROM utility will parse the CFG and construct a .ROM file from
    your BIN.  

    Note:  The .CFG format cannot express the full flexibility of the
    .ROM format.  If you're writing code which uses the Intellicart
    in advanced ways, you may run into issues with the .CFG format.
    That said, most sane programs have no problem with the .CFG format.

 -- I've added support for "include paths."  The "-i pathname" adds
    a directory to the include-path.  Also, the environment variable
    AS1600_PATH can specify additional directories to search. 
    The search order for an INCLUDE is:

     -- Current directory
     -- Directories specified on command line
     -- Directories specified in AS1600_PATH environment variable

    For the AS1600_PATH environment variable, individual directory
    names should be separated by semicolons.  For example:

        "/path/to/dir1;path/to/dir2;/path/to/dir3"

    The AS1600_PATH environment variable can be set from DOS using
    the SET command:

        SET AS1600_PATH="C:\path\to\dir1;C:\path\to\dir2"

    It can be set under UNIX and Linux by simple assignment.  Under Bourne

    It can be set under UNIX and Linux by simple assignment.  Under Bourne
    shell, the correct syntax is:

        AS1600_PATH="/path/to/dir1:/path/to/dir2"
        export AS1600_PATH

    Under C-Shell and tcsh, the correct syntax is:

        setenv AS1600_PATH "/path/to/dir1:/path/to/dir2"


 -- AS1600 Syntax differs sligthly from Carl's assembler.  This assembler 
    is much stricter about using GI's proper syntax.  Also, procedure
    declarations are handled differently.

             Carl's:                      Frankenstein:

    ROMWIDTH EQU 10                       ROMWIDTH 10
    PROC     FOO                  FOO:    PROC
             XORI  $123, R0               XORI #$123, R0
    @@LBL:                        @@LBL:  MVII #$123, R0
             MVII  $123, R0             
    ENDP                                  ENDP
             JSR   R5,FOO.LBL             CALL FOO.LBL

 -- This assembler recognizes "SP" as an alias for R6, and "PC" as an 
    alias for R7.

 -- This assembler supports a large number of pseudo-ops:

        TSTR Rx     -->   MOVR Rx, Rx
        CLRR Rx     -->   XORR Rx, Rx
        PSHR Rx     -->   MVO@ Rx, SP
        PULR Rx     -->   MVI@ SP, Rx
        JR   Rx     -->   MOVR Rx, PC
        CALL addr   -->   JSR  R5, addr
        BEGIN       -->   MVO@ R5, SP
        RETURN      -->   MVI@ SP, PC

 -- It also supports a number of CP-1600-specific directives:

        DCW         -->   Emit words to binary.  Same as DECLE.
        DECLE       -->   Same as DCW, similar to BYTE.
        BIDECLE     -->   Outputs word in DBD format.  Same as WORD.
        ROMWIDTH    -->   Sets ROM width (default is 16.)
        ROMW        -->   Alias for ROMWIDTH

        STRUCT/ENDS -->   SEE BELOW
        MEMATTR     -->   SEE BELOW
        ORG         -->   SEE BELOW
        RMB/RESERVE -->   SEE BELOW

    The BYTE, DCW, DECLE, BIDECLE and WORD directives have somewhat odd
    definitions.  This is due to the fact that I've adapted an 8-bit
    assembler for a machine that is not byte-addressable.  Further
    complicating things is the fact that the width of the output word is 
    variable.  Here is a short description of each:

        BYTE:    Emits a single byte (in the range 0..255) to the output.
                 The byte occupies exactly one location in the output.
                
        DECLE:   Emits a single word to the output.  The allowed range 
                 of values determined by the ROM width.  For instance, if 
                 the ROM width is 10, then the allowed range is $000 - $3FF.
                 If the ROM width is 16, then the allowed range is $0000 - 
                 $FFFF.  The value will occupy exactly one location in 
                 the output.

        BIDECLE: Divides a 16-bit word into two 8-bit values.  It outputs
                 the lower 8 bits first, followed by the upper 8 bits.
                 This is sometimes known as "DBD format".  The value
                 will occupy exactly two locations in the output.

        WORD:    Identical to BIDECLE.
    

 -- This assembler will automatically insert SDBDs in many cases
    for immediate-mode instructions.  See the description of ROMW
    (next bullet) for more details.

 -- The ROMW directive accepts one or two parameters.  The first
    parameter is the ROM width.  The second parameter changes the
    the assembler's behavior on immediate values.  The default
    is Mode 0 -- assume forward references do not need SDBD.  
    You can change this to Mode 1 -- assume forward references DO
    need SDBD.

    Ordinarily, the assembler automatically inserts SDBD instructions
    for immediate operands as is necessary.  However, this does not
    work when an expression or symbol is not yet defined (eg. a forward
    reference).  By default, the assembler assumes these references
    will fit within the ROM width, and it requires you to explicitly
    provide SDBD where they won't.  In Mode 1, the assembler will
    insert an SDBD in this case, and issue a warning telling you it
    did so.


Limitations:

 -- Error reporting isn't all that great when constant widths overflow
    the ROM width.  All you get is "expression exceeds available field 
    width", which isn't 100% descriptive.

 -- When using SDBD Mode 0 (see ROMW description above), you will still
    need to use SDBDs when making forward references which will not fit 
    in the immediate field width.  There are also certain pathelogical
    cases in *both* SDBD modes where you will need to insert an explicit
    SDBD.  Honestly, just set ROMW 16 and don't sweat the details.  :-)

 -- Code which works with Carl's assembler will not work with this
    one out-of-the-box, due to the difference in syntax on immediate-mode 
    instructions.

 -- Multiple ORG statements or ROM images with gaps are supported,
    but one must still take care to stay within the desired memory map.
    There is no protection against spilling into areas of the memory
    map that you don't want to be in.  See the "world" example in the
    "examples" directory for an example of a program that is larger than
    8K words.


==============================================================================
  Major New Directives And Operators
==============================================================================

I've made some additions to as1600 to support Intellicart bank switching,
overlays, and so on.  To do this, I've extended "ORG" and "RMB" (aka
RES or RESERVE), and I've added a new directive "MEMATTR" below.

------------------------------------------------------------------------------
    [label] ORG  inty_addr:ecs_page [, icart_attr]
------------------------------------------------------------------------------

Sets the current program counter, and specifies the address to be page-
flipped using ECS-style page flipping.  icart_attr defaults to "=R" if not 
specified.  

(Remaining description TBD.  Read below for definition of icart_attr.)

------------------------------------------------------------------------------
    [label] ORG  inty_addr [, icart_addr[, icart_attr]]
------------------------------------------------------------------------------

Sets the current program counter, and optionally a different load address
within the Intellicart's address space.  It can also specify the memory
attributes for the given range of addresses in the Intellivision's
address map.

 -- inty_addr is the address at which the code will appear in the
    Intellivision address map.

 -- icart_addr is the address at which the code will be loaded
    into the Intellicart address map.  If unspecified, "icart_addr ==
    inty_addr".

 -- icart_attr is a string defining the attributes for this
    range of memory.  The icart_attr defaults to "+R" if "icart_addr ==
    inty_addr", or "" if "icart_addr != inty_addr".  See the description
    below for a definition of "icart_attr" strings.

    NOTE:  The memory attributes are assigned on the corresponding
    range of addresses in the Intellicarts's address map, not the
    Intellivision's.  If you are constructing overlays, you will need
    to set the attributes on the overlay window separately.


EXAMPLE: Overlaid Tables

This example assembles two pairs of lookup tables.  They both pairs are 
intended to reside at $6000 in the Intellivision memory map, but one will 
be loaded at $0000 in the Intellicart address space and the other will be 
loaded at $1000.  The tables can be selected using the Intellicart's
bankswitching functionality.


        ; Set up TABLE1A/TABLE1B to load at $0000 in Intellicart.  
        ; The symbols for TABLE1A and TABLE1B will show them residing
        ; at $6000 and $6010.

        ORG      $6000,  $0000,  "-RWBN"  
TABLE1A PROC
        DECLE    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
        ENDP
TABLE1B PROC
        DECLE    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
        ENDP

        ; Set up TABLE2A/TABLE2B to load at $1000 in Intellicart.  
        ; The symbols for TABLE2A and TABLE2B will show them residing
        ; at $6000 and $6010.

        ORG      $6000,  $1000,  "-RWBN"  
TABLE2A PROC
        DECLE    0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15
        ENDP
TABLE2B PROC
        DECLE    0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10,-11,-12,-13,-14,-15
        ENDP

        ; Finally, reserve some readable memory at $6000 that is 
        ; bankswitched, so that we can switch in these tables on the
        ; fly:

        ORG      $6000,  $6000,  "=RB"
        RMB      32      ; Each pair of tables is 32 words long

------------------------------------------------------------------------------
    [label] RMB      count
    [label] RES      count
    [label] RESERVE  count
------------------------------------------------------------------------------

Advances the program counter by 'count' words, without defining the
contents of the storage.  The range of addresses are assigned the
currently active memory attributes as specified in the most recent
"ORG" statement.

The RMB (aka RES or RESERVE) directive is useful for allocating storage
to global and local variables without having to manually assign addresses
to objects.  In conjunction with ORG, it's also useful for allocating 
bankswitched ranges as shown above.

------------------------------------------------------------------------------
    MEMATTR addr_lo, addr_hi, icart_attr
------------------------------------------------------------------------------

Adjusts attributes on an inclusive range of addresses in the
Intellivision's address map.  This directive should be used carefully
and sparingly.  The most common use of MEMATTR is to mark a range of
uninitialized memory as read/write and possibly bankswitched.

MEMATTR is provided as an "out" for unforseen circumstances.
Most programs shouldn't need it, and should use ORG and RMB together.

------------------------------------------------------------------------------
    [label]            STRUCT [addr]  
    @@[local_label]:   EQU    $ + offset
                       ENDS
------------------------------------------------------------------------------

The STRUCT directive is very similar to PROC.  It is an enclosure that
is intended to make it easy to define a set of related symbols.  It
supports local labels in a manner similar to PROC.  STRUCTs cannot nest,
and cannot be contained within PROCs.  STRUCTs are ended by the ENDS
directive.

The primary difference of STRUCT as compared to PROC is that it accepts 
a starting address.  Also, the main program counter is not advanced while 
a STRUCT is active.  STRUCTs have their own private program counter.  This 
makes them useful for defining the structure of objects in memory, such 
as peripherals.  

Consider this example from gimini.asm.  It defines a set of labels named
PSG0.register which map to the 16 registers in the Programmable Sound
Generator.  In this example, notice how '$' expands to the address of the
STRUCT itself ($1F0).

PSG0            STRUCT  $01F0

@@chn_a_lo      EQU     $ + 0   ; Channel A period, lower 8 bits of 12
@@chn_b_lo      EQU     $ + 1   ; Channel B period, lower 8 bits of 12
@@chn_c_lo      EQU     $ + 2   ; Channel C period, lower 8 bits of 12
@@envlp_lo      EQU     $ + 3   ; Envelope period,  lower 8 bits of 16

@@chn_a_hi      EQU     $ + 4   ; Channel A period, upper 4 bits of 12
@@chn_b_hi      EQU     $ + 5   ; Channel B period, upper 4 bits of 12
@@chn_c_hi      EQU     $ + 6   ; Channel C period, upper 4 bits of 12
@@envlp_hi      EQU     $ + 7   ; Envelope period,  upper 8 bits of 16

@@chan_enable   EQU     $ + 8   ; Channel enables (bits 3-5 noise, 0-2 tone)
@@noise         EQU     $ + 9   ; Noise period (5 bits)
@@envelope      EQU     $ + 10  ; Envelope type/trigger (4 bits)

@@chn_a_vol     EQU     $ + 11  ; Channel A volume / Envelope select (6 bits)
@@chn_b_vol     EQU     $ + 12  ; Channel B volume / Envelope select (6 bits)
@@chn_c_vol     EQU     $ + 13  ; Channel C volume / Envelope select (6 bits)

@@io_port0      EQU     $ + 14  ; I/O port 0 (8 bits)
@@io_port1      EQU     $ + 15  ; I/O port 1 (8 bits)

                ENDS

STRUCT can also be useful for constructing a set of related constants
under a single umbrella.  Consider the following example, adapted from
gimini.asm:

PSG             STRUCT  $0000   ; Constants, etc. common to both PSGs.

                ;;----------------------------------------------------------;;
                ;; Bits to OR together for Channel Enable word              ;;
                ;;----------------------------------------------------------;;
@@tone_a_on     EQU     00000000b   
@@tone_b_on     EQU     00000000b
@@tone_c_on     EQU     00000000b
@@noise_a_on    EQU     00000000b
@@noise_b_on    EQU     00000000b
@@noise_c_on    EQU     00000000b

@@tone_a_off    EQU     00000001b
@@tone_b_off    EQU     00000010b
@@tone_c_off    EQU     00000100b
@@noise_a_off   EQU     00001000b
@@noise_b_off   EQU     00010000b
@@noise_c_off   EQU     00100000b

                ENDS

Notice that this example does not make use of the STRUCT's address.
Therefore, the address was set to 0.


And finally a word of warning:  While one may include code inside a 
STRUCT, this is strongly discouraged.  The results of such an action 
are NOT well defined.


------------------------------------------------------------------------------
    Syntax of icart_attr
------------------------------------------------------------------------------

The "icart_attr" argument is a case-insensitive string whose format is
similar to the UNIX "chmod" command.  The string may specify relative
or absolute attributes for the particular range of addresses.  Because
attribute mode changes are processed in linear order at assembly time,
the final value of the memory attributes for a given range of addresses
is determined by the order in which attributes are applied.  For this
reason, avoid specifying overlapping attributes that conflict.

Attribute strings take on the following forms:

  [{ACTION}{FLAGS}[,{ACTION}{FLAGS}]]

ACTION is one these characters:

  +    Set the following flag bits
  -    Clear the following flag bits
  =    Set the memory attributes to this exact pattern of flags

FLAGS is a list of characters from the following set:

  R    Readable
  W    Writable
  N    Narrow (8-bit memory -- not supported by Intellicart.)
  B    Bank-switchable

Notes:
  -- Up to two actions may be specified, but the "=" action
     only makes sense when used alone.

  -- Attribute strings are case insensitive, and the
     ordering within a set of flags is unimportant.

  -- Empty strings are valid, and represent "no change" on 
     memory attributes.

Examples:

  "+RWN"   ; Add readable, writable and 8-bit flags to memory
  "+RB"    ; Add readable and bank-switchable to memory
  "-W"     ; Remove writable flag from memory.
  "=RW"    ; Mark memory as exactly readable and writable.
  "+RB,-W" ; Add readable, bank-switchable, remove writable

This syntax is probably overkill, and it certainly provides the programmer
with enough rope to shoot himself in the foot.  ;-)  But that's what
assembly's all about, right?


------------------------------------------------------------------------------
    REPEAT [constant expression]    
    ; block to repeat
    ENDR

 or: 

    RPT [constant expression]    
    ; block to repeat
    ENDR
------------------------------------------------------------------------------

The REPEAT directive causes the enclosed block to be repeated in the
output file the specified number of times.  The argument must be a
constant expression -- that is, an expression whose exact value is 
known at the point the REPEAT block is first reached.

The mnemonic "RPT" is a synonym for REPEAT.

The repeat count must be >= 0.  If the count == zero, the enclosed
block is skipped -- that is, no code object code is generated for the
enclosed block.  If the count == 1, the block is copied and assembled
as-is.  If the count > 1, then the block is copied and assembled a 
total of 'count' times -- once within the REPEAT/ENDR block, and count-1
times immediately following ENDM.

When the count is larger than 1, the assembler will insert comments
to delimit the repeated copies.  

EXAMPLE #1:

    ; Copy 8 words 
    REPEAT 8 
    MVI@ R4, R0
    MVO@ R0, R5
    ENDM

This assembles to (from the listing file):

                            ; Copy 8 words
                            REPEAT 8
0000 02a0                   MVI@ R4, R0
0001 0268                   MVO@ R0, R5
                            ENDR
                        ;== 1
0002 02a0                   MVI@ R4, R0
0003 0268                   MVO@ R0, R5
                        ;== 2
0004 02a0                   MVI@ R4, R0
0005 0268                   MVO@ R0, R5
                        ;== 3
0006 02a0                   MVI@ R4, R0
0007 0268                   MVO@ R0, R5
                        ;== 4
0008 02a0                   MVI@ R4, R0
0009 0268                   MVO@ R0, R5
                        ;== 5
000a 02a0                   MVI@ R4, R0
000b 0268                   MVO@ R0, R5
                        ;== 6
000c 02a0                   MVI@ R4, R0
000d 0268                   MVO@ R0, R5
                        ;== 7
000e 02a0                   MVI@ R4, R0
000f 0268                   MVO@ R0, R5
                        ;== 8


EXAMPLE #2:

    ; Just copy 1 word
    REPEAT 1
    MVI@ R4, R0
    MVO@ R0, R5
    ENDR

This assembles to (from the listing file):

                            ; Just copy one word
                            REPEAT 1
0000 02a0                   MVI@ R4, R0
0001 0268                   MVO@ R0, R5
                            ENDR

EXAMPLE #3:

    ; No code will be generated
    REPEAT 0
    MVI@ R4, R0
    MVO@ R0, R5
    ENDR

This assembles to (from the listing file):
                            ; No code will be generated
                            REPEAT 0
                            MVI@ R4, R0
                            MVO@ R0, R5
                            ENDR

Notice that although the lines from the source appear in the listing
file, no code was generated (nothing appears to the left of the code).


RESTRICTIONS:

 -- Repeat counts must be >= 0.  
 -- Repeat blocks may not cross file boundaries.
 -- Repeat blocks may not contain INCLUDE directives.
 -- Repeat blocks may nest; however, older versions of AS1600 break with
    certain nesting patterns, and the current version may still have issues.

Other details:

 -- Repeat blocks are processed after macro expansion.  Repeat blocks
    may therefore contain expanded macros.

 -- Although REPEAT 0 disables a block, the macro expander still expands
    macros within a REPEAT 0 block.  Therefore, you cannot use REPEAT 0
    to terminate macro recursion.

 -- Errors reported for repeated lines contain the line number of the 
    original line.  In the case of a macro-expanded line, this line 
    number will be the line of the pre-expansion source line.

 -- MACRO /definitions/ contained within a repeat block will not be repeated.
    All other text within the repeat block *will* be repeated.

 -- If you use IF/ELSE/ENDI to control expressions within a REPEAT block,
    and those expressions contain macros, you may need to use _EXPMAC to
    force macro expansion.  See next section.

------------------------------------------------------------------------------
    IF _EXPMAC expr         Force macro expansion inside IF/ENDI
------------------------------------------------------------------------------

The macro engine allows recursive macros, using IF/ENDI to terminate
the recursion.  For example:

;; Generate a hailstone sequence:
MACRO Collatz(c)

.c0 QSET    %c%
.c  QSET    .c0
    DECLE   .c
    IF .c0 > 1
        IF (.c AND 1) = 0
.c          QSET    .c / 2
        ELSE
.c          QSET    3 * .c + 1
        ENDI
        Collatz(.c)
    ENDI
ENDM


However, this interferes with the REPEAT mechanism.  The macro expander
explicitly ignores lines in the not-taken branch of an IF.  The REPEAT block
resides completely after the macro engine.  It caches the output of the
macro expander to replay it.

If the taken vs. not-taken legs of the IF vary across a REPEAT block's
iterations, then the resulting code will not work.  Example:

MACRO EmitEven(x)
    DECLE   (%x%) * 8 + C_WHT
ENDM

MACRO EmitOdd(x)
    DECLE   (%x%) * 8 + C_RED
ENDM

v   QSET    0
    REPEAT  6
        IF v AND 1
            EmitOdd(v)
        ELSE
            EmitEven(v)
        ENDI
v       QSET    v + 1
    ENDR

The above code fails to assemble, because the macro expander only expands
EmitEven(), and not EmitOdd().

The new _EXPMAC keyword fixes this, by forcing macro expansion inside the
entire body of the IF:

MACRO EmitEven(x)
    DECLE   (%x%) * 8 + C_WHT
ENDM

MACRO EmitOdd(x)
    DECLE   (%x%) * 8 + C_RED
ENDM

v   QSET    0
    REPEAT  6
        IF _EXPMAC v AND 1   ; <== Added keyword
            EmitOdd(v)
        ELSE
            EmitEven(v)
        ENDI
v       QSET    v + 1
    ENDR


This only works with non-recursive macros.  Recursive macros require an IF
to terminate expansion.  That happens before the repeat buffer can see the
expansion.  Therefore, REPEAT blocks and recursive macros are incompatible.

The _EXPMAC feature was added in January 2018.  You can test for its presence
by testing whether __FEATURE.EXPMAC is defined. 

------------------------------------------------------------------------------
    ERR "string"
------------------------------------------------------------------------------

This directive issues an assembler error with the indicated message string.
This is intended to be used inside a conditional-assembly directive,
in order to indicate that some condition was not met.

Potential uses include error-checking for macro arguments, and checking
to ensure assembly did not extend beyond certain ROM boundaries.

Example:

    IF ($ > $7000)
        ERR "Program code extends beyond location $7000."
    ENDI

------------------------------------------------------------------------------
    LISTING "on"
    LISTING "off"
    LISTING "code"
    LISTING "prev"
------------------------------------------------------------------------------

Controls the listing file generation mode.  The default is "on", where
all source code lines are echoed to the listing file.  The mode "off" 
causes no lines to be echoed to the listing file.  The mode "code" causes
only those lines which generate object code to be echoed to the listing
file.  

Whenever a LISTING directive is encountered, the assembler pushes the
previous mode onto a "listing mode stack".  To return to the previous
listing mode, use the listing mode "prev".  The listing mode stack is
255 modes deep.  Overflowing this stack is not an error.  The dropped
entries are replaced with the mode "on".

The main purpose of the "code" listing mode is within macros that have
extensive conditional-assembly directives.  By including a LISTING "code"
directive at the beginning and a LISTING "prev" directive at the end,
one can produce rather clean macro assemblies.  Consider, for example,
this beast:

MACRO   gfx_row s
        LISTING "code"
        ; start of graphics definition
_gfx_x  SET 0
_gfx_b  SET $8000
_gfx_w  SET (_gfx_w SHR 8)
        REPEAT 8
_gfx_w  SET (_gfx_w + _gfx_b*((ASC(%s%,_gfx_x)<>$20) AND (ASC(%s%,_gfx_x)<>$2E) 
AND (ASC(%s%,_gfx_x)<>0)))
_gfx_x  SET _gfx_x + 1
_gfx_b  SET _gfx_b SHR 1
        ENDR
_gfx_eo SET _gfx_eo + 1
        IF  _gfx_eo = 2
        DECLE _gfx_w
_gfx_eo SET 0
        ENDI
        LISTING "prev"
ENDM

Now consider the following code which invokes this macro:

_gfx_eo SET 0
_gfx_w  SET 0
    gfx_row   ".######."
    gfx_row   "#......#"
    gfx_row   "#.#..#.#"
    gfx_row   "#......#"
    gfx_row   "#.#..#.#"
    gfx_row   "#..##..#"
    gfx_row   "#......#"
    gfx_row   ".######."


The following listing output is generated, thanks to the LISTING "code"
and LISTING "prev" directives:

                        ;   gfx_row   ".######."
                        ;   gfx_row   "#......#"
0000 817e                       DECLE _gfx_w
                        ;   gfx_row   "#.#..#.#"
                        ;   gfx_row   "#......#"
0001 81a5                       DECLE _gfx_w
                        ;   gfx_row   "#.#..#.#"
                        ;   gfx_row   "#..##..#"
0002 99a5                       DECLE _gfx_w
                        ;   gfx_row   "#......#"
                        ;   gfx_row   ".######."
0003 7e81                       DECLE _gfx_w


------------------------------------------------------------------------------
    LISTCOL expr1, expr2, expr3
------------------------------------------------------------------------------

(Available if __FEATURE.LISTCOL defined.)

This directive controls the formatting of the listing file.  Specifically:

    expr1:      Number of hex values shown on lines with source code
    expr2:      Number of hex values shown on lines without source code
    expr3:      Starting column for source code

The defaults are equivalent to LISTCOL 4, 8, 32.

The value for expr3 must be greater than or equal to 7 + 5*expr1.  All three
expressions must be greater than 0.  expr1 and expr2 must be less than 256,
and expr3 must be less than 2048.

Example:

    ORG     $7000
    LISTCOL 4, 8, 32
    DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
    LISTCOL 4, 4, 32
    DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
    LISTCOL 8, 8, 52
    DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1

Results in:

0x7000                              ORG     $7000
                                    LISTCOL 4, 8, 32
7000   0000 0001 0002 0003          DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
7004   0004 0005 0006 0007 0008 0009 0000 0001 
700C   0002 0003 0004 0005 0006 0007 0008 0009 
7014   0000 0001 0002 0003 0004 0005 0006 0007 
701C   0008 0009 0000 0001 
                                    LISTCOL 4, 4, 32
7020   0000 0001 0002 0003          DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
7024   0004 0005 0006 0007 
7028   0008 0009 0000 0001 
702C   0002 0003 0004 0005 
7030   0006 0007 0008 0009 
7034   0000 0001 0002 0003 
7038   0004 0005 0006 0007 
703C   0008 0009 0000 0001 
                                                        LISTCOL 8, 8, 52
7040   0000 0001 0002 0003 0004 0005 0006 0007          DECLE   0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1
7048   0008 0009 0000 0001 0002 0003 0004 0005 
7050   0006 0007 0008 0009 0000 0001 0002 0003 
7058   0004 0005 0006 0007 0008 0009 0000 0001 
 ERROR SUMMARY - ERRORS DETECTED 0
               -  WARNINGS       0

------------------------------------------------------------------------------
    STRLEN ("string")
------------------------------------------------------------------------------

This operator returns the length of the enclosed string.  This length is
a constant expression and can be used in SET/EQU directives, or as the
argument to a REPEAT block.

This operator is mostly useful in a macro context.

------------------------------------------------------------------------------
    ASC ("string", index)
------------------------------------------------------------------------------

This operator returns the ASCII value of the character at given index 
within the string.  ASC returns 0 for indices that are beyond the end
of the string.  The index must be a constant expresion.  ASC returns
a constant expression, and so can be used in SET/EQU directives, or
as the argument of a REPEAT block.

This operator is mostly useful in a macro context.

------------------------------------------------------------------------------
    label QSET expr     => "Quiet" SET
    label QEQU expr     => "Quiet" EQUate
------------------------------------------------------------------------------

These directives behave identically to SET/EQU, except that they mark
the symbol as "quiet."  A "quiet" symbol functions like an ordinary
symbol, but will not appear in AS1600's symbol table output.

This is useful inside macros to keep macro-internal symbols from
cluttering up the symbol table and listing output files.


------------------------------------------------------------------------------
    WMSG "string" => Warning message
    CMSG "string" => Comment message
    SMSG "string" => Status message
------------------------------------------------------------------------------

These three related directives write out messages of various forms:

 -- WMSG writes out an assembler warning from "string"

 -- CMSG writes out a comment message in the listing from "string"

 -- SMSG writes out a status message in the listing and to stdout 
    from "string"

User messages are always printed regardless of the listing mode.

------------------------------------------------------------------------------
    Stringification operators:
        $( expr-list )      => Stringify expr-list
        $#( expr )          => Render expr as signed decimal string
        $$( expr )          => Render expr as a 4 character hex string
        $%( expr )          => Render expr as an 8 character hex string
------------------------------------------------------------------------------

These operators produce strings that are usable in most contexts 
that require or accept strings.  The only exceptions are INCLUDE,
ORG and LISTING directives.

The generic stringify operator $( ) produces a string from the lower
byte of each value in expr-list.  If one of the expressions in the
list is undefined or not computable, stringify will substitute in a 
question mark ('?').

The numeric stringify operators $#( ), $$( ), and $%( ) return a
string rendered either as a signed decimal value, 4 character hex
value or 8 character hex string.  If the value is undefined or
not computable, the stringify operator will return an appropriate
number of question marks.

NOTE:  The generic stringify operator $( ) assumes no character
translation is currently active, and will warn if it detects that
a translation table is currently active.  It warns due to the
potential for a subtle interaction between these two features.

Strings decay to expression lists as needed.  AS1600 applies
character translations when converting strings to expression lists.
The stringify operator takes expression lists and converts them back
into strings.  That makes complicated expressions such as this work:

 SMSG $(" Game size:  $", $$(_SIZE), " (", $#(_SIZE), " decimal) words")

However, when a character translation table is active, the string
gets converted when decaying to an expression list.  When $( )
then converts the list back into a string, there is no way to do
a reverse mapping.

------------------------------------------------------------------------------
    Expresison-list operators:
        expr-list, expr             => concatenates to an expr-list
        ( expr-list )[ expr ]       => returns an element from an expr-list
        ( expr-list )[ expr, expr ] => Slice expr-list, returns new expr-list
------------------------------------------------------------------------------

Expression lists are built up by concatenating expressions together with
commas.

You can index a specific value in an expression list using the second
syntax.  Indexing past the end of an expression list returns 0.

Because strings decay to expression lists, ("string")[index] is a
synonym for ASC("string", index).  This only works for strings.  If
you have a general expression list, you must use (expr-list)[index].

The expr-list slice operator (expr-list)[expr,expr] returns a new expr-list
which is a subset of the elements from the original expr-list.  The slice
indices are [first,last], inclusive.  If last < first, then the values in
the slice will be in reverse order as compared to the original values.

See exprlist_examples.asm for some examples.


------------------------------------------------------------------------------
    CLASSIFY( thing )   => Returns an integer saying what 'thing' is.
------------------------------------------------------------------------------

This can be useful in macros that need to behave polymorphically.  CLASSIFY
will return one of the following values:

CLASS.ABS       EQU (-1)        ; Absolute expression (e.g. value is known)
CLASS.SET       EQU (-2)        ; Symbol defined by 'SET'
CLASS.EQU       EQU (-3)        ; Symbol defined by 'EQU'
CLASS.STRING    EQU (-4)        ; "A quoted string."
CLASS.FEATURE   EQU (-5)        ; Keyword corresponding to a FEATURE
CLASS.RESV      EQU (-6)        ; Reserved word (AND, OR, SHL, etc.)
CLASS.EMPTY     EQU (-7)        ; An empty argument, e.g. CLASSIFY()
CLASS.UNUSED    EQU (-8)        ; Should never happen (unused slot in symtab)
CLASS.UNKNOWN   EQU (-9)        ; Unable to classify argument
CLASS.UNDEF     EQU (-10000)    ; Expression with undefined value

Or, if the argument corresponds to one of the CPU registers R0 through R7,
it will return a value in the range 0..7.  R0 => 0, R1 => 1, etc.  Note
that SP and PC are recognized as synonyms for R6 and R7, and will return 6
and 7 respectively.

Nomenclature:

Absolute expressions are expressions whose value is already known.  SET
and EQU symbols are also absolute expressions, so if you want to key off
of "I know the value of this expression", then you probably want to check for
all three values (ABS, SET, EQU).

Undefined expressions are expressions that incorporate a symbol whose value
is not known yet.  These are generally forward references to labels.

Reserved words are generally the tokens the assembler recognizes as operators,
such as AND, OR, SHL, etc.  They do _not_ include mnemonics, as amazingly
enough, mnemonics can be used as labels.  This is a legal line of code:

MVI:    MVI     MVI,    R0

So, CLASSIFY(MVI) checks the /label/ MVI, not whether MVI is a mnemonic.

See "classify_example.asm" in this directory for more extensive examples.

------------------------------------------------------------------------------
    CFGVAR "var" = value    => Declares a config variable w/ a numeric value
    CFGVAR "var" = "string" => Declares a config variable w/ a string value
------------------------------------------------------------------------------

The CFG file output as part of a BIN+CFG build may include arbitrary variables
in a section named [vars].  (Also, in recent jzIntv, .ROM supports a subset.
See note at the end of this section.)

The following is an example:

[vars]
jlp = 3
name = "Cartridge Title"

Variables can have either numeric or string values.

The CFGVAR directive allows you to add variables of your own to the CFG or
ROM file.  For example, to generate the example above, you might write:

    CFGVAR "jlp" = 3
    CFGVAR "name" = "Cartridge Title"

You can also use array symbols (see "Array Symbols" below) with CFGVAR to
provide either the variable name, or the string value.  The syntax is slightly
odd, due to the way array symbols function.

    N   QSET "name"
    V   QSET "Cartridge Title"
        ; Equiv to CFGVAR "name" = "Cartridge Title"
        CFGVAR $(N[0,N]) = $(V[0,V])  

(Note:  Array symbol support for CFGVAR was added 23-Dec-2017 and does not
work with older versions of the assembler.)

The following CFG variables have special meaning to jzIntv and other related
tooling.  Names are case sensitive:

    name            The full name of this program.
    short_name      Abbrev name of this program.  18 chars or less is best.
*   author          Name of the program's author.
*   game_art_by     Name of the artist who provided in-game artwork
*   music_by        Name of in-game music composers, arrangers
*   sfx_by          Name of the sound effects artist
*   voices_by       Name of the voice actors
*   docs_by         Name of the documentation author
*   box_art_by      Name of the artist that did box, manual, overlay artwork
*   concept_by      Creator of the game concept
*   more_info_at    Free form string (URL preferred) for more information
*   publisher       Name of the program's publisher.
*   license         License this code is released under
*   release_date    Date program was released (see below for date formats)
*   year            Synonym for release_date
*   build_date      Date program was built
*   version         Free-form version string
*   description     Free-form description string
*   desc            Synonym for description
    ecs_compat      Compat value: Compatibility level with ECS
    voice_compat    Compat value: Compatibility level with Intellivoice
    intv2_compat    Compat value: Compatibility with Intellivision 2
    kc_compat       Compat value: Compatibility with Keyboard Component
    tv_compat       Compat value: Compatibility with TutorVision/INTV88
    ecs             0 = same as ecs_compat = 1;   1 = same as ecs_compat = 3
    voice           0 = same as voice_compat = 1; 1 = same as voice_compat = 2
    intv2           0 = same as intv2_compat = 0; 1 = same as intv2_compat = 1
    lto_mapper      0 = disable LTO mapper; 1 = enable.
    jlp_accel       Enable JLP accelerator (see table below)
    jlp             Synonym for jlp_accel.
    jlp_flash       Specify JLP flash storage copacity in 1.5K byte blocks.

Lines marked with a "*" can be repeated.  For example, if a game has multiple
authors, you can list them all with their own author variable.  Likewise, if
a program was released multiple times, you can give a list of release dates.

For other values, repeating a variable does not have a well defined meaning.
Typically (but not always), the first instance takes precedence.

Dates are variable-precision quantities.  They can specify as little as just
the year, or they can specify all the way down to seconds, including timezone.
The full date format is one of the following four patterns:

    YYYY-MM-DD HH:MM:SS +hhmm
    YYYY-MM-DD HH:MM:SS +hh:mm
    YYYY/MM/DD HH:MM:SS +hhmm
    YYYY/MM/DD HH:MM:SS +hh:mm

That is:

    -- Year: 4 digits, 1900 - 2155
    -- Month: 2 digits, 01 - 12
    -- Day: 2 digits, 01 - 31
    -- Hour: 2 digits, 00 - 23
    -- Minutes: 2 digits, 00 - 59
    -- Seconds: 2 digits, 00 - 60  (leap second permitted)
    -- + or - to indicate east/west of UTC
    -- Hours ahead/behind UTC (0 - 12)
    -- Minutes ahead/behind UTC

Either slashes or dashes are permitted between the YYYY-MM-DD; however, you
must be consistent.  You can specify lower precision dates by leaving off
later fields.  For example:

    YYYY                Year only
    YYYY-MM             Year and month only
    YYYY-MM-DD HH:MM    Year, month, day, hours, and minute

For dates, years below 100 are interpreted as 19xx.  So 80 means 1980, while
17 means 1917.   Be mindful of Y2K.  The valid range of years is 1901 to 2155,
with 1 through 99 serving as aliased for 1901 to 1999.  A year of 0 or any
other out-of-range value is treated as an invalid/missing date.

Compat values (for XX_compat vars) take on one of four integer values:

    0       Incompatible with.
    1       Don't care / tolerates.
    2       Supports / is enhanced by.
    3       Requires.

"Incompatible with" means that the program will not run, or runs incorrectly
when the given peripheral is in the system or when attached to the specified
type of system.  For example, certain 3rd party games don't run on INTV 2.

"Don't care / tolerates" is the default state for most things. For example,
Astrosmash does not care if you plug in the Intellivoice.  It ignores it
completely.

"Supports / is enhanced by" means that the program supports the peripheral
directly, and is enhanced in some way when it is present.  For example,
World Cup Soccer enables a 4-player mode when it detects ECS.  Space Patrol
adds 6-voice sound when it detects ECS.

"Requires" means that the program will not run, or will run incorrectly if
the peripheral is missing or if run on a different type of system.  For
example, Mind Strike will not play at all unless an ECS is attached:  You
need to press a key on the ECS keyboard to start the game.

Now for an example:  suppose *your* program has enhanced sound capabilities
when the ECS is attached.  You could mark it "ecs_compat" = 2.  If your game
crashes when the ECS is attached, you should mark it "ecs_compat" = 0.


The jlp_accel value (aka. jlp) takes on one of 4 values:

    0       JLP disabled
    1       JLP accelerators enabled at reset, no JLP flash
    2       JLP accelerators disabled at reset, JLP flash storage present
    3       JLP accelerators enabled at reset, JLP flash storage present

For jlp_accel >= 2, the jlp_flash value should be non-zero.  If missing,
it will take on a minimum value of 4.  If set to 0, jlp_flash support will
be disabled.


For program license, consider using a short string to indicate a commonly
known, existing license.  Also note that "Public Domain" isn't really a
license, so you might consider "CC CC0".

I suggest the following strings:

    String        License
    ------------  ------------------------------------------------------------
    GPLv2         GNU General Public License, Version 2
    GPLv2+        GNU General Public License, Version 2 or later
    GPLv3         GNU General Public License, Version 3
    GPLv3+        GNU General Public License, Version 3 or later
    BSD3          BSD 3 Clause
    CC CC0        Creative Commons, no restrictions
    CC BY         Creative Commons, attribution alone
    CC BY-SA      Creative Commons, attribution, share alike
    CC BY-NC      Creative Commons, attribution, non-commercial use
    CC BY-ND      Creative Commons, attribution, no derivatives
    CC BY-NC-SA   Creative Commons, attribution, non-commercial, share alike
    CC BY-ND-SA   Creative Commons, attribution, no derivatives, share alike


The following variables have default values if left unspecified:

    Variable        Default  Notes
    --------------  -------  --------------------------------------------------
    ecs_compat         1     jzIntv's internal CRC database may override to 3.
    voice_compat       1     jzIntv's internal CRC database may override to 3.
    intv2_compat       1    
    kc_compat          1    
    tv_compat          1    
    lto_mapper         0    
    jlp_accel          0    
    jlp_flash          0     Or 4 if jlp_accel >= 2.

jzIntv's built-in CRC database focuses only on well-known ROM images, and is
intended to improve behavior when presented with a well-known game.


--------------------------------
>>> NOTE ON THE .ROM FORMAT: <<<
--------------------------------

Recent versions of jzIntv (Nov 2017) add metadata tag support for the .ROM
format. These tags correspond to the variables described above.  These get
encoded as special binary tags appended to the ROM file.  

The assembler and bin2rom both know how to map CFGVARs to .ROM metadata
tags, and will do so automatically.  Conversely, rom2bin knows how to decode
metadata tags into a [vars] section in a .CFG.

The file "jzintv/doc/rom_fmt/id_tag.txt" discusses how the .ROM format
supports these metadata tags.

------------------------------------------------------------------------------
    val _ROTL16 amt     Rotate 16-bit 'val' left  by 'amt'
    val _ROTL32 amt     Rotate 32-bit 'val' left  by 'amt'
    val _ROTR16 amt     Rotate 16-bit 'val' right by 'amt'
    val _ROTR32 amt     Rotate 32-bit 'val' right by 'amt'
------------------------------------------------------------------------------

(Available if __FEATURE.ROTATE defined.)

The _ROTxxx operators rotate the value on the left by the amount specified on
the right.  _ROTLxx rotates to the left, while _ROTRxx rotates to the right.

The _ROTx16 operators truncate the value to 16 bits, and perform rotation on
the lower 16 bits.

The _ROTx32 operators operate on precisely 32 bits.

------------------------------------------------------------------------------
    SRCFILE string, line    Sets source file and line number in debug info
------------------------------------------------------------------------------

(Available if __FEATURE.SRCFILE defined.)

This directive sets the current source filename to "string", and the current
line number to "line".  This overrides the source file and line number that
would be written to the source map file.

The goal here is to support high-level languages such as IntyBASIC.

Provide a zero-length string or negative line number to disable the source
file override.

Example:

    ;FILE ./border.bas
    ;[1] CONST CS_WHITE = 7
    SRCFILE "./border.bas",1
    ;[2] Mode 0,13,6,13,6
    SRCFILE "./border.bas",2
    MVII #54893,R0
    MVO R0,_color
    MVII #2,R0
    MVO R0,_mode_select
;... many lines snipped ...
    SRCFILE "./border.bas",11
    ; HERE
Q3:     B Q3
    ;ENDFILE
    SRCFILE "",0

jzIntv uses the source-map file to provide source level debugging.  AS1600's
"-j foo.smap" flag (or "--src-map=foo.smap") generates the source-map file.

jzIntv has a corresponding --src-map=foo.smap flag for reading the source map
into the debugger.

------------------------------------------------------------------------------
    TODAY_STR_LOC( "spec" ) => Returns string w/date info (localtime)
    TODAY_STR_GMT( "spec" ) => Returns string w/date info (GMT)
    TODAY_VAL_LOC( "spec" ) => Returns expr-list w/date info (localtime)
    TODAY_VAL_GMT( "spec" ) => Returns expr-list w/date info (GMT)
------------------------------------------------------------------------------

Note: You can test for the presence of the TODAY feature by checking whether
__FEATURE.TODAY is defined.

The spec string consists of the following format chars, each preceded by %.
Where possible, these match the corresponding format chars in strftime().
(However, for portability reasons, AS1600 does not call strftime().)

For TODAY_STR_xxx, whitespace and punctuation are copied through as-is to
the output.  For TODAY_VAL_xxx, whitespace and punctuation are ignored.
Unrecognized format chars generate errors.  TODAY_STR_xxx does not NUL
terminate its strings.

TODAY_xxx_LOC returns local time.  TODAY_xxx_GMT returns GMT.

    Char     Action
    ----     -----------------------------------------------------------------
     %Y      Current year.  Always 4 digits as string.
     %y      Current year, modulo 100.  Always 2 digits as string.
     %m      Current month (01 - 12).
     %d      Current day of month (01 - 31).
     %H      Current hour, 24hr clock (00 - 23).
     %M      Current minute (00 - 59).
     %S      Current seconds (00 - 61).
     %I      Current hour, 12hr clock (01 - 12)
     %p      AM/PM ("AM"/"PM" if string, 0/1 if value)
     %z      Current timezone ("+HHMM" if string, minute delta to UTC if value)
     %%      Output a % to the output


For numeric quantities rendered as strings, all values are padded with zeros
to keep the field widths fixed.

Examples:
    
    STRING  TODAY_STR_LOC( "%Y-%m-%d %H:%M:%S" ), 0
    DECLE   TODAY_VAL_GMT( "%Y%m%d%H%M%S" )

Generates:

    STRING  "2017-12-25 18:09:49", 0
    DECLE   2017,12,25,2,09,49

Because TODAY_VAL_xxx returns an expr-list, you need to be mindful when using
TODAY_VAL_xxx with SET or EQU.

year    EQU     TODAY_VAL_LOC( "%Y" )
        DECLE   year, year[0]

The symbol 'year' gets declared as an array symbol.  (See "Array Symbols"
below.)  The above example outputs something like:

        DECLE   0, 2017

Alternately, you can use expression list indexing to get the head element:

year    EQU     (TODAY_VAL_LOC( "%Y" ))[0]
        DECLE   year

------------------------------------------------------------------------------
    ERR_IF_OVERWRITTEN expr     Mark code as "not intended to be overwritten"
    FORCE_OVERWRITE expr        Force code to be overwritten anyway
------------------------------------------------------------------------------

By default, AS1600 lets you assemble new code over addresses you've already
assembled code into.  That allows for some interesting tricks; however, most
often this is really an error.

The ERR_IF_OVERWRITTEN directive controls a flag that indicates whether the
code that follows may be safely overwritten.  0 means "safe to overwrite",
while 1 means "throw an error if overwritten."

>>> Note:  ERR_IF_OVERWRITTEN defaults to 0.  You can change the default at
>>         the command line by adding the flag -e or --err-if-overwritten

For example, if I wanted to fill some ROM with a fixed pattern, and then
overwrite it with final code, I could do something like this:

        ERR_IF_OVERWRITTEN 0    ; About to write some filler data
        ORG $6000
        REPEAT  4096 / 8
        DECLE   -1, -1, -1, -1, -1, -1, -1, -1
        ENDR
       
        ERR_IF_OVERWRITTEN 1    ; Now overwrite it with real code
        ORG $6000
        ; The following generates no errors or warnings.
fun:    PROC
        MVII    #ISR,   R0
        MVO     R0,     $100
        SWAP    R0
        MVO     R0,     $101
        ;...
        ENDP
       
        ; This code, however, will trigger an error, because it's overwriting
        ; the code we just assembled at 'fun':
        ORG $6000
        DECLE   12, 34          ; ERROR - ROM overwrite error on $6000 - $6001

The FORCE_OVERWRITE directive gives you the ability to forcibly overwrite
code that was previously assembled with ERR_IF_OVERWRITTEN == 1.  Revisiting
the previous example:

        ERR_IF_OVERWRITTEN 1    ; Now overwrite it with real code
        ORG $6000
fun:    PROC
        MVII    #ISR,   R0
        MVO     R0,     $100
        SWAP    R0
        MVO     R0,     $101
        ;...
        ENDP
       
        ; With FORCE_OVERWRITE, this code now assembles without errors.
        FORCE_OVERWRITE 1
        ORG $6000
        DECLE   12, 34

The FORCE_OVERWRITE directive is meant for use in specialized macros that
may wish to "back-patch" code that otherwise should have ERR_IF_OVERWRITTEN
turned on.  Use it sparingly.

There is no way to query the current state of ERR_IF_OVERWRITTEN or
FORCE_OVERWRITE.  If you need to track that for some reason, wrap these in
macros.

Truth table:

    ERR_IF_OVERWRITTEN  FORCE_OVERWRITE     Result on an overwrite
            off             off             No error
            off             ON              No error
            ON              off             Report an error
            ON              ON              No error

Note that ERR_IF_OVERWRITTEN tags current code to detect _future_ attempts to
overwrite, while FORCE_OVERWRITE affects the code you're assembling right now.

For example, this still generates an error, because the first DECLE was
assembled with ERR_IF_OVERWRITTEN == 1:

        ERR_IF_OVERWRITTEN 1
        ORG     $6000
        DECLE   1234

        ERR_IF_OVERWRITTEN 0
        ORG     $6000
        DECLE   3456

Conversely, this example does _not_ generate an error:

        ERR_IF_OVERWRITTEN 0
        ORG     $6000
        DECLE   1234

        ERR_IF_OVERWRITTEN 1
        ORG     $6000
        DECLE   3456


==============================================================================
    Array Symbols
==============================================================================

As of approx 2009, AS1600 now has support for array symbols.  Array symbols
are a powerful extension on normal symbols.

You can see some odditional examples in exprlist_example.asm in this
directory.

------------------------------------------------------------------------------
    Basic 1-D Array Symbols
------------------------------------------------------------------------------

You can create an array symbol with SET, QSET, EQU, or QEQU by assigning an
expression _list_ to the value, rather than a single expression.  Expression
lists can even be strings:

foo     QSET    0, 1, 2
bar     QSET    "hello"

Alternately, you can assign elements of an array symbol directly.  If you
assign to an index past the end, the array is extended; however, any missing
elements remain undefined.

baz[3]  QSET    3      ; Must define baz[0], baz[1], and baz[2] before ref'ing

Array symbols give symbols a dual identity:

 -- In most expression contexts, the symbol name returns the index of the
    last element in the array.  For example:

    foo     QSET    10, 20, 30
            DECLE   foo         ; outputs 2

    In reality, it returns either:

     -- The last assigned value to the scalar symbol, or
     -- The maximum of (last index assigned to) and (last scalar value of
        the symbol).

    This mostly gives you variable-sized array semantics if you play your
    cards correctly.

 -- If indexed by an absolute index, it returns the corresponding element:

    foo     QSET    10, 20, 30
            DECLE   foo[1]      ; outputs 20

 -- If indexed by a pair of values, it returns an array slice as an expression
    list:

    foo     QSET    10, 20, 30
            DECLE   foo[0,2]    ; outputs 10, 20, 30

 -- Reverse order slices are possible:

    foo     QSET    10, 20, 30
            DECLE   foo[2,0]    ; outputs 30, 20, 10

 -- You can assign slices to array symbols, including yourself:

    foo     QSET    10, 20, 30
    foo     QSET    foo[2,0]
            DECLE   foo[0,2]    ; outputs 30, 20, 10

The canonical way to expand an entire array (assuming it's densely populated)
is to ask for a slice from 0 to its own name.  Since the array's name in an
expression context returns its highest index, this works out well:

    foo     QSET    10, 20, 30
            DECLE   foo[0, foo] ; outputs 10, 20, 30

This makes it easy to build up variable sized structures.  For example, if you
want to push a value onto the back of an array symbol, you can do something
like this:

    stk[stk + 1]    QSET    val

You can effectively change the length of an array symbol by assigning it with
a scalar assignment.  This works if you stick to the canonical way of referring
to the entire array as a[0, a]. 

Recall that the scalar value of an array symbol represents its notional length;
however, whether the array elements are defined or not actually exists
separately of this.

Consider this example:

    fun     QSET    0,1,2,3,4,5,6,7,8,9

            DECLE   fun         ; Outputs 9
            DECLE   fun[0,fun]  ; Outputs 0,1,2,3,4,5,6,7,8,9
            DECLE   fun[0,9]    ; Outputs 0,1,2,3,4,5,6,7,8,9

    fun     QSET    5           ; Notionally chops off the last 4 elements

            DECLE   fun         ; Outputs 5
            DECLE   fun[0,fun]  ; Outputs 0,1,2,3,4,5
            DECLE   fun[0,9]    ; Outputs 0,1,2,3,4,5,6,7,8,9

The assembler does not provide a way to unset a symbol.  The length-tracking
semantic, however, gives you a mechanism to say which elements are still valid.

------------------------------------------------------------------------------
    Slice assignment
------------------------------------------------------------------------------

You can assign to a slice of an array, even from itself.  For example:

    fun         QSET    0,1,2,3,4,5,6,7,8,9
    fun[4,7]    QSET    fun[7,4]            ; Reverse elements 4 through 7.
                DECLE   fun[0,9]

This emits:  0, 1, 2, 3, 7, 6, 5, 4, 8, 9

------------------------------------------------------------------------------
    Multi-dimensional Symbols
------------------------------------------------------------------------------

The assembler supports multi-dimensional array symbols, to a limited extent:

 -- A multi-dimensional array symbol is inherently "ragged":  Each row could
    be a different length. No attempt is made to keep a multi-dimensional array
    rectangular.
   
 -- Multi-dimensional arrays only support slices on their last dimension.


------------------------------------------------------------------------------
    Stringifying arrays
------------------------------------------------------------------------------

You can stringify an array by using the expression-list-to-string operator $().
You need to reference an array slice to get the array contents.  Without that,
you'll end up with just the array length.

For example:

Warning     QEQU    "This is an example warning."
            WMSG    $(Warning[0,Warning])

This will assemble like so, as expected:

/tmp/wmsg.asm:3: WARNING - This is an example warning.
 ERROR SUMMARY - ERRORS DETECTED 0
               -  WARNINGS       1


==============================================================================
    __FEATURE           Summarizing available features
==============================================================================

AS1600 defines a number of "feature symbols" to indicate the presence of
support for a particular feature.  The feature symbols take a single common
form:

    __FEATURE.{name}

where {name} is the name of the feature.  Currently, AS1600 defines the
following features:

   Feature Name         Description
   -------------------- ----------------------------------------------------
   MACRO                Support for macros
   CFGVAR               Support for the CFGVAR directive
   SRCFILE              Support for the SRCFILE directive
   CLASSIFY             Support for the CLASSIFY operator
   TODAY                Support for the TODAY_xxx operators
   ROTATE               Support for the _ROTxxx operators
   EXPMAC               Support for the _EXPMAC operator
   OVERWRITE            Support for ERR_IF_OVERWRITTEN / FORCE_OVERWRITE

