pta/ptasm.py

494 lines
16 KiB
Python
Executable file

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright © 2014 Jeff Epler http://emergent.unpythonic.net
#
# This program is is licensed under a disjunctive dual license giving you
# the choice of one of the two following sets of free software/open source
# licensing terms:
#
# * GNU General Public License (GPL), version 2.0 or later
# * 3-clause BSD License
#
#
# The GNU GPL License:
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#
#
# The 3-clause BSD License:
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials
# provided with the distribution.
#
# * Neither the name of Mesa Electronics nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
#
# Disclaimer:
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import getopt
import os
import sys
import pta
import collections
import struct
import re
from pta.grammar import *
OptArgs = Optional(Regex(r"([^;'\\]|\\.|'\\.'|'[^\\']')+")).setResultsName("args")
OptComment = Regex(r"\s*(;.*)?")
Mnemonic = Identifier.copy().setResultsName("mnemonic")
Directive = Word( ".", srange("[a-zA-Z0-9_]") ).setResultsName("pseudoop")
word_re = re.compile(r"[.a-zA-Z_][a-zA-Z0-9_]*\b")
class Instruction:
def __init__(self, op, args):
self.op = op
self.args = args
def __str__(self): return "Instruction(%r, %r)" % (self.op, self.args)
def length(self): return 1
def assemble(self): return 0
class Pseudo:
def __init__(self, op, args):
self.op = op
self.attr = "pseudo_" + self.op[1:]
self.args = args
def __str__(self): return "Pseudo(%r, %r)" % (self.op, self.args)
def __call__(self, assembler, no, label):
func = getattr(assembler, self.attr.lower())
return func(no, label, self.args)
class Macro:
def __init__(self, name, sig, filename, firstline):
self.name = name
self.filename = filename
self.firstline = firstline+1
self.sig = sig.strip().split(",")
if sig:
self.re = re.compile(r"\b(" + "|".join(self.sig) + r")\b")
print "RX", name, (r"\b(" + "|".join(self.sig) + r")\b")
else:
self.re = None
self.lines = []
self.append = self.lines.append
def subst(self, (filename, lno, line), d):
if self.re:
line = self.re.subn(lambda x: d.get(x.group(0)), line)[0]
return line
def __call__(self, assembler, args, target):
if self.sig:
values = args.strip().split(",")
lines = [self.subst(l, dict(zip(self.sig, values)))
for l in self.lines]
print "| Expansion of", self.name
for i in lines: print "||", i
assembler.preprocess(self.filename, target, lines, self.firstline)
class Assembler(object):
def __init__(self):
self.InstructionSet = collections.defaultdict(list)
self.irules = {}
Instr = ((Mnemonic + OptArgs).setResultsName("instruction")
.setRawParseAction(self.parse_instr))
Pseudo = ((Directive + OptArgs).setResultsName("pseudo")
.setRawParseAction(self.parse_pseudo))
IP = Optional(Instr ^ Pseudo)
self.Grammar = (
Pseudo ^
(White() + Instr + OptComment) ^
(Identifier.copy().setResultsName("label") + Optional(Colon) + IP + OptComment) ^
OptComment)
self.msfirst = False
def exprlist(self, exprs):
exprs = ValueList.parseString(exprs, True)
return map(self.value, exprs[::2])
def expr(self, expr):
return self.value(Value.parseString(expr, True).value)
def assignexpr(self, label, expr):
self.assign(label, self.expr(expr))
def pseudo_equ(self, no, label, expr):
print "equ", repr(label), repr(expr)
if no == 1: self.assignexpr(label, expr)
pseudo_set = pseudo_equ
def pseudo_org(self, no, label, expr):
value = self.expr(expr)
if no == 1 and label:
self.assign(label, value)
self.addr = value
def pseudo_word(self, no, label, expr):
if no == 1: return
values = self.exprlist(expr)
for addr, value in enumerate(values, self.addr):
if not self.msfirst:
value = struct.unpack("<H", struct.pack(">H", value))[0]
self.rom[addr] = value
self.addr += len(values)
pseudo_dw = pseudo_word
def pseudo_msfirst(self, no, label, expr):
self.msfirst = True
def pseudo_lsfirst(self, no, label, expr):
self.msfirst = False
def pseudo_echo(self, no, label, expr):
if expr.startswith('"'): print "ECHO:", expr
else: print self.expr(expr)
def pseudo_label(self, no, label, expr):
self.assign(expr, self.addr)
def pseudo_end(self, no, label, expr):
Empty().parseString(expr, True)
pass # should touch flow control
def makeirule(self, v):
if not v in self.irules:
self.irules[v] = MatchFirst([
InstructionPatternToParseElement(func, func.pattern)
for func in self.InstructionSet[v]])
return self.irules[v]
def parse_instr(self, s, l, toks):
try:
rule = self.makeirule(toks.mnemonic.lower())
except KeyError:
self.error(self.lno, "No instructions matching %r" % toks.mnemonic)
r = rule.parseString(toks.args, True)
return [r]
def parse_pseudo(self, s, l, toks):
return [Pseudo(toks.pseudoop, toks.args)]
def run(self, program, filename):
self.rom = {}
self.symbols = {}
self.macros = {}
self.nunique = 0
self.lstack = []
self.defines = {'LABEL': self.label, 'UNIQUE': self.unique, 'DUP': self.dup, 'SWAP': self.swap}
self.lines = []
self.preprocess(filename)
print "pass1"
self.pass1()
print "pass2"
self.pass2()
def ismacro(self, line):
parts = line.split(None, 1)
if not parts: return False
return parts[0] in self.macros
def expandmacro(self, line, target):
parts = line.split(None, 1)
if not parts: return False
macro = self.macros.get(parts[0])
print "expandmacro", repr(parts[0]), macro
if not macro: return False
macro(self, parts[1] if len(parts) > 1 else "", target)
return True
def create_define(self, line):
parts = line.split(None, 2)
if len(parts) != 3:
self.error("Missing substitution in #define")
self.defines[parts[1]] = parts[2].rstrip()
def expand_defines(self, line):
def replace(s):
s = s.group(0)
print "%s -> %s" % (s, self.defines.get(s, s))
s = self.defines.get(s, s)
if callable(s): s = s()
return s
return word_re.subn(replace, line)[0]
def unique(self):
self.nunique += 1
print ">> UNIQUE", self.lstack, len(self.lstack),
self.lstack.append("__u_%d" % self.nunique)
print self.lstack, len(self.lstack)
return ''
def dup(self):
print ">> DUP", self.lstack, len(self.lstack),
self.lstack.append(self.lstack[-1])
print self.lstack, len(self.lstack)
return ''
def swap(self):
print ">> SWAP", self.lstack, len(self.lstack),
a = self.lstack.pop()
b = self.lstack.pop()
self.lstack.append(a)
self.lstack.append(b)
print self.lstack, len(self.lstack)
return ''
def label(self):
if self.lstack:
print ">> LABEL", self.lstack, len(self.lstack),
result = self.lstack.pop()
print self.lstack, len(self.lstack)
return result
else:
print "!!! pop from empty list"
return 'undefined'
def preprocess(self, filename, target = None, lines = None, firstline=1):
def basetarget((filename, lno, line)):
line = self.expand_defines(line)
print "%s:%d: %r" % (filename, lno, line)
self.lines.append((filename, lno,
self.Grammar.parseString(line, True)))
if not target: target = basetarget
if lines is None:
if filename == '-':
line = list(sys.stdin)
else:
with open(filename, "U") as f: lines = list(f)
for lno, line in enumerate(lines, firstline):
self.symbols['__filename__'] = filename
self.symbols['__lno__'] = lno
print "%s:%d: %s" % (filename, lno, line.rstrip())
line = line.rsplit(";", 1)[0]
if line.startswith("#include") or line.startswith(".include"):
self.preprocess(line.strip().split()[1], target)
continue
elif line.lower().startswith("#define"):
self.create_define(line)
print self.defines.keys()
continue
if line.startswith(".macro"):
parts = line.split(None, 2)
if len(parts) == 2:
name, sig = parts[1], ''
else:
_, name, sig = parts
self.macros[name] = mac = Macro(name, sig, filename, lno)
target = mac.append
elif line.startswith(".endm"):
self.target = basetarget
elif line.startswith(".end"):
return
elif self.expandmacro(line, target):
pass
else:
print "%s:%d: %s" % (filename, lno, line.rstrip())
target((filename, lno, line.rstrip()))
def value(self, expr):
if not expr: return 0
return eval(expr, self.symbols)
def assign(self, name, value):
print '# assign', name, repr(value)
self.symbols[name] = value
def error(self, lno, err):
print "%s:%d: %s" % (self.filename, lno, err)
raise SystemExit
def pushline(self, line):
self.flow.appendleft(line)
def pushlines(self, lines):
self.flow.extendleft(lines)
def flow_exit(self):
del self.flow
def flow_control(self):
self.flow = collections.deque(self.lines)
while self.flow:
filename, lno, line = self.flow.popleft()
self.symbols['__filename__'] = filename
self.symbols['__lno__'] = lno
yield line
@property
def lno(self):
return self.symbols['__lno__']
@lno.setter
def lno(self, v):
self.symbols['__lno__'] = v
@property
def passno(self):
return self.symbols['__passno__']
@property
def addr(self):
return self.symbols['__addr__']
@addr.setter
def addr(self, v):
self.symbols['__addr__'] = v
@property
def filename(self):
return self.symbols['__filename__']
@filename.setter
def filename(self, v):
self.symbols['__filename__'] = v
def pass1(self):
self.symbols['__passno__'] = 1
self.addr = 0
for line in self.flow_control():
if line.label:
self.assign(line.label, self.addr)
if line.pseudo:
line.pseudo[0](self, 1, line.label)
if line.instruction:
self.addr += line.instruction[0].length
def pass2(self):
self.symbols['__passno__'] = 2
self.addr = 0
for line in self.flow_control():
if line.pseudo:
line.pseudo[0](self, 2, line.label)
if line.instruction:
op = line.instruction[0].assemble(self)
if isinstance(op, int): op = [op]
print "## %04x" % self.addr, " ".join("%04x" % o for o in op)
for addr, opc in enumerate(op, self.addr):
self.rom[self.addr] = opc
self.addr += len(op)
print self.addr
def read_table(self, tabname):
def Insn(a, b, *args, **kw):
def inner(*toks):
if toks is None: return
return b(*args + (a,) + toks, **kw)
inner.func_name = a
a = a.split(None, 1)
if len(a) == 1: a, o = a[0], ''
else: a, o = a
inner.pattern = o
self.InstructionSet[a.lower()].append(inner)
resolved_tabname = resolve(tabname, os.curdir, os.path.dirname(pta.__file__), os.path.dirname(__file__))
ns = {'__name__': os.path.splitext(os.path.basename(tabname))[0],
'__file__': resolved_tabname,
'Insn': Insn, 'assembler': self, 'Value': self.value}
execfile(resolved_tabname, ns)
def key(inner):
pattern = inner.pattern
return -len(pattern), pattern.count("*"), pattern
def order(v): return sorted(v, key=key)
for k in sorted(self.InstructionSet.keys()):
self.InstructionSet[k] = order(self.InstructionSet[k])
self.irules.clear()
def resolve(fn, *args):
if os.sep in fn: return fn
for path in args:
fn1 = os.path.join(path, fn)
if os.path.exists(fn1): return fn1
return fn
def main():
tabname = "d8_tab.py"
opts, args = getopt.getopt(sys.argv[1:], "t:o:")
if not args:
filename = '-'
elif len(args) > 1:
raise SystemExit, "usage: %s [-t table] [filename]" % sys.argv[0]
else: filename = args[0]
for k, v in opts:
if k == '-t': tabname = v
if k == '-o': objname = v
if filename == '-': program = sys.stdin.read()
else: program = open(filename).read()
assembler = Assembler()
assembler.read_table(tabname)
assembler.run(program, filename)
max_insn = max(assembler.rom.keys())
rom = [assembler.rom.get(i) for i in range(max_insn + 1)]
for i in range(0, len(rom), 8):
print "%04x:" % i,
for b in rom[i:i+8]:
print "%04x" % (b or 0),
print
if objname:
with open(objname, "w") as f:
for i in range(0, len(rom), 8):
for b in rom[i:i+8]:
print >>f, "%04x" % (b or 0),
print >>f
if __name__ == '__main__': main()