SPL#
a Forth-style concatenative stack language. The compiler is (currently) written in Python and emits 6502 assembly code.
Originaly created by Ron Kneusel 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
#