diff --git a/pyasm/pyasm b/pyasm/pyasm index ba662d5..12e30b2 100755 --- a/pyasm/pyasm +++ b/pyasm/pyasm @@ -70,6 +70,10 @@ CurrentLineNumber = None # Any undefined label Undefined = None +# buffer for blocked code +BlockBuffer = [] +BlockBufferStart = None + ###### # Mostly constant stuff ###### @@ -95,9 +99,13 @@ ZeroLeaderSize = 16 # dict mapping opcode to generated word, address opts, address mask & indirect allowed ###### -# helper function to generate N-bit mask +# helper function to generate N-bit mask, right justified def mask(n): - return (0xFFFF << n) & 0xFFFF + value = 0 + for _ in range(n): + value = (value << 1) + 1 + + return value OpcodeData = { 'LAW': ( 0004000, AYES, mask(11), False), @@ -201,6 +209,8 @@ OpcodeData = { 'DNOP': (0004000, ANO, 0, False), } +print('OpcodeData=%s' % str(OpcodeData)) + ###### # The papertape/teletype loader code ###### @@ -331,12 +341,6 @@ def usage(msg=None): print(__doc__) def error(msg): - """Print error message and stop.""" - - print(msg) - sys.exit(10) - -def syn_error(msg): """Print a syntax error and abort.""" print('-' * 80) @@ -378,17 +382,25 @@ def emit_loader(): for _ in range(ZeroLeaderSize): emit_byte(0) +def start_block(addr): + """Prepare next block to start at 'addr'""" + + BlockBufferStart = addr + def emit_block(): """Emit the current code block and reset the buffer.""" + global BlockBuffer, BlockBufferStart + # emit the block size and load address - code_block_size = len(CodeBlock) + code_block_size = len(BlockBuffer) emit_byte(code_block_size) - emit_word(CodeBlockStart) + for v in BlockBuffer: + emit_word(v) # emit the block & calculate checksum as we go checksum = 0 - for word in CodeBlock: + for word in BlockBuffer: checksum += word if checksum and ~WordMask: ++checksum @@ -399,8 +411,11 @@ def emit_block(): emit_word(checksum) # reset the code buffer - CodeBlock = [] - CodeBlockStart += code_block_size + BlockBuffer = [] + if BlockBufferStart: + BlockBufferStart += code_block_size + else: + BlockBufferStart = None def write_list(code, addr, lnum, line): """Generate one line of listing file. @@ -445,14 +460,14 @@ def eval_expr(expr): try: result = eval(expr, globs) except TypeError as e: - syn_error("ORG pseudo-opcode expression contains unsatisfied references") + error("ORG pseudo-opcode expression contains unsatisfied references") return None except NameError as e: Undefined = e.message if 'is not defined' in e.message: Undefined = e.message[len("name '"):-len("' is not defined")] - syn_error("ORG pseudo-opcode expression has '%s' undefined" % Undefined) - syn_error("ORG pseudo-opcode expression has an error") + error("ORG pseudo-opcode expression has '%s' undefined" % Undefined) + error("ORG pseudo-opcode expression has an error") return result @@ -460,19 +475,10 @@ def num_gen_words(opcode, addr): """Calculate number of words generated by this opcode.""" if opcode: - if opcode == 'EQU': - return 0 - if opcode == 'DATA': - return 1 -# if opcode == 'ASCII': -# return ? - if opcode == 'END': - return 0 - # we assume opcode will return 1 return 1 - else: - return 0 + + return 0 def pass_1(lines): """Do pass 1 of the assembly. @@ -483,40 +489,45 @@ def pass_1(lines): Returns False if there was an error. """ - global Dot, CurrentLineNumber, CurrentLine + global StartAddress + global Dot, CurrentLineNumber, CurrentLine, SymTable, SymTableLine + + # initialize things + Dot = None + Symtable = {} + SymTableLine = {} # for each line in the file - Dot = None for (lnum, line) in enumerate(lines): lnum += 1 # line numbers are 1-based CurrentLineNumber = lnum CurrentLine = line # get line fields - (label, opcode, addr) = split_fields(line) + (label, opcode, indirect, addr) = split_fields(line) if opcode: # we have an opcode, so code might be generated if opcode == 'ORG': if not addr or eval_expr(addr) is None: - syn_error("ORG pseudo-op has bad address") + error("ORG pseudo-op has bad address") return False Dot = eval_expr(addr) elif opcode == 'EQU': # no code, but we define a label if not label: - syn_error("EQU pseudo-op must have a label") + error("EQU pseudo-op must have a label") return False if not addr or eval_expr(addr) is None: - syn_error("EQU pseudo-op has bad value") + error("EQU pseudo-op has bad value") return False define_label(label, eval_expr(addr), lnum) elif opcode == 'BSS': # no code, but Dot moves if not addr or eval_expr(addr) is None: - syn_error("BSS pseudo-op has bad value") + error("BSS pseudo-op has bad value") return False if label: define_label(label, Dot, lnum) @@ -525,18 +536,23 @@ def pass_1(lines): elif opcode == 'DATA': # a single data word if not addr or eval_expr(addr) is None: - syn_error("BSS pseudo-op has bad value") + error("BSS pseudo-op has bad value") return False if label: define_label(label, Dot, lnum) Dot += 1 -# TODO: implement "label ASCII 'Abcde'", producing packed ASCII chars, possible label -# elif opcode == 'ASCII': -# pass + elif opcode == 'ASCII': + # an ASCII string, packed two bytes/word + ascii_len = len(addr) + ascii_words = ascii_len / 2 + if ascii_len % 2: + ascii_words += 1 + Dot += ascii_words elif opcode == 'END': - # TODO: get the start address, if any + # get the (optional) start address + StartAddress = eval_expr(addr) return True else: @@ -548,7 +564,7 @@ def pass_1(lines): elif label: # label but no code generated, just set label in symtab if label in SymTable: - syn_error("Label '%s' has already been defined" % label) + error("Label '%s' has already been defined" % label) return False define_label(label, Dot, lnum) @@ -570,24 +586,27 @@ def pass_2(lines): CurrentLine = line # get line fields - (label, opcode, addr) = split_fields(line) - print('#####: label=%s, opcode=%s, addr=%s' % (str(label), str(opcode), str(addr))) + (label, opcode, indirect, addr) = split_fields(line) + print('#####: label=%s, opcode=%s, indirect=%s, addr=%s' + % (str(label), str(opcode), str(indirect), str(addr))) if opcode: # we have an opcode, so code might be generated if opcode == 'ORG': if not addr or eval_expr(addr) is None: - syn_error("ORG pseudo-op has bad address") + error("ORG pseudo-op has bad address") return False + emit_block() # punch any accumulated code Dot = eval_expr(addr) + start_block(Dot) elif opcode == 'EQU': # no code, but we define a label if not label: - syn_error("EQU pseudo-op must have a label") + error("EQU pseudo-op must have a label") return False if not addr or eval_expr(addr) is None: - syn_error("EQU pseudo-op has bad value") + error("EQU pseudo-op has bad value") return False # check EQU value unchanged try: @@ -603,28 +622,39 @@ def pass_2(lines): elif opcode == 'BSS': # no code, but Dot moves if not addr or eval_expr(addr) is None: - syn_error("BSS pseudo-op has bad value") + error("BSS pseudo-op has bad value") return False + emit_block() # punch any accumalated code Dot += eval_expr(addr) + start_block(Dot) elif opcode == 'DATA': # a single data word if not addr or eval_expr(addr) is None: - syn_error("BSS pseudo-op has bad value") + error("BSS pseudo-op has bad value") return False + emit_word(eval_expr(addr)) Dot += 1 -# TODO: implement "label ASCII 'Abcde'", producing packed ASCII chars, possible label -# elif opcode == 'ASCII': -# pass + elif opcode == 'ASCII': + len_addr = len(addr) + for i in range(0, len_addr-1, 2): + emit_word(ord((addr[i]) << 8) + ord(addr[i+1])) + Dot += 1 + if len_addr % 2: + emit_word((ord(addr[-1]) << 8)) + Dot += 1 elif opcode == 'END': # get optional start address global StartAddress - StartAddress = None - if addr: - StartAddress = eval_expr(addr) + start_address = eval_expr(addr) + if start_address != StartAddress: + print('start_address=%s, StartAddress=%s' % (str(start_address), str(StartAddress))) + error("Pass 2 start address is different from pass 1, was %06o but now %06o" + % (StartAddress, start_address)) + StartAddress = start_address return True else: @@ -638,11 +668,11 @@ def pass_2(lines): error("Label '%s' has different value in pass 2.\n" "Was %06o, now %06o" % (label. old_dot, Dot)) - gen_code(lnum, line, label, label, opcode, addr) + gen_code(lnum, line, label, label, opcode, indirect, addr) Dot += 1 elif label: - # label but no code generated, just check Dot dor label unchanged + # label but no code generated, just check Dot for label unchanged if label in SymTable: dot = SymTable[label] if dot != Dot: @@ -656,18 +686,18 @@ def pass_2(lines): # check nothing after END if lnum - 1 > len(lines): - syn_error("Something after the 'END' pseudo-op!?") + error("Something after the 'END' pseudo-op!?") - -def gen_code(lnum, line, dot, label, opcode, addr): +def gen_code(lnum, line, dot, label, opcode, indirect, addr): """Assemble one line of code. - lnum source file line number - line the actual source line (for error reporting) - dot current address in the assembly - label optional label - opcode opcode, uppercase - addr address expression, uppercase + lnum source file line number + line the actual source line (for error reporting) + dot current address in the assembly + label optional label + opcode opcode, uppercase + indirect True if indirect flag found + addr address expression, uppercase Puts the assembled word into the punch buffer. """ @@ -675,21 +705,36 @@ def gen_code(lnum, line, dot, label, opcode, addr): print('gen_code: lnum=%d, line=%s, label=%s, opcode=%s, addr=%s' % (lnum, line, str(label), str(opcode), str(addr))) + # get instruction coding details try: (word, aok, mask, ind) = OpcodeData[opcode] except KeyError: error("%d: %s\nUnrecognized opcode '%s'" % (lnum, line, opcode)) + print('word=%06o, aok=%d, mask=%06o, ind=%s' % (word, aok, mask, str(ind))) value = eval_expr(addr) - print('addr=%s, value=%s' % (str(addr), str(value))) + if aok in (AYES, AOPT): + print('addr=%s, value=%s' % (str(addr), str(value))) - word_s = format(word, '016b') if word else '' + word_s = format(word, '016b') mask_s = format(mask, '016b') if mask else '' value_s = format(value, '016b') if value else '' print('word=%s, mask=%s, ind=%s, value=%s' % (word_s, mask_s, str(ind), value_s)) - return 1025 + # check if 'addr' has overflowed. add in if OK + if value: + if value & mask != value: + error("Address field overflow: %06o" % value) + word += value + + # if indirect and indirect OK, set high bit + if indirect and ind: + word += 0100000 + if not ind and indirect: + error("Indirect not allowed here") + + emit_word(word) def define_label(label, address, lnum): """Put 'label' into the symbol tables. @@ -746,20 +791,22 @@ def next_symbol(line): return fields def split_fields(line): - """Split one ASM line into fields: label, opcode, address. + """Split one ASM line into fields: label, opcode, indirect, address. - Returns a tuple: (label, opcode, address). - If label and opcode ar not None, uppercase the result string. + Returns a tuple: (label, opcode, indirect, address). + If label and opcode are not None, uppercase the result string. + If address is not None and is not a string, it's uppercased. + 'indirect' is either True or False. If a field is missing, return None for it. If the line is empty, return - (None, None, None). + (None, None, False, None). We take pains not to split the address field if it's something like ALPHA + 100 """ if not line: - return (None, None, None) + return (None, None, False, None) # check for the label label = None @@ -776,10 +823,11 @@ def split_fields(line): opcode = opcode.upper() # get address + indirect = False address = None if remainder and remainder[0] != ';': - # first, check for a string if remainder[0] in "'\"": + # it's a string delim = remainder[0] remainder = remainder[1:] ndx = remainder.find(delim) @@ -789,20 +837,22 @@ def split_fields(line): address = '"' + remainder[:ndx].strip() + '"' remainder = remainder[ndx+1:].strip() else: - # strip off any comment + # otherwise just an expression, strip off any indirect ndx = remainder.find(';') if ndx != -1: remainder = remainder[:ndx].strip() - address = remainder.strip() + if remainder[0] == '*': + indirect = True + remainder = remainder[1:] + address = remainder.strip().upper() remainder = None - address = address.upper() # check that remainder is empty or only a comment if remainder and remainder[0] != ';': error('Badly formed instruction:\n' '%d: %s' % (CurrentLineNumber, CurrentLine)) - return (label, opcode, address) + return (label, opcode, indirect, address) def assemble_file(): """Assemble the file and produce listing & output files."""