276 lines
11 KiB
Python
276 lines
11 KiB
Python
from method import Argument, Method
|
|
from mode import Fundamental
|
|
from lex import Grammar, PatternRule, PatternMatchRule, RegionRule
|
|
import re
|
|
|
|
class CommentGrammar(Grammar): pass
|
|
|
|
comment_rule = RegionRule('comment', r'\((?:[ \t\n]|$)', CommentGrammar, r'\)(?:[ \t\n]|$)')
|
|
|
|
CommentGrammar.rules = [
|
|
comment_rule,
|
|
PatternRule('data', r'[^\(\) \t\n][^ \t\n]*|[\(\)][^ \t\n]+'),
|
|
PatternRule('spaces', r'[ \t]+'),
|
|
]
|
|
|
|
class TalGrammar(Grammar):
|
|
rules = [
|
|
PatternRule('spaces', '[ \t]+'),
|
|
comment_rule,
|
|
PatternRule('delimiter', r'[\[\]{}]'),
|
|
PatternRule('tal.inst', r'(BRK|LIT|INC|POP|DUP|NIP|SWP|OVR|ROT|EQU|NEQ|GTH|LTH|JMP|JCN|JSR|STH|LDZ|STZ|LDR|STR|LDA|STA|DEI|DEO|ADD|SUB|MUL|DIV|AND|ORA|EOR|SFT)2?k?r?(?![a-zA-z0-9_])'), # instructions
|
|
PatternRule('tal.defmacro', r'%[^ \t\n]+'), # macro-define
|
|
PatternRule('tal.pad', r'\|[^ \t\n]+'), # pad (absolute)
|
|
PatternRule('tal.pad', r'\$[^ \t\n]+'), # pad (relative)
|
|
PatternRule('tal.deflabel', r'@[^ \t\n]+'), # label-define
|
|
PatternRule('tal.defsublabel', r'&[^ \t\n]+'), # sublabel-define
|
|
PatternRule('tal.spacer', r'/[^ \t\n]+'), # sublabel-spacer
|
|
PatternRule('tal.number', r'#[0-9a-fA-F]+'), # literal number
|
|
PatternRule('tal.addr', r'\.[^/ \t\n]+'), # little addr (zero page)
|
|
PatternRule('tal.addr', r',[^/ \t\n]+'), # little addr (relative)
|
|
PatternRule('tal.addr', r';[^/ \t\n]+'), # little addr (absolute)
|
|
PatternRule('tal.addr', r':[^/ \t\n]+'), # raw addr
|
|
PatternRule('tal.addr', r'\'[^/ \t\n]+'), # raw char
|
|
PatternRule('tal.addr', r'\"[^/ \t\n]+'), # raw word
|
|
PatternMatchRule('x', r'(~)(.+)$', 'tal.include', 'tal.filename'),
|
|
PatternRule('tal.word', r'[^ \t\n]+'),
|
|
PatternRule('eol', '\n'),
|
|
]
|
|
|
|
# white is for delimiters, operators, numbers
|
|
default = ('default', 'default')
|
|
|
|
# magenta is for keywords/builtins
|
|
lo_magenta = ('magenta202', 'default')
|
|
hi_magenta = ('magenta414', 'default')
|
|
|
|
# red is for comments
|
|
lo_red = ('red300', 'default')
|
|
hi_red = ('red511', 'default')
|
|
|
|
# orange is for macro definitions, headers and constants
|
|
hi_orange = ('yellow531', 'default')
|
|
lo_orange = ('yellow520', 'default')
|
|
|
|
# yellow is for parts of macros
|
|
hi_yellow = ('yellow551', 'default')
|
|
lo_yellow = ('yellow330', 'default')
|
|
|
|
# green is for strings and characters
|
|
lo_green = ('green030', 'default')
|
|
hi_green = ('green050', 'default')
|
|
|
|
# cyan is for types
|
|
lo_cyan = ('cyan033', 'default')
|
|
hi_cyan = ('cyan155', 'default')
|
|
|
|
# blue is definitions, functions and some macros
|
|
lo_blue = ('blue113', 'default')
|
|
hi_blue = ('blue225', 'default')
|
|
|
|
class Talasm(Method):
|
|
'''Assemble the given .tal file'''
|
|
bname = '*Talasm*'
|
|
def run_pipe(self, w, args, switch, mname=None):
|
|
return w.application.run_pipe(args, w.buffer, self.bname, switch, mname)
|
|
def get_dest(self, w):
|
|
path = w.buffer.path
|
|
if path.endswith('.tal'):
|
|
return path[:-4] + '.rom'
|
|
else:
|
|
raise Exception('invalid path: %s' % path)
|
|
def _execute(self, w, **vargs):
|
|
path = w.buffer.path
|
|
dest = self.get_dest(w)
|
|
args = ['uxnasm', path, dest]
|
|
r = self.run_pipe(w, args, lambda x: x != 0, 'error')
|
|
b = w.application.get_buffer_by_name(self.bname)
|
|
b.orig_path = w.buffer.path
|
|
if r == 0: w.set_error(b.lines[-2])
|
|
|
|
class TalCheck(Talasm):
|
|
'''Check the syntax of the current .tal file'''
|
|
def get_dest(self, w):
|
|
return '/dev/null'
|
|
|
|
class Taldoc(Method):
|
|
'''View documentation'''
|
|
ops = {
|
|
# instructions
|
|
'BRK': ['Break', [[], []], None, 'halt the program'],
|
|
'LIT': ['Literal', [[], ['a']], None, 'push the next value onto the stack'],
|
|
'INC': ['Increment', [['a'] , ['b']], None, 'adds one to the top of the stack'],
|
|
'POP': ['Pop', [['a'], []], None, 'remove the top of the stack'],
|
|
'DUP': ['Duplicate', [['a'], ['a', 'a']], None, 'duplicate the top of the stack'],
|
|
'NIP': ['Nip', [['a', 'b'], ['b']], None, 'remove the second value (a)'],
|
|
'SWP': ['Swap', [['a', 'b'], ['b', 'a']], None, 'swap the top two stack values'],
|
|
'OVR': ['Over', [['a', 'b'], ['a', 'b', 'a']], None, 'duplicate the second value (a) to the top of the stack'],
|
|
'ROT': ['Rotate', [['a', 'b', 'c'], ['b', 'c', 'a']], None, 'rotate the top three values to the left'],
|
|
# logic
|
|
'EQU': ['Equal', [['a', 'b'], ['bool^']], None, 'push 01 if a == b; push 00 otherwise'],
|
|
'NEQ': ['Not Equal', [['a', 'b'], ['bool^']], None, 'push 01 if a != b; push 00 otherwise'],
|
|
'GTH': ['Greater Than', [['a', 'b'], ['bool^']], None, 'push 01 if a > b; push 00 otherwise'],
|
|
'LTH': ['Less Than', [['a', 'b'], ['bool^']], None, 'push 01 if a < b; push 00 otherwise'],
|
|
# control flow
|
|
'JMP': ['Jump', [['x'], []], None, 'modify the pc using addr'],
|
|
'JCN': ['Jump Conditional', [['bool^ addr'], []], None, 'if bool != 00, modify the pc using addr'],
|
|
'JSR': ['Jump Stash Return', [['addr'], []], [[], ['pc']], 'store pc onto return stack; modify pc using addr'],
|
|
'STH': ['Stash', [['a'], []], [[], ['a']], 'move the top of the stack to the return stack'],
|
|
# memory
|
|
'LDZ': ['Load Zero-Page', [['addr^'], ['val']], None, 'load value from first 256 bytes of memory onto the stack'],
|
|
'STZ': ['Store Zero-Page', [['val', 'addr^'], []], None, 'write top of stack into the first 256 bytes of memory'],
|
|
'LDR': ['Load Relative', [['addr^'], ['val']], None, 'load relative address onto the stack'],
|
|
'STR': ['Store Relative', [['val', 'addr^'], []], None, 'write top of stack to relative address'],
|
|
'LDA': ['Load Absolute', [['addr*'], ['val']], None, 'load absolute address onto the stack'],
|
|
'STA': ['Store Absolute', [['val', 'addr*'], []], None, 'write top of stack to absolute address'],
|
|
'DEI': ['Device In', [['addr^'], ['val']], None, 'load from the given device onto the stack'],
|
|
'DEO': ['Device Out', [['val', 'addr^'], []], None, 'write top of stack to the given device'],
|
|
# arithmetic
|
|
'ADD': ['Add', [['a', 'b'], ['a+b']], None, 'addition (a + b)'],
|
|
'SUB': ['Subtract', [['a', 'b'], ['a-b']], None, 'subtraction (a - b)'],
|
|
'MUL': ['Multiply', [['a', 'b'], ['a*b']], None, 'multiplication (a * b)'],
|
|
'DIV': ['Divide', [['a', 'b'], ['a/b']], None, 'division (a / b)'],
|
|
# bitwise
|
|
'AND': ['And', [['a', 'b'], ['a&b']], None, 'bitwise-and (a & b)'],
|
|
'ORA': ['Or', [['a', 'b'], ['a|b']], None, 'bitwise-or (a | b)'],
|
|
'EOR': ['Exclusive Or', [['a', 'b'], ['a^b']], None, 'bitwise-xor (a ^ b)'],
|
|
'SFT': ['Shift', [['a', 'b^'], ['c']], None, 'bitshift left (b >> 4) then right (b & 0xf)'],
|
|
}
|
|
inst_re = re.compile(r'^([A-Z]{3})(2?k?r?)$')
|
|
addr_dict = {
|
|
'.': 'zero-page address',
|
|
',': 'relative address',
|
|
';': 'absolute address',
|
|
':': 'raw address',
|
|
'\'': 'raw character',
|
|
'\"': 'raw word',
|
|
}
|
|
def find_macro(self, name, w):
|
|
toks = w.get_highlighter().tokens
|
|
target = '%' + name
|
|
found = False
|
|
start = None
|
|
end = None
|
|
defn = None
|
|
for line in toks:
|
|
for t in line:
|
|
if not found:
|
|
found = t.name == "tal.defmacro" and t.string == target
|
|
elif start is None:
|
|
if t.name == 'delimiter' and t.string == '{':
|
|
start = (t.x + 1, t.y)
|
|
elif end is None:
|
|
if t.name == 'delimiter' and t.string == '}':
|
|
end = (t.x, t.y)
|
|
break
|
|
if start is None or end is None:
|
|
return None
|
|
(sx, sy) = start
|
|
(ex, ey) = end
|
|
lines = w.buffer.lines
|
|
if sy == ey:
|
|
# 1 lines
|
|
return lines[sy][sx:ex]
|
|
elif sy == ey + 1:
|
|
# 2 lines
|
|
return lines[sy][sx:] + ' ' + lines[ey][:e.x]
|
|
else:
|
|
# 3+ lines
|
|
out = []
|
|
out.append(lines[sy][sx:])
|
|
out.extend(lines[sy+1:ey])
|
|
out.append(lines[ey][:ex])
|
|
return ' '.join(out)
|
|
def _execute(self, w, **vargs):
|
|
tok = w.get_token()
|
|
word = tok.string
|
|
if word is None:
|
|
w.set_error('no word selected')
|
|
return
|
|
m = self.inst_re.match(word)
|
|
if m and m.group(1) in self.ops:
|
|
prefix, suffix = m.groups()
|
|
glyph = '^'
|
|
name, ds, rs, desc = self.ops[prefix]
|
|
if 'r' in suffix:
|
|
rs, ds = ds, rs
|
|
if '2' in suffix:
|
|
glyph = '*'
|
|
if 'k' in suffix:
|
|
if ds is not None:
|
|
ds = [ds[0], ds[0] + ds[1]]
|
|
if rs is not None:
|
|
rs = [rs[0], rs[0] + rs[1]]
|
|
def mark(x):
|
|
if x.endswith('^') or x.endswith('*'):
|
|
return x
|
|
else:
|
|
return x + glyph
|
|
def stk(xss):
|
|
return ' -- '.join([' '.join([mark(x) for x in xs]) for xs in xss])
|
|
if rs is None:
|
|
msg = '%s (%s) %s: %s' % (word, stk(ds), name, desc)
|
|
elif ds is None:
|
|
msg = '%s {%s} %s: %s' % (word, stk(rs), name, desc)
|
|
else:
|
|
msg = '%s (%s) {%s} %s: %s' % (word, stk(ds), stk(rs), name, desc)
|
|
w.set_error(msg)
|
|
elif tok.name == 'tal.number':
|
|
n = int(word[1:], 16)
|
|
w.set_error('%r is a number (%d)' % (word, n))
|
|
elif tok.name == 'tal.defmacro':
|
|
w.set_error('%r is a macro definition' % word)
|
|
elif tok.name == 'tal.pad':
|
|
n = int(word[1:], 16)
|
|
if word[0] == '|':
|
|
w.set_error('%r is an absolute pad (%d)' % (word, n))
|
|
else:
|
|
w.set_error('%r is a relative pad (+%d)' % (word, n))
|
|
elif tok.name == 'tal.deflabel':
|
|
w.set_error('%r is a label definition' % word)
|
|
elif tok.name == 'tal.defsublabel':
|
|
w.set_error('%r is a sublabel definition' % word)
|
|
elif tok.name == 'tal.addr':
|
|
kind = self.addr_dict.get(word[0], 'unknown address')
|
|
w.set_error('%r is a %s' % (word, kind))
|
|
else:
|
|
defn = self.find_macro(word, w)
|
|
if defn is None:
|
|
w.set_error('%r is not recognized' % word)
|
|
else:
|
|
w.set_error('%r is a macro: %s' % (word, defn))
|
|
|
|
|
|
class Tal(Fundamental):
|
|
name = 'tal'
|
|
extensions = ['.tal']
|
|
grammar = TalGrammar
|
|
actions = [Taldoc, Talasm, TalCheck]
|
|
colors = {
|
|
'tal.addr': hi_cyan,
|
|
'tal.defmacro': hi_blue,
|
|
'tal.addr': hi_yellow,
|
|
'tal.inst': hi_magenta,
|
|
'tal.deflabel': hi_blue,
|
|
'tal.defsublabel': hi_cyan,
|
|
'tal.number': hi_green,
|
|
'tal.pad': hi_orange,
|
|
'tal.spacer': hi_orange,
|
|
'tal.include': lo_cyan,
|
|
'tal.filename': hi_cyan,
|
|
}
|
|
opentokens = ('delimiter',)
|
|
opentags = {'(': ')', '[': ']', '{': '}'}
|
|
closetokens = ('delimiter',)
|
|
closetags = {')': '(', ']': '[', '}': '{'}
|
|
_bindings = {
|
|
'talasm': ('C-c c', 'C-c C-c',),
|
|
'tal-check': ('C-c s',),
|
|
'taldoc': ('C-c p',),
|
|
'close-paren': (')',),
|
|
'close-brace': ('}',),
|
|
'close-bracket': (']',),
|
|
}
|
|
|
|
install = Tal.install
|