1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00
Files
rzzzwilson.pymlac/pyasm/pyasm
2016-01-23 20:34:48 +07:00

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()