mirror of
https://github.com/rzzzwilson/pymlac.git
synced 2025-06-10 09:32:41 +00:00
322 lines
8.0 KiB
Python
Executable File
322 lines
8.0 KiB
Python
Executable File
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
An assembler for the Imlac simulator.
|
|
|
|
Usage: pyasm [ -h ] [ -l <listfile> ] [ -o <outputfile> ] <asmfile>
|
|
|
|
Where <asmfile> is the file to assemble,
|
|
<outputfile> is the output PTP file
|
|
<listfile> is the optional listing file
|
|
|
|
If <outputfile> is not specified the output filename is the input
|
|
<asmfile> with any extension removed and a .ptp axtenstion added.
|
|
"""
|
|
|
|
"""
|
|
The basic structure of the assembler:
|
|
0. Read all file lines into memory
|
|
1. Create ORG blocks
|
|
2. Create CODE blocks from ORG blocks (assemble() function)
|
|
3. Check for undefined things in the symbol table
|
|
4. Allocate addresses to literal CODE blocks
|
|
5. Fix relative addresses in literal blocks
|
|
6. Backpatch all code blocks
|
|
7. Emit PTP data
|
|
"""
|
|
|
|
import sys
|
|
import os
|
|
import string
|
|
import getopt
|
|
|
|
######
|
|
# Globals
|
|
######
|
|
|
|
# the input assembler filename
|
|
AsmFile = None
|
|
|
|
# the output listing file
|
|
ListFile = None
|
|
ListFileHandle = None # open listing file
|
|
|
|
# the output PTP file
|
|
OutputFile = None
|
|
OutputFileHandle = None # open output file
|
|
|
|
# the program start address (optional)
|
|
StartAddr = None
|
|
|
|
# the symbol table
|
|
# {<name>: [<value>, <line#>], ... }
|
|
SymTable = {}
|
|
|
|
# the backpatch list
|
|
# [[symname, coderef, offset], [symname, coderef, offset], ... ]
|
|
BackpatchList = []
|
|
|
|
######
|
|
# Mostly constant stuff
|
|
######
|
|
|
|
# the output PTP filename extension
|
|
PTPExtension = '.ptp'
|
|
|
|
# the output listing filename extension
|
|
ListFileExtension = '.lst'
|
|
|
|
|
|
def usage(msg=None):
|
|
"""Print usage and optional error message."""
|
|
if msg:
|
|
print('*'*60)
|
|
print(msg)
|
|
print('*'*60)
|
|
print(__doc__)
|
|
|
|
def error(msg):
|
|
"""Print error message and stop."""
|
|
|
|
print(msg)
|
|
sys.exit(10)
|
|
|
|
def str2int(s):
|
|
"""Convert string to numeric value.
|
|
|
|
s numeric string (decimal or octal)
|
|
|
|
Returns the numeric value.
|
|
"""
|
|
|
|
base = 10
|
|
if s[0] == '0':
|
|
base = 8
|
|
|
|
try:
|
|
value = int(s, base=base)
|
|
except:
|
|
return None
|
|
|
|
return value
|
|
|
|
def list(code, addr, lnum, line):
|
|
"""Generate one line of listing file.
|
|
|
|
code is the word of generated code
|
|
addr is the address of the generated code
|
|
lnum file line number
|
|
line complete text of the line of assembler
|
|
"""
|
|
|
|
code_str = '%06o' % code if code else ''
|
|
addr_str = '%04o' % addr if addr else ''
|
|
|
|
ListFileHandle.write('%6s %4s %04d: %s\n'
|
|
% (code_str, addr_str, lnum, line))
|
|
ListFileHandle.flush()
|
|
|
|
def assemble_line(lnum, opcode, addr):
|
|
"""Assemble one line of code.
|
|
|
|
lnum source file line number
|
|
opcode opcode, uppercase
|
|
addr address expression, uppercase
|
|
|
|
Returns the 16bit word value, with possibly the backpatch list
|
|
updated.
|
|
"""
|
|
|
|
return 1025
|
|
|
|
def assemble_oblock(oblock):
|
|
"""Assemble one org block to a code block.
|
|
|
|
An org block: [(lnum, line, label, opcode, addr), ...]
|
|
A code block: [address, [word, word, word, ...]]
|
|
"""
|
|
|
|
cblock = [None, []]
|
|
|
|
# if we get an empty block, do nothing
|
|
if len(oblock) == 0:
|
|
return cblock
|
|
|
|
# assemble the org block
|
|
address = None # '.' value
|
|
for (lnum, line, label, opcode, addr) in oblock:
|
|
print("lnum=%d, line='%s', label='%s', opcode='%s', addr'%s'" % (lnum, line, label, opcode, addr))
|
|
|
|
# if no code, just list it
|
|
if label is None and opcode is None:
|
|
list(None, None, lnum, line)
|
|
else:
|
|
# we have some code to generate
|
|
if label:
|
|
# we have a label, so a new symbol
|
|
label_upper = label.upper()
|
|
# {<name>: [<value>, <line#>], ... }
|
|
if label_upper in SymTable:
|
|
prev_lnum = SymTable[label_upper][1]
|
|
error("Label '%s' define twice, lines %d and %d."
|
|
% (label, prev_lnum, lnum))
|
|
SymTable[label_upper] = [address, lnum]
|
|
if opcode:
|
|
opcode_upper = opcode.upper()
|
|
if opcode == 'ORG':
|
|
address = str2int(addr)
|
|
code = None
|
|
cblock = [address, []]
|
|
else:
|
|
code = assemble_line(lnum, opcode, addr)
|
|
print('cblock=%s' % str(cblock))
|
|
cblock[1].append(code)
|
|
list(code, address, lnum, line)
|
|
|
|
def assemble_org_blocks(blocks):
|
|
"""Assemble org blocks producing a code blocks list."""
|
|
|
|
code_block = []
|
|
|
|
# for each org block, assemble to code block
|
|
for oblock in blocks:
|
|
code_block.append(assemble_oblock(oblock))
|
|
|
|
return code_block
|
|
|
|
def next_symbol(line):
|
|
"""Return next symbol and line remainder."""
|
|
|
|
fields = string.split(line, maxsplit=1)
|
|
if len(fields) != 2:
|
|
fields.append('')
|
|
|
|
return fields
|
|
|
|
def split_fields(line):
|
|
"""Split one ASM line into fields: label, opcode, address."""
|
|
|
|
if not line:
|
|
return (None, None, None)
|
|
|
|
# check for the label
|
|
label = None
|
|
if line and line[0] not in (' ', ';'):
|
|
(label, line) = next_symbol(line)
|
|
|
|
# get opcode
|
|
opcode = None
|
|
if line and line[0] != ';':
|
|
(opcode, line) = next_symbol(line)
|
|
|
|
# get address
|
|
address = None
|
|
if line and line[0] != ';':
|
|
(address, line) = next_symbol(line)
|
|
|
|
return (label, opcode, address)
|
|
|
|
def split_orgs(asm_lines):
|
|
"""Split ASM lines into ORG blocks (one or more).
|
|
|
|
Error if no ORG pseudo-opcode found.
|
|
"""
|
|
|
|
result = []
|
|
block = []
|
|
|
|
for (lnum, line) in enumerate(asm_lines):
|
|
(label, opcode, addr) = split_fields(line)
|
|
if opcode:
|
|
if opcode.lower() == 'org':
|
|
if block:
|
|
# save previous ORG block
|
|
result.append(block)
|
|
block = []
|
|
if str2int(addr) is None:
|
|
error('Line %d: %s\nORG pseudo-opcode without an address?'
|
|
% (lnum, line))
|
|
block.append((lnum+1, line, label, opcode, addr))
|
|
|
|
if block:
|
|
# save previous ORG block
|
|
result.append(block)
|
|
block = []
|
|
|
|
return result
|
|
|
|
def assemble_file():
|
|
"""Assemble the file and produce listing, output files."""
|
|
|
|
# read all of ASM file into memory, strip \n, etc
|
|
with open(AsmFile, 'rb') as fd:
|
|
asm_lines = fd.readlines()
|
|
asm_lines = [line.rstrip() for line in asm_lines]
|
|
|
|
print('asm_lines=\n%s' % '\n'.join(asm_lines))
|
|
|
|
org_blocks = split_orgs(asm_lines)
|
|
print('org_blocks=%s' % str(org_blocks))
|
|
|
|
code_blocks = assemble_org_blocks(org_blocks)
|
|
print('code_blocks=%s' % str(code_blocks))
|
|
print('SymTable=%s' % str(SymTable))
|
|
|
|
def main():
|
|
"""The assembler."""
|
|
|
|
global AsmFile, ListFile, OutputFile
|
|
global ListFileHandle, OutputFileHandle
|
|
|
|
# handle the options
|
|
try:
|
|
(opts, args) = getopt.gnu_getopt(sys.argv, "hl:o:",
|
|
["help", "list=", "output="])
|
|
except getopt.GetoptError:
|
|
usage()
|
|
sys.exit(10)
|
|
|
|
ListFile = None
|
|
OutputFile = None
|
|
|
|
for opt, arg in opts:
|
|
if opt in ('-h', '--help'):
|
|
usage()
|
|
sys.exit(0)
|
|
elif opt in ('-l', '--list'):
|
|
ListFile = arg
|
|
elif opt in ('-o', '--output'):
|
|
OutputFile = arg
|
|
|
|
if len(args) != 2:
|
|
usage()
|
|
sys.exit(10)
|
|
|
|
# get ASM filename and make sure it exists
|
|
AsmFile = args[1]
|
|
try:
|
|
f = open(AsmFile, 'rb')
|
|
except IOError:
|
|
print("Sorry, can't find file '%s'" % AsmFile)
|
|
sys.exit(10)
|
|
f.close()
|
|
|
|
if OutputFile is None:
|
|
(path, ext) = os.path.splitext(AsmFile)
|
|
OutputFile = path + PTPExtension
|
|
OutputFileHandle = open(OutputFile, 'wb')
|
|
|
|
if ListFile is None:
|
|
(path, ext) = os.path.splitext(AsmFile)
|
|
ListFile = path + ListFileExtension
|
|
ListFileHandle = open(ListFile, 'wb')
|
|
|
|
print('ListFile=%s, OutputFile=%s, AsmFile=%s'
|
|
% (str(ListFile), str(OutputFile), str(AsmFile)))
|
|
|
|
assemble_file()
|
|
|
|
if __name__ == '__main__':
|
|
main()
|