SPL#

a Forth-style concatenative stack language. The compiler is (currently) written in Python and emits 6502 assembly code.

http://sourceforge.net/projects/spl65/ for the Apple II, adapted for the Atari 8bit and enhanced by Carsten Strotmann (cas@strotmann.de).

SPL is still work-in-progress, it has some "rough-edges" and might fail in certain places. It has many bugs. However, it works fine for some developers.

Applications in SPL:

* ATR Copy Center ACC - A copy tool for the SIO2USB device

SPL Source (Python) #

#!/usr/bin/python
#
#  SPL compiler
#  RTK, 03-Sep-2007
#  Last update:  10-Aug-2012
#
#  $Id$
#
#  This code is freeware, do as you please with it.
#
##########################################################

import os
import sys
import math
import time
from types import *

#  Modification date
LAST_MOD = "10-Aug-2012"


#-----------------------------------------------------------------------
#  Configuration section.  Modify these values to reflect
#  your working environment and target system.
#-----------------------------------------------------------------------

#  Assembler name and/or path.  If the executable is in your
#  $PATH just the name is needed.  If on Windows, a good place
#  to put the as6502.exe file is in the C:\WINDOWS\ directory in
#  which case all you need is "as6502" here.
ASSEMBLER = "atasm"

#  Library and include paths.  If you have environment variables set
#  for these, they will be used.  If you do not set environment variables
#  the default value will be used.  Therefore, set the default value to
#  the full pathname, trailing / or \ included, to the place where you
#  set up the directories, if not using environment variables.
LIB_DIR = os.getenv("SPL_LIB_PATH", default="lib/")
INCLUDE_DIR = os.getenv("SPL_INCLUDE_PATH", default="include/")

#  Pathname of blank ProDOS disk image
DISK_IMAGE = LIB_DIR + "blank.dsk"

#  Default base output file name and type
BASE_NAME = "out"
BASE_TYPE = "bin"

#  Compiler version number
VERSION = "1.0atari"

#  Header for assembly output files, must be a list of strings
HEADER = [
    "",
    "Output from SPL compiler version " + VERSION,
    "Generated on " + time.ctime(time.time()),
    ""
]

#  Code start and stack location in RAM
ORG   = 0x2200

#  Start of variable space, grows downwards
VARTOP= 0x8000  #  doesn't clobber the ProDOS BASIC interpreter

#  Library equates - case matters
EQUATES = {
    "stack"  : ORG,   #  stack address
    "sp"     : 0x80,    #  stack pointer
    "ex"     : 0x81,    #  xreg value
    "ey"     : 0x82,    #  yreg value
    "ea"     : 0x83,    #  areg value
    "ta"     : 0x84,    #  and 7, scratch 1
    "tb"     : 0x86,    #  and 9, scratch 2
    "op1"    : 0x87,    #  1st operand (4 bytes)
    "op2"    : 0x8B,    #  2nd operand (4 bytes)
    "res"    : 0x8F,    #  result (8 bytes)
    "rem"    : 0x96,    #  remainder (4 bytes)
    "tmp"    : 0x9A,    #  scratch (4 bytes)
    "sign"   : 0x9F,    #  sign (1 byte)
    "outbuf" : 0xA0,    #  output text buffer (12 bytes)
    "inbuf"  : 0x400	#  input text buffer
}

#
#  Library words dependency table.  New library words (ie, in lib/)
#  must be added to this table.  What is listed is the name of every
#  routine called (JSR or JMP) by the library word.  This table is
#  used to include only the routines necessary for the program being compiled.
#
DEPENDENCIES = {
    "abs"      : ["get_ab","comp_tb","push"],
    "accept"   : ["get_ta","push"],
    "add1"     : [],
    "add"      : ["get_tb","pop","push"],
    "addstore" : ["get_ab"],
    "and"      : ["get_ab","push"],
    "booland"  : ["get_ab","push"],
    "areg"     : ["pop"],
    "fetch"    : ["get_ta","push"],
    "dfetch"   : ["get_ta","push"],
    "cfetch"   : ["get_ta", "push"],
    "b_and"    : ["get_tb","pop","push"],
    "bclr"     : ["get_ab","push"],
    "b_or"     : ["get_tb","push"],
    "bset"     : ["get_ab","push"],
    "btest"    : ["get_ab","push"],
    "btoa"     : ["ptrout"],
    "btod"     : [],
    "b_xor"    : ["get_tb","pop","push"],
    "bye"      : [],
    "call"     : ["pop"],
    "chout"    : [],
    "ch"       : ["pop"],
    "cls"      : [],
    "cmove"    : ["get_ta", "get_tb"],
    "cmoveb"   : ["get_ta", "get_op1"],
    "comp"     : ["pop","push"],
    "comp_ta"  : [],
    "comp_tb"  : [],
    "copy"     : [],
    "count"    : ["get_ta", "push"],
    "cprhex"   : [],
    "cr"       : [],
    "crson"    : [],
    "crsoff"   : [],
    "ctoggle"  : ["pop", "get_ta"],
    "csub"     : [],
    "cv"       : ["pop"],
    "d32"      : [],
    "dabs"     : ["get_op1","push_op1"],
    "d_add"    : [],
    "dadd"     : ["get_ops","push_res"],
    "date"     : ["push"],
    "ddivide"  : ["ddiv","push"],
    "ddivmod"  : ["ddiv","push"],
    "ddiv"     : ["get_ops","d32","neg"],
    "depth"    : ["push"],
    "d_eq"     : ["zerores"],
    "deq"      : ["get_ops","d_eq","push"],
    "d_ge"     : ["d_eq","d_gt"],
    "dge"      : ["get_ops","d_ge","push"],
    "d_gt"     : ["d_sub","iszero","zerores"],
    "dgt"      : ["get_ops","d_gt","push"],
    "disp"     : ["get_ta","chout","udiv16","push"],
    "div"      : ["get_ta","comp_ta","comp_tb","udiv16","push"],
    "d_le"     : ["d_eq","d_lt"],
    "dle"      : ["get_ops","d_le","push"],
    "d_lt"     : ["d_sub","zerores"],
    "dlt"      : ["get_ops","d_lt","push"],
    "dmod"     : ["ddiv","push"],
    "dmult"    : ["get_ops","neg","m32","push_res"],
    "dnegate"  : ["get_op1","neg","op1res","push_res"],
    "d_ne"     : ["d_eq"],
    "dne"      : ["get_ops","d_ne","push"],
    "decaddr"  : ["get_ta"],
    "deccaddr" : ["pop"],
    "dnumber"  : ["pop","neg","push"],
    "dprhex"   : ["get_op1", "cprhex"],
    "dprint"   : ["get_op2","chout","neg","pntres"],
    "drop2"    : ["pop"],
    "drop"     : ["pop"],
    "d_sqrt"   : ["d_sub"],
    "dsqrt"    : ["get_op1","d_sqrt","push_res"],
    "d_sub"    : [],
    "dsub"     : ["get_ops","d_sub","push_res"],
    "dtos"     : ["pop"],
    "dup2"     : ["push"],
    "duprint"  : ["get_op2","btod","pntres"],
    "dprint"   : ["get_op2","chout","neg","btod","pntres"],
    "dup"      : ["push"],
    "emit"     : ["pop","chout"],
    "eq"       : ["get_ab","push"],
    "exit"     : [],
    "execute"  : ["pop"],
    "erase"    : ["pop","get_ab"],
    "fill"     : ["pop","get_ab"],
    "fclose"   : ["pop","push"],
    "fcreate"  : ["pop","push"], 
    "fdestroy" : ["pop","push"],
    "feof"     : ["pop","push"],
    "fetchend" : ["push"],
    "fflush"   : ["pop","push"],
    "fgetc"    : ["pop","push"],
    "fread"    : ["pop","push"],
    "fopen"    : ["pop","push","pdos_addr"],
    "fputc"    : ["pop","push"],
    "fseek"    : ["pop","push"],
    "ftell"    : ["pop","push"],
    "fwrite"   : ["pop","push"],
    "ge"       : ["get_ab","csub","push"],
    "get_ab"   : ["get_tb","get_ta"],
    "get_op1"  : ["pop"],
    "get_op2"  : ["pop"],
    "get_ops"  : ["get_op2","get_op1"],
    "get_ta"   : ["pop"],
    "get_tb"   : ["pop"],
    "get_file_info" : ["pop","push"],
    "getcwd"   : ["pop","push"],
    "gt"       : ["get_ab","csub","push"],
    "incaddr"  : ["get_ta"],
    "inccaddr" : ["pop"],
    "input_s"  : ["push"],
    "inverse"  : [],
    "iszero"   : [],
    "keyp"     : ["rdykey","push"],
    "key"      : ["rdkey","push"],
    "le"       : ["get_ab","csub","push"],
    "lt"       : ["get_ab","csub","push"],
    "m32"      : ["zerores"],
    "minus1"   : ["pop","push"],
    "minus2"   : ["pop","push"],
    "mod"      : ["get_ab","comp_ta","comp_tb","udiv16","push"],
    "mult"     : ["get_ab","comp_ta","comp_tb","umult16","push"],
    "negate"   : ["get_ta","comp_ta","push"],
    "neg"      : ["add1"],
    "ne"       : ["get_ab","push"],
    "nip"      : ["get_ta","pop","push"],
    "normal"   : [],
    "not"      : ["get_ta","push"],
    "n_str"    : ["get_ta","ptrout","comp_ta","u_str"],
    "number"   : ["pop","comp_ta","push"],
    "op1res"   : [],
    "or"       : ["get_ab","push"],
    "over"     : ["push"],
    "pad"      : ["push"],
    "pdos_addr": [],
    "plus1"    : ["get_ta","push"],
    "plus2"    : ["get_ta","push"],
    "pntres"   : ["chout"],
    "pop"      : [],
    "pos"      : ["pop"],
    "prbuf"    : ["chout"],
    "prhex2"   : ["pop", "cprhex"],
    "prhex"    : ["pop", "cprhex"],
    "primm"    : ["chout"],
    "print"    : ["n_str","prbuf"],
    "ptrout"   : [],
    "push_op1" : ["push"],
    "push_op2" : ["push"],
    "push_rem" : ["push"],
    "push_res" : ["push"],
    "push"     : [],
    "quit"     : [],
    "read_block" : ["pop","push"],
    "rename"   : ["pop","push"],
    "rdkey"    : [],
    "rdykey"   : [],
    "reset"    : [],
    "rot"      : ["get_ta","get_tb","pop","push"],
    "setcwd"   : ["pop","push"],
    "space"    : ["chout"],
    "set_file_info" : ["pop","push"],
    "spfetch"  : ["push"],
    "stod"     : ["push"],
    "strcmp"   : ["get_ab","push"],
    "strcpy"   : ["get_ab"],
    "strlen"   : ["get_ta","push"],
    "strmatch" : ["get_ab","push"],
    "strpos"   : ["get_ab","push"],
    "sub1"     : [],
    "sub"      : ["get_ab","csub","push"],
    "swapcell" : [], 
    "swap2"    : ["get_ta","get_tb","pop","push"],
    "swap"     : ["get_ta","get_tb","push"],
    "time"     : ["push"],
    "to_r"     : ["pop"],
    "from_r"   : ["push"],
    "r_fetch"  : ["to_r", "from_r", "dup"],
    "tooutbuf" : ["u_str"],
    "store16"  : ["get_ta","pop"],
    "store32"  : ["get_ta","pop"],
    "store8"   : ["get_ta","pop"],
    "udiv16"   : [],
    "udiv"     : ["get_ab","udiv16","push"],
    "ugt"      : ["get_ab","push"],
    "ult"      : ["get_ab","push"],
    "umod"     : ["get_ab","udiv16","push"],
    "umult16"  : [],
    "umult"    : ["get_ab","umult16","push"],
    "uncount"  : ["pop","push"],
    "uprint"   : ["u_str","prbuf"],
    "ushiftl"  : ["pop","push"],
    "ushiftr"  : ["pop","push"],
    "u_str"    : ["get_ta","btoa"],
    "within"   : ["over", "to_r", "from_r", "ult"],
    "write_block": ["pop","push"],
    "xor"      : ["get_ab","push"],
    "xreg"     : ["pop"],
    "yreg"     : ["pop"],
    "zerores"  : []
}


#
#  Mapping between library words and assembly labels.
#
LIBRARYMAP = {
    "abs"      : "abs",      
    "accept"   : "accept",  
    "+"        : "add",  
    "++"       : "incaddr",    
    "c++"      : "inccaddr",
    "+!"       : "addstore", 
    "areg"     : "areg",  
    "at"       : "pos",
    "@"        : "fetch",     
    "d@"       : "dfetch",     
    "c@"       : "cfetch",      
    ">outbuf"  : "tooutbuf",
    "and"      : "b_and",     
    "bclr"     : "bclr",     
    "or"       : "b_or",     
    "bset"     : "bset",     
    "btest"    : "btest",    
    "bye"      : "bye",
    "xor"      : "b_xor",    
    "call"     : "call",
    "ch"       : "ch",       
    "cls"      : "cls",      
    "cmove"    : "cmove",
    "cmove>"   : "cmoveb",
    "count"    : "count",
    "~"        : "comp",     
    "cr"       : "cr",       
    "crson"    : "crson",
    "crsoff"   : "crsoff",
    "ctoggle"  : "ctoggle",
    "cv"       : "cv",       
    "dabs"     : "dabs",   
    "date"     : "date",
    "d+"       : "dadd",     
    "d/"       : "ddivide",  
    "d/mod"    : "ddivmod",  
    "depth"    : "depth",    
    "d="       : "deq",      
    "d>="      : "dge",      
    "d>"       : "dgt",      
    "disp"     : "disp",     
    "d<="      : "dle",      
    "d<"       : "dlt",      
    "dmod"     : "dmod",     
    "d*"       : "dmult",    
    "dnegate"  : "dnegate",  
    "d<>"      : "dne",      
    "dnumber"  : "dnumber",  
    "d.$"      : "dprhex",   
    "d."       : "dprint",   
    "2drop"    : "drop2",    
    "drop"     : "drop",     
    "dsqrt"    : "dsqrt",    
    "d-"       : "dsub",     
    "2dup"     : "dup2",     
    "du."      : "duprint",  
    "dup"      : "dup",      
    "emit"     : "emit",   
    "end@"     : "fetchend",
    "exit"     : "exit",
    "erase"    : "erase",  
    "="        : "eq",       
    "execute"  : "execute",
    "fclose"   : "fclose",
    "fdestroy" : "fdestroy",
    "feof"     : "feof", 
    "fflush"   : "fflush",
    "finfo@"   : "get_file_info",
    "finfo!"   : "set_file_info",
    "fgetc"    : "fgetc",
    "fill"     : "fill",
    "fopen"    : "fopen",
    "fputc"    : "fputc",
    "fread"    : "fread",
    "fseek"    : "fseek",
    "fwrite"   : "fwrite",
    "fcreate"  : "fcreate",
    "ftell"    : "ftell",
    "getcwd"   : "getcwd",
    ">="       : "ge",       
    ">"        : "gt",       
    "input"    : "input_s",  
    "inverse"  : "inverse",  
    "keyp"     : "keyp",     
    "key"      : "key",      
    "/"        : "div",
    "<="       : "le",       
    "<"        : "lt",       
    "1-"       : "minus1",   
    "2-"       : "minus2",   
    "mod"      : "mod",      
    "*"        : "mult",     
    "negate"   : "negate",   
    "<>"       : "ne",       
    "nip"      : "nip",      
    "normal"   : "normal",   
    "not"      : "not",      
    "number"   : "number",   
    "over"     : "over",     
    "pad"      : "pad",      
    "1+"       : "plus1",    
    "2+"       : "plus2",    
    ".2$"      : "prhex2",   
    ".$"       : "prhex",    
    "."        : "print",
    "pos"      : "pos",
    "quit"     : "quit",
    "read_block" : "read_block",
    "rename"   : "rename",
    "reset"    : "reset",
    "rot"      : "rot",   
    ">r"       : "to_r",
    "r>"       : "from_r",
    "r@"       : "r_fetch",
    "setcwd"   : "setcwd",
    "space"    : "space",    
    "sp@"      : "spfetch",
    "strcmp"   : "strcmp",   
    "strcpy"   : "strcpy",   
    "strlen"   : "strlen",   
    "strmatch" : "strmatch", 
    "strpos"   : "strpos",   
    "time"     : "time",
    "-"        : "sub",      
    "--"       : "decaddr",
    "c--"      : "deccaddr",
    "2swap"    : "swap2",    
    "swap"     : "swap",     
    "!"        : "store16",     
    "d!"       : "store32",     
    "c!"       : "store8",      
    "uncount"  : "uncount",
    "u/"       : "udiv",     
    "u>"       : "ugt",      
    "u<"       : "ult",     
    "umod"     : "umod",     
    "u*"       : "umult",    
    "u."       : "uprint",   
    "<<"       : "ushiftl",  
    ">>"       : "ushiftr",  
    "><"       : "swapcell",
    "within"   : "within",
    "write_block": "write_block",
    "xreg"     : "xreg",     
    "yreg"     : "yreg" 
}


#-----------------------------------------------------------------------
#  Compiler source code follows.
#-----------------------------------------------------------------------


######################################################
#  SPLCompiler
#
class SPLCompiler:
    """Implements a compiler from SPL to 6502 assembly code."""


    #-------------------------------------------------
    #  GetLabel
    #
    def GetLabel(self, name):
        """Return a unique label."""
        
        label = "A%05d" % self.counter
        self.counter += 1
        return name+"_"+label
    

    #-------------------------------------------------
    #  Decimal
    #
    def Decimal(self, s):
        """Interpret s as a decimal number."""

        sign = +1
        start = 0

        if (s[0] == "-"):
            sign = -1
            start = 1
        elif (s[0] == "+"):
            start = 1

        i = start
        n = 0
        msg = "Illegal decimal number: "

        while (i < len(s)):
            if (s[i] in "0123456789"):
                n = 10*n + int(s[i])
            elif (s[i] == "#") or (s[i] == "%"):
                if (i != len(s)-1):
                    self.Error(msg + s)
            else:
                self.Error(msg + s)
            i += 1

        return n*sign


    #-------------------------------------------------
    #  Binary
    #
    def Binary(self, s):
        """Interpret s as a binary number."""

        n = i = 0
        msg = "Illegal binary number: "

        while (i < len(s)):
            if (s[i] in "01"):
                n = 2*n + int(s[i])
            elif (s[i] == "#") or (s[i] == "%"):
                if (i != len(s)-1):
                    self.Error(msg + s)
            else:
                self.Error(msg + s)
            i += 1

        return n

        
    #-------------------------------------------------
    #  Hexadecimal
    #
    def Hexadecimal(self, s):
        """Interpret s as a hexadecimal number."""

        n = i = 0
        msg = "Illegal hexadecimal number: "

        while (i < len(s)):
            if (s[i] in "0123456789"):
                n = 16*n + int(s[i])
            elif (s[i].upper() in "ABCDEF"):
                n = 16*n + (ord(s[i].upper()) - ord("A") + 10)
            elif (s[i] == "#") or (s[i] == "%"):
                if (i != len(s)-1):
                    self.Error(msg + s)
            else:
                self.Error(msg + s)
            i += 1

        return n


    #-------------------------------------------------
    #  Number
    #
    def Number(self, s):
        """Return s as a number, if possible."""

        if (type(s) != StringType):
            n = 0
        elif (s == ""):
            n = 0
        elif (len(s) < 2):
            n = self.Decimal(s)
        elif (s[0:2].upper() == "0X"):
            n = self.Hexadecimal(s[2:])
        elif (s[0] == "$"):
            n = self.Hexadecimal(s[1:])
        elif (s[0:2].upper() == "0B"):
            n = self.Binary(s[2:])
        elif (s[0] == "%"):
            n = self.Binary(s[1:])
        else:
            n = self.Decimal(s)
        return n


    #-------------------------------------------------
    #  CheckDecimal
    #
    def CheckDecimal(self, s):
        """True if string s is a valid decimal number."""
        
        try:
            n = int(s)
        except:
            return False
        return True
    
    
    #-------------------------------------------------
    #  CheckHexadecimal
    #
    def CheckHexadecimal(self, s):
        """True if string s is a valid hex number."""
        
        for t in s.upper():
            if (t not in "0123456789ABCDEF"):
                return False
        return True
    
    
    #-------------------------------------------------
    #  CheckBinary
    #
    def CheckBinary(self, s):
        """True if string s is a valid binary number."""
        
        for t in s:
            if (t not in "01"):
                return False
        return True
    

    #-------------------------------------------------
    #  isNumber
    #
    def isNumber(self, s):
        """Return true if string s is a valid number."""
        
        #  It must be a string
        if (type(s) != StringType):
            return False
        elif (s == ""):
            return False
        elif (len(s) < 2):
            return self.CheckDecimal(s)
        elif (s[0:2].upper() == "0X"):
            return self.CheckHexadecimal(s[2:])
        elif(s[0] == "$"):
            return self.CheckHexadecimal(s[2:])
        elif (s[0:2].upper() == "0B"):
            return self.CheckBinary(s[2:])
        elif (s[0] == "%"):
            return self.CheckBinary(s[2:])
        else:
            return True
    

    #-------------------------------------------------
    #  InvalidName
    #
    def InvalidName(self, name):
        """Returns true if the given name is not valid."""

        if (type(name) != StringType):
            return True
        if (name == ""):
            return True
        if ((name[0].upper() < "A") or (name[0].upper() > 'Z')) and (name[0] != "_"):
            return True
            
        for c in name:
            if (c.upper() not in "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_"):
                return True

        return False


    #-------------------------------------------------
    #  Error
    #
    def Error(self, msg):
        """Output an error message and quit."""

        raise ValueError("In " + self.funcName + " : " + self.Token + " : " + str(msg))


    #-------------------------------------------------
    #  ParseCommandLine
    #
    def ParseCommandLine(self):
        """Parse the command line arguments."""

        #  Defaults
        self.sys = False
        self.warn = False
        
        #  List of all source code names
        self.files = []
        
        #  Default output file base filename and type
        self.outname = BASE_NAME
        self.outtype = BASE_TYPE
        
        next = 0
        for s in sys.argv[1:]:
            if (s == "-o"):
                next = 1
            elif (s == "-t"):
                next = 2
            elif (s == "-org"):
                next = 3
            elif (s == "-var"):
                next = 4
            elif (s == "-stack"):
                next = 5
            elif (s == "-sys"):
                self.sys = True
            elif (s == "-warn"):
                self.warn = True
            elif (s == "-prodos"):
                self.VARTOP = 0x89ff
            else:
                if (next == 1):
                    self.outname = s
                    next = 0
                elif (next == 2):
                    self.outtype = s
                    next = 0
                elif (next == 3):
                    self.cmdOrigin = self.Number(s)
                    next = 0
                elif (next == 4):
                    self.VARTOP = self.Number(s)
                    next = 0
                elif (next == 5):
                    EQUATES["stack"] = self.Number(s)
                    next = 0
                else:
                    self.files.append(s)
        

    #-------------------------------------------------
    #  LoadFiles
    #
    def LoadFiles(self):
        """Load the source code on the command line."""
        
        self.source = ""

        for fname in self.files:
            
            #  If no .spl extension, add it
            if fname.find(".spl") == -1:
                fname += ".spl"

            try:
                #  Open the file as given on the command line
                f = open(fname,"r")
            except:
                #  Is it in the include directory?
                try:
                    f = open(INCLUDE_DIR + fname, "r")
                except:
                    self.Error("Unable to locate %s" % fname)
                    
            #  Append to the source string
            self.source += (f.read() + "\n")
            f.close()


    #-------------------------------------------------
    #  Tokenize
    #
    def Tokenize(self):
        """Tokenize the input source code."""
        
        if self.source == "":
            return

        self.tokens = []
        inToken = inString = inComment = inCode = False
        delim = ""
        t = ""

        for c in self.source:
            if (inString):
                if (c == delim):
                    t += c
                    self.tokens.append(t)
                    t = ""
                    inString = False
                else:
                    t += c
            elif (inComment):
                if (c == "\n"):
                    inComment = False
                elif (c == ")"):
                    inComment = False
            elif (inToken):
                if (c <= " "):
                    inToken = False
                    self.tokens.append(t)
                    t = ""
                else:
                    t += c
            else:
                if (c == "["):
                    inCode = True
                if (c == "]"):
                    inCode = False
                if (c == '"') or (c == "'"):
                    inString = True
                    delim = c
                    t += c
                elif (c == "#") and not inCode:
                    inComment = True
                elif (c == "(") and not inCode:
                    inComment = True
                elif (c == ")") and not inCode:
                    inComment = False
                elif (c <= " "):
                    pass  # ignore whitespace
                else:
                    t += c
                    inToken = True
        

    #-------------------------------------------------
    #  SetOrigin
    #
    def SetOrigin(self):
        """Set the output origin position."""
        
        #  Use the command line setting, if given
        if (self.cmdOrigin != -1):
            self.org = self.cmdOrigin
        else:
            self.org = ORG
        
        next = indef = False
        
        for i in self.tokens:
            if (i == "org"):
                if (indef):
                    self.Error("Origin statements cannot be within functions.")
                next = True
            elif (i == "def"):
                indef = True
            elif (i == ":"):
                indef = True
            elif (i == "end"):
                indef = False
            elif (i == ";"):
                indef = False
            elif (next):
                self.org = self.Number(i)
                if (self.org < 0) or (self.org > 65535):
                    self.Error("Illegal origin address: " + str(self.org))
                return
        

    #-------------------------------------------------
    #  AddName
    #
    def AddName(self, name):
        """Add a new name to the names dictionary."""

        if self.names.has_key(name):
            self.Error("Duplicate name found: " + str(name))
#        self.names[name] = self.GetLabel(name)
        self.names[name] = name
    
    #-------------------------------------------------
    #  Declarations
    #
    def Declarations(self):
        """Locate all defined variables, constants and strings."""
        
        #  Dictionaries of all defined variables, and numeric and string constants
        #  accessed by name as key.
        self.vars = {}
        self.consts = {}
        self.str = {}
        indef = False
        i = 0

        while (i < len(self.tokens)):
            if (self.tokens[i] == "var"):
                if (indef):
                    self.Error("Variables cannot be defined within functions.")
                if (i+2) >= len(self.tokens):
                    self.Error("Syntax error: too few tokens for variable declaration.")
                name = self.tokens[i+1]
                bytes= self.Number(self.tokens[i+2])
                i += 2
                if (self.InvalidName(name)):
                    self.Error("Illegal variable name: " + str(name))
                if (bytes <= 0) or (bytes >= 65535):
                    self.Error("Illegal variable size: " + name + ", size = " + str(bytes))
                self.vars[name] = bytes
                self.symtbl[name] = "VAR"
                self.AddName(name)
            elif (self.tokens[i] == "const"):
                if (indef):
                    self.Error("Constants cannot be defined within functions.")
                if (i+2) >= len(self.tokens):
                    self.Error("Syntax error: too few tokens for constant declaration.")
                name = self.tokens[i+1]
                val = self.Number(self.tokens[i+2])
                i += 2
                if (self.InvalidName(name)):
                    self.Error("Illegal constant name: " + str(name))
                if (val < -32768) or (val >= 65535):
                    self.Error("Illegal constant value: " + name + ", value = " + str(val))
                self.consts[name] = val
                self.symtbl[name] = "CONST"
                self.AddName(name)
            elif (self.tokens[i] == "str"):
                if (indef):
                    self.Error("Strings cannot be defined within functions.")
                if (i+2) >= len(self.tokens):
                    self.Error("Syntax error: too few tokens for string declaration.")
                name = self.tokens[i+1]
                val = self.tokens[i+2]
                i += 2
                if (self.InvalidName(name)):
                    self.Error("Illegal string name: " + str(name))
                if (val == ""):
                    self.Error("Illegal string value, name = " + str(name))
                self.str[name] = val
                self.symtbl[name] = "STR"
                self.AddName(name)
            elif (self.tokens[i] == "def"):
                indef = True
            elif (self.tokens[i] == ":"):
                indef = True
            elif (self.tokens[i] == "end"):
                indef = False
            elif (self.tokens[i] == ";"):
                indef = False

            i += 1


    #-------------------------------------------------
    #  ParseDataBlocks
    #
    def ParseDataBlocks(self):
        """Get all data blocks."""

        #  Dictionary of all data blocks.  Each entry stores the data values
        #  for that block.
        self.dataBlocks = {}

        indata = False
        i = 0
        name = ""
        data = []
        
        while (i < len(self.tokens)):
            if (self.tokens[i] == "data"):
                if (indata):
                    self.Error("Data blocks may not be nested.")
                indata = True
                data = []
                if (i+1) >= len(self.tokens):
                    self.Error("Too few tokens for data block declaration.")
                name = self.tokens[i+1]
                if (self.InvalidName(name)):
                    print i
                    self.Error("Illegal data block name: " + str(name))
                i += 2
            elif (self.tokens[i] == "end") and (indata):
                indata = False
                self.dataBlocks[name] = data
                self.symtbl[name] = "DATA"
                self.AddName(name)
                i += 1
            elif (indata):
                data.append(self.tokens[i])
                i += 1
            else:
                i += 1

    #-------------------------------------------------
    #  ParseCodeBlocks
    #
    def ParseCodeBlocks(self):
        """Get a code block."""

        #  Dictionary of all code blocks.  Each entry stores the code values
        #  for that block.
        self.codeBlocks = {}

        incode = False
        instatement = False
        i = 0
        name = ""
        statement = ""
        code = []
        
        while (i < len(self.tokens)):
            if (self.tokens[i] == "code"):
                if (incode):
                    self.Error("Code blocks may not be nested.")
                incode = True
                code = []
                if (i+1) >= len(self.tokens):
                    self.Error("Too few tokens for code block declaration.")
                name = self.tokens[i+1]
                if (self.InvalidName(name)):
                    print i
                    self.Error("Illegal code block name: " + str(name))
                i += 2
            elif (self.tokens[i] == "end") and (incode):
                incode = False
                self.codeBlocks[name] = code
                self.symtbl[name] = "CODE"
                self.AddName(name)
                i += 1
            elif (incode):
                if (self.tokens[i] == "["):
                        instatement = True
                        statement = "    "
                elif (self.tokens[i] == "]"):
                        code.append(statement)
                else:
                    statement = statement + " " + self.tokens[i]
                i += 1
            else:
                i += 1
        

    #-------------------------------------------------
    #  ParseFunctions
    #
    def ParseFunctions(self):
        """Parse all functions."""
        
        #  Dictionary of all functions.  Each entry stores the tokens associated with
        #  that function, accessible by function name as key.
        self.funcs = {}
        
        indef = False
        i = 0
        name = ""
        func = []
        
        while (i < len(self.tokens)):
            if ((self.tokens[i] == "def") or (self.tokens[i] == ":")):
                if (indef):
                    self.Error("Function declarations may not be nested.")
                indef = True
                func = []
                if (i+1) >= len(self.tokens):
                    self.Error("Too few tokens for function declaration.")
                name = self.tokens[i+1]
                if (self.InvalidName(name)):
                    print i
                    self.Error("Illegal function name: " + str(name))
                i += 2
            elif ((self.tokens[i] == "end") or (self.tokens[i] == ";")) and (indef):
                indef = False
                self.funcs[name] = func
                self.symtbl[name] = "FUNC"
                self.referenced[name] = False
                self.AddName(name)
                i += 1
            elif (indef):
                func.append(self.tokens[i])
                i += 1
            else:
                i += 1


    #-------------------------------------------------
    #  TagFunctions
    #
    def TagFunctions(self):
        """Mark all functions that are referenced."""

        for f in self.funcs:
            for token in self.funcs[f]:
                if (self.symtbl.has_key(token)):
                    if (self.symtbl[token] == "FUNC"):
                        self.referenced[token] = True

    
    #-------------------------------------------------
    #  isStringConstant
    #
    def isStringConstant(self, token):
        """Return true if this token is a string constant."""
        
        return (token[0] == token[-1]) and ((token[0] == '"') or (token[0] == "'"))
    
    
    #-------------------------------------------------
    #  StringConstantName
    #
    def StringConstantName(self):
        """Generate a unique name for this string constant."""
       
        name = "STR_%04X" % self.stringCount
        self.stringCount += 1
        return name
    

    #-------------------------------------------------
    #  LocateStringConstants
    #
    def LocateStringConstants(self):
        """Locate string constants inside of functions and replace them with
           references to STR constants."""

        #  Always reference "main"
        self.referenced["main"] = True

        #  Check all tokens of all defined functions
        for key in self.funcs:
            if (self.referenced[key]):
                i = 0
                while (i < len(self.funcs[key])):
                    token = self.funcs[key][i]
                    if self.isStringConstant(token):       #  if it is a string constant
                        name = self.StringConstantName()   #  generate a name for it
                        self.str[name] = token             #  and add it to the string constant dictionary
                        self.symtbl[name] = "STR"          #  plus the symbol table
                        self.funcs[key][i] = name          #  and change the token to the generated name
                        self.AddName(name)                 #  add the new name
                    i += 1


    #-------------------------------------------------
    #  Dependencies
    #
    def Dependencies(self, names):
        """Add all dependencies for the given list of dependencies."""

        for d in names:                                 #  for all dependencies
            if (d not in self.dependencies):            #  if not in global list
                self.dependencies.append(d)             #  add it
                self.Dependencies(DEPENDENCIES[d])      #  and all of its dependencies, too
                

    #-------------------------------------------------
    #  LibraryRoutines
    #
    def LibraryRoutines(self):
        """Locate all library routines used."""
        
        #  List of all library routines used
        self.lib = []
        
        #  Check all tokens of all defined functions
        for key in self.funcs:
            for token in self.funcs[key]:
                if LIBRARYMAP.has_key(token):           #  if it is a library routine
                    if (token not in self.lib):         #  and hasn't been added yet
                        self.lib.append(token)          #  add it to the list
                        self.symtbl[token] = "LIB"      #  and the symbol table
                        
        #  Now add all the dependencies
        depends = []
        for routine in self.lib:                        #  for every identified library routine
            name = LIBRARYMAP[routine]                  #  get the library file name
            for d in DEPENDENCIES[name]:                #  check its dependencies
                if (d not in depends):                  #  if not already added
                    depends.append(d)                   #  add it to the list
                    
        #  Now add the dependencies of the dependencies
        self.Dependencies(depends)
                
                
    #-------------------------------------------------
    #  CompileNumber
    #
    def CompileNumber(self, token):
        """Compile a number by pushing it on the stack."""
        
        n = self.Number(token)
        
        #  Low 16-bits
        self.fasm.append(["","lda","#<$"+hex(n)[2:]])
        self.fasm.append(["","ldx","#>$"+hex(n)[2:]])
        self.fasm.append(["","jsr","push"])
        
        #  If marked as double-precision, push the upper 16-bits, too
        if ("#" in token):
            self.fasm.append(["","lda","#"+hex((n >> 16) & 0xFF)])
            self.fasm.append(["","ldx","#"+hex((n >> 24) & 0xFF)])
            self.fasm.append(["","jsr","push"])
                    
        #  Make sure we include push and pop in the dependencies
        if ("push" not in self.dependencies):
            self.dependencies.append("push")
        if ("pop" not in self.dependencies):
            self.dependencies.append("pop")

                    
    #-------------------------------------------------
    #  CompileVariableRef
    #
    def CompileVariableRef(self, token):
        """Compile a variable ref by putting its address on the stack."""

        name = self.names[token]
        self.fasm.append(["","lda","#<"+name])
        self.fasm.append(["","ldx","#>"+name])
        self.fasm.append(["","jsr","push"])
    
    
    #-------------------------------------------------
    #  CompileDataBlockRef
    #
    def CompileDataBlockRef(self, token):
        """Compile a data block ref by putting its address on the stack."""

        name = self.names[token]
        self.fasm.append(["","lda","#<"+name])
        self.fasm.append(["","ldx","#>"+name])
        self.fasm.append(["","jsr","push"])

    #-------------------------------------------------
    #  CompileCodeBlockRef
    #
    def CompileCodeBlockRef(self, token):
        """Compile a code block ref by jsr to its address."""

        name = self.names[token]
        self.fasm.append(["","jsr", name])
    
    
    #-------------------------------------------------
    #  CompileStringConstantRef
    #
    def CompileStringConstantRef(self, token):
        """Compile a reference to a string constant by putting its
           address on the stack."""

        name = self.names[token]
        self.fasm.append(["","lda","#<"+name])
        self.fasm.append(["","ldx","#>"+name])
        self.fasm.append(["","jsr","push"])


    #-------------------------------------------------
    #  CompileLoopBegin
    #
    def CompileLoopBegin(self):
        """Compile the start of a loop."""
        
        t = self.GetLabel("loop")
        self.fasm.append([t,"",""])
        self.loop.append([t, self.GetLabel("loop2")])
    
    
    #-------------------------------------------------
    #  CompileLoopEnd
    #
    def CompileLoopEnd(self):
        """Compile the end of a loop."""
        
        if self.loop == []:
            self.Error("Loop underflow!")
        t = self.loop[-1]
        self.loop = self.loop[0:-1]
        self.fasm.append(["","jmp",t[0]])
        self.fasm.append([t[1],"",""])

       
    #-------------------------------------------------
    #  CompileIf
    #
    def CompileIf(self):
        """Compile an if statement."""
        
        t_else = self.GetLabel("else")
        t_then = self.GetLabel("then")
        t      = self.GetLabel("t")
        t_end  = self.GetLabel("tend")
        self.compare.append([t_else, t_then, False, t_end])
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","beq", t])
        self.fasm.append(["","bne", t_then])
        self.fasm.append([t, "jmp", t_else])
        self.fasm.append([t_then, "", ""])
   
    
    #-------------------------------------------------
    #  CompileNotIf
    #
    def CompileNotIf(self):
        """Compile a not if statement."""
        
        t_else = self.GetLabel("else")
        t_then = self.GetLabel("then")
        t      = self.GetLabel("t")
        t_end  = self.GetLabel("tend")
        self.compare.append([t_else, t_then, False, t_end])
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","bne", t])
        self.fasm.append(["","beq", t_then])
        self.fasm.append([t, "jmp", t_else])
        self.fasm.append([t_then, "", ""])
    
    
    #-------------------------------------------------
    #  CompileElse
    #
    def CompileElse(self):
        """Compile an else statement."""
        
        self.compare[-1][2] = True
        t_else, t_then, flag, t_end = self.compare[-1]
        self.fasm.append(["","jmp",t_end])
        self.fasm.append([t_else,"",""])
    
    
    #-------------------------------------------------
    #  CompileThen
    #
    def CompileThen(self):
        """Compile a then statement."""
        
        if self.compare == []:
            self.Error("Compare underflow!")
        t_else, t_then, flag, t_end = self.compare[-1]
        self.compare = self.compare[0:-1]
        if not flag:
            self.fasm.append([t_else,"",""])
        else:
            self.fasm.append([t_end,"",""])
    
    
    #-------------------------------------------------
    #  CompileBreak
    #
    def CompileBreak(self):
        """Comple a break statement."""
        
        t = self.loop[-1]
        self.fasm.append(["","jmp",t[1]])

    
    #-------------------------------------------------
    #  CompileCont
    #
    def CompileCont(self):
        """Compile a continue statement."""
        
        t = self.loop[-1]
        self.fasam.append(["","jmp",t[0]])
    
    
    #-------------------------------------------------
    #  CompileIfBreak
    #
    def CompileIfBreak(self):
        """Compile a conditional break."""
        
        t = self.loop[-1]
        q = self.GetLabel("break")
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","beq", q])
        self.fasm.append(["","jmp", t[1]])
        self.fasm.append([q,"",""])
    
    
    #-------------------------------------------------
    #  CompileIfCont
    #
    def CompileIfCont(self):
        """Compile a conditional continue."""
        
        t = self.loop[-1]
        q = self.GetLabel("ifcont")
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","beq", q])
        self.fasm.append(["","jmp", t[0]])
        self.fasm.append([q,"",""])
    
    
    #-------------------------------------------------
    #  CompileNotIfBreak
    #
    def CompileNotIfBreak(self):
        """Compile a negated conditional break."""
        
        t = self.loop[-1]
        q = self.GetLabel("notifbreak")
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","bne", q])
        self.fasm.append(["","jmp", t[1]])
        self.fasm.append([q,"",""])
    
    
    #-------------------------------------------------
    #  CompileNotIfCont
    #
    def CompileNotIfCont(self):
        """Compile a negated conditional continue."""
        
        t = self.loop[-1]
        q = self.GetLabel("notifcont")
        self.fasm.append(["","jsr","pop"])
        self.fasm.append(["","cmp","#0"])
        self.fasm.append(["","bne", q])
        self.fasm.append(["","jmp", t[0]])
        self.fasm.append([q,"",""])
    
    
    #-------------------------------------------------
    #  CompileLibraryRef
    #
    def CompileLibraryRef(self, token):
        """Compile a reference to a library word."""
        
        self.fasm.append(["","jsr", LIBRARYMAP[token]])


    #-------------------------------------------------
    #  CompileReturn
    #
    def CompileReturn(self):
        """Compile a return statement."""

        #  Return from subroutine
        self.fasm.append(["","rts",""])
        

    #-------------------------------------------------
    #  FunctionAddress
    #
    def FunctionAddress(self):
        """Mark the next function reference to push the address
           on the stack."""

        self.functionAddress = True
        
   
    #-------------------------------------------------
    #  Keywords
    #
    def Keywords(self):
        """Place all keywords in the symbol table and
           create the compile helper function dictionary."""
        
        self.symtbl["{"]       = "KWD"
        self.symtbl["}"]       = "KWD"
        self.symtbl["if" ]     = "KWD"
        self.symtbl["0if"]     = "KWD"
        self.symtbl["else"]    = "KWD"
        self.symtbl["then"]    = "KWD"
        self.symtbl["break"]   = "KWD"
        self.symtbl["cont"]    = "KWD"
        self.symtbl["?break"]  = "KWD"
        self.symtbl["?cont"]   = "KWD"
        self.symtbl["?0break"] = "KWD"
        self.symtbl["?0cont"]  = "KWD"
        self.symtbl["&"]       = "KWD"
        self.symtbl["return"]  = "KWD"
        
        #  Compile helper dictionary
        self.keywords = {
            "{"       : self.CompileLoopBegin,
            "}"       : self.CompileLoopEnd,
            "if"      : self.CompileIf,
            "0if"     : self.CompileNotIf,
            "else"    : self.CompileElse, 
            "then"    : self.CompileThen,
            "break"   : self.CompileBreak,
            "cont"    : self.CompileCont,
            "?break"  : self.CompileIfBreak,
            "?cont"   : self.CompileIfCont,
            "?0break" : self.CompileNotIfBreak,
            "?0cont"  : self.CompileNotIfCont,
            "return"  : self.CompileReturn,
            "&"       : self.FunctionAddress
        }


    #-------------------------------------------------
    #  CompileDataBlocks
    #
    def CompileDataBlocks(self):
        """Create assembly instructions for all data blocks."""

        #  Holds the assembly code for the data blocks
        self.dasm = []

        #  Compile each block
        for f in self.dataBlocks:
            self.dasm.append([self.names[f],"",""])   #  Data block label
            for number in self.dataBlocks[f]:
                n = self.Number(number)
                if ("#" in number):
                    if (n < -32768):
                        n += 2**32
                    self.dasm.append(["",".byte",str(n & 0xff)])
                    self.dasm.append(["",".byte",str((n >> 8) & 0xff)])
                    self.dasm.append(["",".byte",str((n >> 16) & 0xff)])
                    self.dasm.append(["",".byte",str((n >> 24) & 0xff)])
                elif ("%" in number):
                    if (n < 0):
                        n += 2**16
                    self.dasm.append(["",".byte",str(n & 0xff)])
                    self.dasm.append(["",".byte",str((n >> 8) & 0xff)])                                   
                else:
                    if (n >= -32768) and (n < 0):
                        n += 2**16
                    if (n < -32768):
                        n += 2**32
                    if (n >= 0) and (n < 256):
                        self.dasm.append(["",".byte",str(n)])
                    elif (n >= 0) and (n <= 65535):
                        self.dasm.append(["",".byte",str(n & 0xff)])
                        self.dasm.append(["",".byte",str((n >> 8) & 0xff)])
                    elif (n > 65535):
                        self.dasm.append(["",".byte",str(n & 0xff)])
                        self.dasm.append(["",".byte",str((n >> 8) & 0xff)])
                        self.dasm.append(["",".byte",str((n >> 16) & 0xff)])
                        self.dasm.append(["",".byte",str((n >> 24) & 0xff)])
        
    #-------------------------------------------------
    #  CompileCodeBlocks
    #
    def CompileCodeBlocks(self):
        """Create assembly instructions for all code blocks."""

        #  Holds the assembly code for the data blocks
        self.casm = []

        #  Compile each block
        for f in self.codeBlocks:
            self.casm.append([self.names[f],"",""])   #  Code block label
            for line in self.codeBlocks[f]:
                self.casm.append(["",line,""])
         

    #-------------------------------------------------
    #  CompileFunctions
    #
    def CompileFunctions(self):
        """Compile all defined functions."""
        
        #  Holds the assembly code for the functions
        self.fasm = []

        #  Compile each function
        for f in self.funcs:
            if (self.referenced[f]):
                self.loop = []
                self.compare = []
                self.funcName = f  #  Currently compiling
                self.fasm.append([self.names[f],"",""])  #  Subroutine label
                for token in self.funcs[f]:
                    self.Token = token  #  Current token
                    if (self.symtbl.has_key(token)):
                        if (self.symtbl[token] == "FUNC"):
                            if (self.functionAddress):
                                #  Push the function address
                                self.fasm.append(["","lda", "#<"+self.names[token]])
                                self.fasm.append(["","ldx", "#>"+self.names[token]])
                                self.fasm.append(["","jsr", "push"])
                                self.functionAddress = False
                            else:
                                #  Compile a subroutine call
                                self.fasm.append(["","jsr",self.names[token]])
                        elif (self.symtbl[token] == "VAR"):
                            self.CompileVariableRef(token)          #  Compile a variable reference
                        elif (self.symtbl[token] == "DATA"):
                            self.CompileDataBlockRef(token)         #  Compile a reference to a data block
                        elif (self.symtbl[token] == "CODE"):
                            self.CompileCodeBlockRef(token)
                        elif (self.symtbl[token] == "CONST"):
                            self.CompileNumber(str(self.consts[token]))  #  Compile a constant reference
                        elif (self.symtbl[token] == "STR"):
                            self.CompileStringConstantRef(token)    #  Compile a reference to a string constant
                        elif (self.symtbl[token] == "KWD"):
                            self.keywords[token]()                  #  Compile a keyword
                        elif (self.symtbl[token] == "LIB"):
                            if (self.functionAddress):
                                #  Push the address of the library routine
                                self.fasm.append(["","lda", "#<"+token])
                                self.fasm.append(["","ldx", "#>"+token])
                                self.fasm.append(["","jsr", "push"])
                                self.functionAddress = False
                            else:
                                #  Compile a library word call
                                self.CompileLibraryRef(token)           #  Compile a library word reference
                        else:
                            self.Error("Unknown symbol table type: " + str(token) + ", type = " + \
                                       str(self.symtbl[token]) + ", function = " + f)
                    elif (self.isNumber(token)):
                        self.CompileNumber(token)
                    else:
                        self.Error("Unknown token: " + str(token) + ", function = " + f)
                self.fasm.append(["","rts",""])  #  Ending RTS instruction


    #-------------------------------------------------
    #  pp
    #
    def pp(self, f, t):
        """Write an instruction to the assembly output file."""
        
        f.write(t[0])
        f.write("\t")
        f.write(t[1])
        f.write(" ")
        f.write(t[2])
        f.write("\n")
        
        
    #-------------------------------------------------
    #  AssemblyOutput
    #
    def AssemblyOutput(self):
        """Generate the output assembly code."""
         
        #  Check for a main function
        if not self.symtbl.has_key('main'):
            self.Error("No main function defined.")
        if (self.symtbl["main"] != "FUNC"):
            self.Error("No main function defined.")

        #  Open the output assembly source code file
        f = open(self.outname + ".s", "w")
         
        #  Write the header
        for s in HEADER:
            f.write("; "+s+"\n")
         
        #  Origin
        if (self.sys):
            self.org = 0x2000
        self.pp(f, ["","*=","$%04X" % self.org])
        f.write("\n")
        
        #  Library equates
        for s in EQUATES:
            self.pp(f, [s,"=","$%04X" % EQUATES[s]])
        f.write("\n")
        
        #  Variables
        offset = self.VARTOP
        for v in self.vars:
            offset -= self.vars[v]
            self.pp(f, [self.names[v], "=", "$%04X" % offset])
        f.write("\n")
        
        #  Main call
        self.pp(f, ["", "lda", "#0"])
        self.pp(f, ["", "sta", "sp"])  #  zero stack pointer
        self.pp(f, ["", "jmp", self.names["main"]])
        f.write("\n")

        #  Data blocks
        for s in self.dasm:
            self.pp(f, s)
        f.write("\n")

        #  Code blocks
        for s in self.casm:
            self.pp(f, s)
        f.write("\n")
        
        #  String constants
        for s in self.str:
            self.pp(f, [self.names[s], ".byte", str(len(self.str[s])-2) +","+ self.str[s]+",$9B"])
        f.write("\n")
        
        #  Dependencies
        for s in self.dependencies:
            g = open(LIB_DIR+s+".s", "r")
            f.write(g.read())
            g.close()
        f.write("\n")
        
        #  Library routines
        for s in self.lib:
            g = open(LIB_DIR+LIBRARYMAP[s]+".s", "r")
            f.write(g.read())
            g.close()
        f.write("\n")
        
        #  Functions
        for s in self.fasm:
            self.pp(f, s)
        f.write("\n")

        # end label
        f.write("_end\n")
        f.close()
        
    
    #-------------------------------------------------
    #  ConvertToAppleII
    #
    def ConvertToAppleII(self):
        """Convert the binary output file to an Apple II
           text file of EXEC-ing into memory."""
           
        #  Get the binary data
        f = open(self.outname + ".bin", "rb")
        d = f.read()
        f.close()
        
        #  Write to a new output file
        f = open(self.outname + ".txt", "w")
        f.write("CALL -151")
        
        #  Write each byte
        i = 0
        while (i < len(d)):
            if ((i % 8) == 0):
                f.write("\n%04X: " % (self.org + i))
            f.write("%02X " % ord(d[i]))
            i += 1
        
        #  Close the file
        if (self.sys):
            f.write("\nBSAVE %s, A%d, L%d, TSYS\n" % (self.outname.upper(), self.org, len(d)))
        else:
            f.write("\nBSAVE %s, A%d, L%d\n" % (self.outname.upper(), self.org, len(d)))
    
    
    #-------------------------------------------------
    #  BlockToTrackSector
    #
    def BlockToTrackSector(self, blk):
        """Convert a block number to the corresponding track and
           sector numbers."""
        
        trk = int(blk/8)
        sec = ([[0,14],[13,12],[11,10],[9,8],[7,6],[5,4],[3,2],[1,15]])[blk % 8]
        return [trk, sec]
    
    
    #-------------------------------------------------
    #  TrackSectorOffset
    #
    def TrackSectorOffset(self, trk, sec):
        """Convert a given track and sector to an offset in
           the disk image."""
        
        return 256*(16*trk + sec)
    
    
    #-------------------------------------------------
    #  ReadBlock
    #
    def ReadBlock(self, dsk, blk):
        """Return a 512 byte block from dsk at blk."""
        
        #  Get the track and sectors for this block
        trk, sec = self.BlockToTrackSector(blk)
        
        #  Get the offsets
        off1 = self.TrackSectorOffset(trk, sec[0])
        off2 = self.TrackSectorOffset(trk, sec[1])
        
        #  Get the 256 bytes at each of these locations
        #  as a single list and return it
        return dsk[off1:(off1+256)] + dsk[off2:(off2+256)]
        
    
    #-------------------------------------------------
    #  WriteBlock
    #
    def WriteBlock(self, dsk, blk, data):
        """Write 512 bytes of data to dsk at block blk."""
        
        #  Data must be exactly 512 long
        if (len(data) != 512):
            self.Error("Illegal block length.")
        
        #  Get the track and sectors for this block
        trk, sec = self.BlockToTrackSector(blk)
        
        #  Get the points into the dsk image
        off1 = self.TrackSectorOffset(trk, sec[0])
        off2 = self.TrackSectorOffset(trk, sec[1])
        
        #  Update the 256 values at off1 with the first
        #  256 values of data and the values at off2 with
        #  the next 256 values of data
        for i in xrange(256):
            dsk[off1+i] = data[i]
            dsk[off2+i] = data[i+256]


    #-------------------------------------------------
    #  Date
    #
    def Date(self):
        """Return the date in ProDOS format."""

        t = time.localtime(time.time())
        dlow = ((t[1] & 0x07) << 5) | t[2]
        dhigh = ((t[0] - 2000) << 1) | ((t[1] & 0x08) >> 3)
        return [dlow, dhigh]


    #-------------------------------------------------
    #  Time
    #
    def Time(self):
        """Return the time in ProDOS format."""

        t = time.localtime(time.time())
        return [t[4], t[3]]


    #-------------------------------------------------
    #  MarkBlock
    #
    def MarkBlock(self, blk, blocknum):
        """Mark blocknum as being used."""

        #  Byte number containing blocknum
        bytenum = blocknum / 8
        
        #  Bit number containing blocknum
        bitnum = 7 - (blocknum % 8)
        
        #  Set to used
        blk[bytenum] = blk[bytenum] & ~(1 << bitnum)
    
    
    #-------------------------------------------------
    #  ConvertToDSK
    #
    def ConvertToDSK(self):
        """Convert the binary output file to a .dsk file."""
        
        #  Get the binary data
        f = open(self.outname + ".bin", "rb")
        d = f.read()
        f.close()
        data = []
        for c in d:
            data.append(ord(c))
    
        #  Read the blank disk image file as list of bytes.
        #  It is assumed that this file is a blank ProDOS 140k 
        #  floppy image stored in DOS 3.3 order.
        f = open(DISK_IMAGE, "rb")
        t = f.read()
        f.close()
        dsk = []
        for c in t:
            dsk.append(ord(c))

        #
        #  Create the directory entry for this file
        #

        #  File storage type and name length
        blk = self.ReadBlock(dsk, 2)
        offset = 0x2b  #  Offset to start of second directory entry
        if (len(data) <= 512):
            blk[offset] = 0x15  #  seedling file
            seedling = True
        else:
            blk[offset] = 0x25  #  sapling file
            seedling = False

        #  File name, always "A.OUT"
        blk[offset+1] = ord("A")
        blk[offset+2] = ord(".")
        blk[offset+3] = ord("O")
        blk[offset+4] = ord("U")
        blk[offset+5] = ord("T")

        #  File type
        if (self.sys):
            blk[offset+0x10] = 255
        else:
            blk[offset+0x10] = 6

        #  Key pointer, block 7
        blk[offset+0x11] = 7
        blk[offset+0x12] = 0

        #  File size
        blocks = int(math.ceil(len(data)/512.0))
        if (blocks == 0):
            blocks = 1
        blk[offset+0x13] = blocks % 256
        blk[offset+0x14] = blocks / 256
        blk[offset+0x15] = len(data) % 256
        blk[offset+0x16] = len(data) / 256
        blk[offset+0x17] = 0

        #  Date and time
        dlow, dhigh = self.Date()
        blk[offset+0x18] = dlow
        blk[offset+0x19] = dhigh
        m, h = self.Time()
        blk[offset+0x1a] = m
        blk[offset+0x1b] = h

        #  ProDOS versions
        blk[offset+0x1c] = 0
        blk[offset+0x1d] = 0

        #  Access code
        blk[offset+0x1e] = 0xc3

        #  Aux type code (program start in memory)
        blk[offset+0x1f] = self.org % 256
        blk[offset+0x20] = self.org / 256

        #  Last accessed time
        blk[offset+0x21] = dlow
        blk[offset+0x22] = dhigh
        blk[offset+0x23] = m
        blk[offset+0x24] = h

        #  Key block for directory
        blk[offset+0x25] = 2
        blk[offset+0x26] = 0

        #  Number of files in this directory
        blk[0x25] = blk[0x25] + 1

        #  Write the block
        self.WriteBlock(dsk, 2, blk)

        #
        #  Index block
        #
        blk = self.ReadBlock(dsk, 7)
        
        if (seedling):
            #  Write the data
            i = 0
            while (i < len(data)):
                blk[i] = data[i]
                i += 1
            #  Block used
            used = [7]
        else:
            #  Reserve the necessary number of blocks
            used = []
            for i in xrange(blocks):
                used.append(i+8)
            j = 0
            for i in used:
                blk[j] = i % 256
                blk[j+256] = i / 256
                j += 1

        #  Write the index block
        self.WriteBlock(dsk, 7, blk)

        #  Write the file data if not a seedling file
        if (not seedling):
            #  Pad data to a multiple of 512
            data = data + [0]*(512 - (len(data) % 512))
            j = 0
            for b in used:
                blk = self.ReadBlock(dsk, b)
                for i in xrange(512):
                    blk[i] = data[j+i]
                self.WriteBlock(dsk, b, blk)
                j += 512
            
            #  Include block 7, the index block, so it will
            #  be marked as used in the volume bitmap
            used.append(7)

        #
        #  Volume bitmap updates
        #
        blk = self.ReadBlock(dsk, 6)
        for i in used:
            self.MarkBlock(blk, i)
        self.WriteBlock(dsk, 6, blk)

        #
        #  Write the entire image to disk
        #
        f = open(self.outname + ".dsk", "wb")
        for i in dsk:
            f.write(chr(i))
        f.close()
        
        
    #-------------------------------------------------
    #  ConvertToR65
    #
    def ConvertToR65(self):
        """Make an r65 file."""

        #  Get the binary data
        f = open(self.outname + ".bin", "rb")
        d = f.read()
        f.close()
        data = []
        for c in d:
            data.append(ord(c))
    
        #  Create the output file
        f = open(self.outname + ".r65", "wb")
        f.write(chr(self.org % 256))
        f.write(chr(self.org / 256))
        for c in data:
            f.write(chr(c))
        f.close()

    #-------------------------------------------------
    #  ConvertToAtariCom
    #
    def ConvertToCom(self):
        """Make an Atari COMpound file."""

        #  Get the binary data
        f = open(self.outname + ".bin", "rb")
        d = f.read()
        f.close()
        data = []
        for c in d:
            data.append(ord(c))

        #  Create the output file
        f = open(self.outname + ".com", "wb")
        f.write(chr(0xff)) # compound marker $FFFF
        f.write(chr(0xff))
        f.write(chr(self.org % 256))  # startaddress
        f.write(chr(self.org / 256))
        f.write(chr((self.org + len(data)+1)% 256))  # endaddress
        f.write(chr((self.org + len(data)+1)/ 256))

        for c in data:
            f.write(chr(c))

        f.write(chr(0xff))
        f.write(chr(0xff))
        f.write(chr(0xe0)) # RUNAD $02E0
        f.write(chr(0x02))
        f.write(chr(0xe1))
        f.write(chr(0x02))
        f.write(chr(self.org % 256)) # startaddress
        f.write(chr(self.org / 256))

        f.close()


    #-------------------------------------------------
    #  GenerateFinalOutput
    #
    def GenerateFinalOutput(self):
        """Create the final output file."""
        
        #  Assemble the .s file
        if (self.outtype != "asm"):
            rc = os.system(ASSEMBLER + " -r " + self.outname + ".s")
            if (rc != 0):
                self.Error("Error during assembly!")
                
        #  Convert to desired output file format
        if (self.outtype == "bin"):
            return
        elif (self.outtype == "a2t"):
            self.ConvertToAppleII()
        elif (self.outtype == "dsk"):
            self.ConvertToDSK()
        elif (self.outtype == "r65"):
            self.ConvertToR65()
        elif (self.outtype == "atari"):
            self.ConvertToCom()


    #-------------------------------------------------
    #  Compile 
    #
    def Compile(self):
        """Compile the files on the command line."""

        self.funcName = "$MAIN$"      #  Name of currently compiling function
        self.Token = "<na>"           #  Currently compiling token
        self.VARTOP = VARTOP          #  Variable starting address
        self.cmdOrigin = -1           #  -org value, if any
        self.counter = 0              #  Start labels from zero
        self.stringCount = 0          #  String constant name counter
        self.symtbl = {}              #  Ready the symbol table
        self.names = {}               #  Map names to labels
        self.dependencies = []        #  Library word dependencies
        self.functionAddress = False  #  If true, push a function address
        self.referenced = {}          #  True if that function referenced by another
                                      #  only functions actually referenced are compiled
        
        self.ParseCommandLine()       #  Parse the command line arguments
        self.Keywords()               #  Put all keywords into the symbol table
        self.LoadFiles()              #  Load all source code into self.src
        self.Tokenize()               #  Break up into tokens in self.tokens
        self.SetOrigin()              #  Determine the origin for the output code
        self.Declarations()           #  Find all variables and constants
        self.ParseDataBlocks()        #  Get all data blocks and their data
        self.ParseCodeBlocks()        #  Get all code blocks
        self.ParseFunctions()         #  Get all the functions and their tokens
        self.TagFunctions()           #  Tag used functions
        self.LocateStringConstants()  #  Locate string constants in functions
        self.LibraryRoutines()        #  Determine all library routines used
        self.CompileDataBlocks()      #  Generate assembly code for all data blocks
        self.CompileCodeBlocks()      #  Generate assembly code for all code blocks
        self.CompileFunctions()       #  Generate assembly code for all functions
        self.AssemblyOutput()         #  Output all assembly code and assemble it
        self.GenerateFinalOutput()    #  Convert the assembler output into the final desired format


    #--------------------------------------------------
    #  __init__
    #
    def __init__(self):
        """Build the compiler object."""
        
        #  Help message
        if (len(sys.argv) == 1):
            print "\nSPL Compiler (" + LAST_MOD + ")\n"
            print "Use: spl file1 [file2 ...] [-o outbase] [-t type] [-sys] [-org n] [-var m] " + \
                  "[-stack p] [-prodos]\n"
            print "Where:\n"
            print "    file1     =  first source code file (.spl extension assumed)"
            print "    file2...  =  additional source code files"
            print "    outbase   =  base file name for output files (NO extension)"
            print "    type      =  output file type:"
            print "                     bin = compiled binary"
            print "                     asm = assembly source only"
            print "                     a2t = text file for EXEC on Apple II"
            print "                     dsk = a disk file for Apple II emulators"
            print "                     r65 = source file for the r65 65C02 simulator"
            print "                   atari = compound file for Atari XL/XE DOS"
            print "    sys       =  create a ProDOS system file (-t a2t and -t dsk only)"
            print "    org       =  set the origin address to n"
            print "    var       =  set the VARTOP value (variables grow down from here)"
            print "    stack     =  set the stack address (up to 256 bytes)"
            print "    prodos    =  set VARTOP to allow room for 3 file buffers (Apple II)\n"
            sys.exit(1)
            
        #  Otherwise, compile the files
        self.Compile()
        

#  Run if not imported
if __name__ == '__main__':
    app = SPLCompiler()

#
#  end spl.py
#