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 _execute(self, w, **vargs): path = w.buffer.path if path.endswith('.tal'): dest = path[:-4] + '.rom' else: raise Exception('invalid path: %s' % path) 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 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] 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'), 'taldoc': ('C-c p',), 'close-paren': (')',), 'close-brace': ('}',), 'close-bracket': (']',), } install = Tal.install