Assembler for Little Computer 3 (LC-3) in Python

Updated TrustyTony 5 Tallied Votes 2K Views Share

There is free tools to do the same, but I was curious to see what it takes to make one myself. For info see: http://en.wikipedia.org/wiki/LC-3

Gribouillis commented: Great work ! +14
Tcll commented: I doubt the free tool has a src written in a language I program in regularly ;) +2
""" Assembler for teaching processor LC3 (original version)
    Tools exist to do same job, but check how much effort it
    is to do in Python. Order of BRanch flags relaxed, BR without
    flags interpreted as BRnzv (allways).

    With verbosity on prints source instructions without label
    replacements (immediates and registers in place) to stdout.
"""

from __future__ import print_function
from array import array
import os
import sys

try:
    input = raw_input
except:
    pass

reg_pos = [9, 6, 0]
flags = {'n': 1 << 11, 'z': 1 << 10, 'p': 1 << 9}
memory = array('H', [0] * (1 << 16))

swap = True       # need to swap bytes

instruction_info = dict((
    ('ADD', 0b1 << 12),
    ('AND', 0b0101 << 12),
    ('BR', 0b0),
    ('GETC', (0b1111 << 12) + 0x20),
    ('HALT', (0b1111 << 12) + 0x25),
    ('IN', (0b1111 << 12) + 0x23),
    ('JMP', 0b1100 << 12),
    ('JMPT', (0b1100000 << 9) + 1),
    ('JSR', 0b01001 << 11),
    ('JSRR', 0b010000 << 9),
    ('LD', 0b0010 << 12),
    ('LDI', 0b1010 << 12),
    ('LDR', 0b0110 << 12),
    ('LEA', 0b1110 << 12),
    ('NOT', (0b1001 << 12) + 0b111111),
    ('OUT', (0b1111 << 12) + 0x21),
    ('PUTS', (0b1111 << 12) + 0x22),
    ('PUTSP', (0b1111 << 12) + 0x24),
    ('RET', 0b1100000111000000),
    ('RTI', 0b1000 << 12),
    ('RTT', 0b1100000111000001),
    ('ST', 0b0011 << 12),
    ('STI', 0b1011 << 12),
    ('STR', 0b0111 << 12),
    ('TRAP', 0b1111 << 12),
    ))

immediate = dict((
    ('ADD', 5),
    ('AND', 5),
    ('BR', 9),
    ('GETC', 0),
    ('HALT', 0),
    ('IN', 0),
    ('JMP', 0),
    ('JMPT', 0),
    ('JSR', 11),
    ('JSRR', 0),
    ('LD', 9),
    ('LDI', 9),
    ('LDR', 6),
    ('LEA', 9),
    ('NOT', 9),
    ('OUT', 0),
    ('PUTS', 0),
    ('PUTSP', 0),
    ('RET', 0),
    ('RTI', 0),
    ('RTT', 0),
    ('ST', 9),
    ('STI', 9),
    ('STR', 6),
    ('TRAP', 8),
    ('UNDEFINED', 0)
    ))

immediate_mask = dict()
for im in immediate:
    immediate_mask[im] = (1 << immediate[im]) - 1

instructions = instruction_info.keys()


regs = dict(('R%1i' % r, r) for r in range(8))
labels = dict()
label_location = dict()


def put_and_show(n):
    if n < 0: n = (1<<16) + n
    memory[pc] = n
    if verbose:
        print(get_mem_str(pc), end='')


def get_mem_str(loc):
    return 'x{0:04X}: {1:016b} {1:04x} '.format(loc, memory[loc])


def reg(s, n=1):
    return registers[s.rstrip(', ')] << reg_pos[n]


def undefined(data):
    raise ValueError('Undefined Instruction')


def valid_label(word):
    if word[0] == 'x' and word[1].isdigit():
        return False
    return (word[0].isalpha() and
            all(c.isalpha() or c.isdigit() or c == '_' for c in word))


def get_immediate(word, mask=0xFFFF):
    if (word.startswith('x') and
        all(n in '-0123456789abcdefgABCDEF' for n in word[1:]) and
        not '-' in word[2:]
        ):
        return int('0' + word, 0) & mask
    elif word.startswith('#'):
        return int(word[1:]) & mask
    else:
        try:
            return int(word) & mask
        except ValueError:
            return


def process_instruction(words):
    """ Process ready split words from line and parse the line
        use put_and_show to show the instruction line without
        label values

    """
    global orig, pc
    found = ''
    if not words or words[0].startswith(';'):
        if verbose:
            print(3 * '\t', end='')
        return
    elif '.FILL' in words:
        word = words[words.index('.FILL') + 1]
        try:
            put_and_show(int(word))
        except ValueError:
            value = get_immediate(word)
            if value is None:
                if word in label_location:
                    label_location[word].append([pc, 0xFFFF, 16])
                else:
                    label_location[word] = [[pc, 0xFFFF, 16]]
            else:
                memory[pc] = value

        if words[0] != '.FILL':
            labels[words[0]] = pc
        pc += 1
        return    
    elif '.ORIG' in words:
        orig = pc = int('0' + words[1]
                        if words[1].startswith('x')
                        else words[1], 0)
        if verbose:
            print(3 * '\t', end='')
        return
    elif '.STRINGZ' in words:
        if valid_label(words[0]):
            labels[words[0]] = pc
        else:
            print('Warning: no label for .STRINGZ in line for PC = x%04x:\n%s' % (pc, line))
        s = line.split('"')
        string1 = string = s[1]
        # rejoin if "  inside quotes
        for st in s[2:]:
            if string.endswith('\\'):
                string += '"' + st

        # encode backslash to get special characters
        backslash = False
        for c in string:
            if not backslash:
                if c == '\\':
                    if not backslash:
                        backslash = True
                        continue
                m = ord(c)
            else:
                if c in 'nblr':
                    m = ord(c) - 100
                else:
                # easiest to implement:
                # anything else escaped is itself (unlike Python)
                    m = ord(c)

                backslash = False

            put_and_show(m)
            if verbose:
                print(repr(chr(m)))
            pc += 1
        put_and_show(0)
        pc += 1
        return
    elif '.BLKW' in words:
        labels[words[0]] = pc
        value = get_immediate(words[-1])
        if value is None or value <= 0:
            raise ValueError('Bad .BLKW immediate: %s, %r' %
                              (words[-1], value))
        pc += value
        return

    ind = -1
    if words[0].startswith('BR'):
        ind = 0
    elif words[1:] and words[1].startswith('BR'):
        ind = 1
    if ind >= 0 and len(words[ind]) <= 5:
        if all(c in flags for c in words[ind][2:].lower()):
            fl = 0
            # BR alone does not make sense so default to Branch always
            if words[ind] == 'BR':
                words[ind] = 'BRnzp'
            for f in words[ind][2:].lower():
                fl |= flags[f]
            words[ind] = 'BR'

    if words[0] in instructions:
        found = words[0]
    else:
        if valid_label(words[0]):
            labels[words[0]] = pc
        else:
            print('Warning: invalid label %s in line\n%s' % (words[0], line))

        if len(words) < 2:
            return
        found = words[1] if words[1] in instructions else ''

    if not found:
        word = words[0]
        if len(words) > 1:
            input('Not instruction:%s' % line)
            return
        else:
            if valid_label(word):
                if word in label_location:
                    label_location[word].append([pc, 0xFFFF, 16])
                else:
                    label_location[word] = [[pc, 0xFFFF, 16]]
            else:
                raise ValueError('Invalid label: %r, line:\n%s\n' %
                                    (word, line))
        return

    try:
        instruction = instruction_info[found]
    except KeyError:
        input('Unknown: %s' % found)
    else:
        if found == 'BR':
            instruction |= fl
        r = rc = 0
        rc += found == 'JMPT'
    
        for word in words[1:]:
            word = word.rstrip(',')
            if word in regs:
                t = regs[word] << reg_pos[rc]
                r |= t
                rc += 1
            else:
                value = get_immediate(word, immediate_mask[found])
                if value is not None:
                    instruction |= value
                    if found in ('ADD', 'AND'):
                        instruction |= 1 << 5
                elif word != found:
                    if valid_label(word):
                        if word in label_location:
                            label_location[word].append([pc, immediate_mask[found], immediate[found]])
                        else:
                            label_location[word] = [[pc, immediate_mask[found], immediate[found]]]
                    else:
                        raise ValueError('Invalid label: %r, line:\n%s\n' %
                                     (word, line))

            instruction |= r
            if found == 'JMPT':
                break

        put_and_show(instruction)
        pc += 1

def lc_hex(h):
    """ lc hex has not the first 0 """
    return hex(h)[1:]

def in_range(n, bits):
    return -(1<< (bits-1)) <=  n < (1<< (bits-1))
   
if __name__ == '__main__':

    code = r'''
        .ORIG   x3000
        NOT     R1, R1
        JSR     F      ; Jump to subroutine F.
        STI     R1, FN
        HALT
    FN  .FILL   3121 ; Address where fn will be stored.
    N   .FILL   3120 ; Address where n is located
    HELLO .STRINGZ  "N is too far!\n"

    ; Subroutine F begins
    F   AND     R1, R1, N ; Clear R1
        ADD     R1, R0, #-16
        STR     R6, R1, #3 ; problem test
        ADD     R1, R1, R1 ; R1 is R1 + R1
        ADD     R1, R1, x3 ; R1 is R1 + 3
        LD      R2, HELLO
        BRnz     F
        RET ; Return from subroutine
        .END'''

    fn = 'Nothing'
    while (fn and not os.path.exists(fn)):
        print('\n'.join(fn for fn in os.listdir(os.curdir)
                            if fn.endswith('.asm')))
        fn = input('''
    Give name of code file
    or empty for  test code: ''')  # .rstrip()
    # rstrip for Python3.2 CMD bug, Python3.2.1rc fixed

    if fn:
        with open(fn) as codefile:
            code = codefile.read()
        base = fn.rsplit('.', 1)[0] + '_py'
    else:
        print('Using test code')
        base = 'out'

    verbose = input('Verbose Y/n? ').lower() != 'n'

    # processing the lines
    for line in code.splitlines():
        # remove comments
        orig_line, line = line, line.split(';')[0]
        # add space after comma to make sure registers are space separated also (not with strings)
        if '"' not in line:
            line = line.replace(',', ', ')
        # drop comments
        words = (line.split()) if ';' in line else line.split()
        if '.END' in words:
                break
        process_instruction(words)
        if verbose:
            # show binary form without label values in verbose mode
            print('\t', orig_line)

    # producing output
    for label, value in label_location.items():
        if label not in labels:
            print('Bad label failure:')
            print(label, ':', label_location[label])
        else:
            for ref, mask, bits in value:
                current = labels[label] - ref - 1
                # gludge for absolute addresses,
                # but seems correct for some code (lc3os.asm)
                if memory[ref] == 0: # not instruction -> absolute
                    memory[ref] = labels[label]
                elif not in_range(current,bits) :
                    raise ValueError(("%s, mask %s, offset %s,  %s, ref %s" %
                            (label,
                            bin(mask),
                            labels[label] - ref,
                            bin(labels[label]),
                            hex(ref))))
                else:
                    memory[ref] |= mask & current

    # choose base different from standard utilities to enable comparison

    # symbol list for Simulators
    with open(base + '.sym', 'w') as f:
        print('''//Symbol Name		Page Address
//----------------	------------
//''', end='\t', file=f)

        print('\n//\t'.join('\t%-20s%4x' % (name, value)
                        for name, value in labels.items()), file=f)

    print(80 * '-', '\n')
    print('Symbol cross reference:'.center(60) +
            '\n\n\t%-20s%-10s%s' % ('NAME', 'PLACE', 'USED'),
            end='\n' + 80 * '-' + '\n\t')
    # some cutting required to drop 0 from hex numbers
    print('\n\n\t'.join('%-20s%-10s%s' %
           (key, lc_hex(item),
            ', '.join(map(lc_hex, (list(zip(*label_location[key]))[0]
                        if key in label_location else ''))))
          # sort case insensitively by key
          for key, item in sorted(labels.items(), key=lambda x: x[0].lower())))

    # binary numbers output
    print('\n.bin file saved as ', end='')

    with open(base + '.bin', 'w') as f:
        print(f.name)
        print('{0:016b}'.format(orig), file=f)  # orig address
        print('\n'.join('{0:016b}'.format(memory[m]) for m in range(orig, pc)),
                file=f)

    # object file for running in Simulator
    with open(base + '.obj', 'wb') as f:
        print('.obj file saved as', f.name)
        #do slice from right after code and write
        #(byteorder of 0 does not matter)
        memory[pc] = orig
        if swap:
            memory.byteswap()

        memory[pc:pc+1].tofile(f)
        memory[orig:pc].tofile(f)
        # better to restore swap for future, even program stops here
        if swap:
            memory.byteswap()
TrustyTony 888 pyMod Team Colleague Featured Poster

Update, bugfix:
1) Biggest bug found was that also memory saved addresses were considered relative to location, changed those to be absolute.
2)The STINGZ label saving was dropped by mistake, the assert had bad format in it's message string. Corrected the obj saving to work with lc3os.asm, which starts from 0.
3) Added RTI and JMPT, as found the reference for them finally.
4) Range check function should be correct.

TrustyTony 888 pyMod Team Colleague Featured Poster

Sorry, but the lines for .FILL were not updated to have the third (bit length) value.(lines 158 and 160). Alternative for storing the value for every line would have been to replace the two values with 'found' and doing lookup from dictionaries label writing time instead of line decoding time. That would have been more efficient, but third value was quicker to implement.

elif '.FILL' in words:
        word = words[words.index('.FILL') + 1]
        try:
            put_and_show(int(word))
        except ValueError:
            value = get_immediate(word)
            if value is None:
                if word in label_location:
                    label_location[word].append([pc, 0xFFFF, 16])
                else:
                    label_location[word] = [[pc, 0xFFFF, 16]]
            else:
                memory[pc] = value

        if words[0] != '.FILL':
            labels[words[0]] = pc
        pc += 1
        return
~s.o.s~ 2,560 Failure as a human Team Colleague Featured Poster

I've updated the code snippet as per the request. Also, it would be nice if you could mention all the references you used when creating this since the linked Wikipedia page isn't very helpful in regards to specification, instruction set etc.

TrustyTony 888 pyMod Team Colleague Featured Poster

Instruction set is presented in quite detail for example in document:
LC-3 Instruction Set Architecture
and
http://www.scribd.com/doc/4596293/LC3-Instruction-Details
Simulator (even C to LC compiler) is available in http://highered.mcgraw-hill.com/sites/0072467509/student_view0/lc-3_simulator.html

My update seems to be put as original post. Ignore the update as it is missing the .FILL correction (it could be deleted).

TrustyTony 888 pyMod Team Colleague Featured Poster

I learned that c style escapes are not only reducing value of letter by certain amount (like ord('n')-ord('\n') == 100, but ord('b')-ord('\b') == 90), so here dict based solution (leaving the \x escape still todo)

To begining dictionaries:

escapes = '''a\ab\bf\fn\nr\rt\tv\v\'\'\"\"'''
esc_dict = dict(zip(escapes[::2], map(ord, escapes[1::2])))

replace lines 185-202 with:

# encode backslash to get special characters
        backslash = False
        for c in string:
            if not backslash:
                if c == '\\':
                    backslash = True
                    continue
                m = ord(c)
            else:
                if c in esc_dict:
                    m = esc_dict[c]
                else:
                # easiest to implement:
                # anything else escaped is itself (unlike Python)
                    m = ord(c)

                backslash = False

Also removed doubled if not backslash.

TrustyTony 888 pyMod Team Colleague Featured Poster

Some food for program from http://sheezyart.com/art/view/255537/.
However in order to multiplication to work it needs small change, so here code inline:

;; Registry usage is as follows:
; R7 temp and return addresses
; R6 linked stack pointer (last stack linked to 0)
; Reading phase:
; R1 reading number, R2 reading digit, R3 temp
; Calculating phase:
; R1 op1, R2 op2, R3 temp, R4 sign inverter switch
; Printing phase:
; R0 string, R1 answer, R2 counting digit, R3 units subtractor, R4 leading zeroes switch, R5 temp

.ORIG x3000

;; strings can be stored at the start of the program
;; because their top 8 bits are not used.
szMessage .STRINGZ "Lets do some 16-bit integer math!\n+ add, - subtract, * multiply, / divide, x quit\n"
szError .STRINGZ "\nCannot divide by Zero\n"
szSizeError .STRINGZ "\nExceeds range (-32,768 <= x <= 32,767)\n"

LEA R0, szMessage
PUTS ; greet the user
START
LD R0, asciicrt
OUT ; print out a prompt of ">"
AND R1, R1, 0 ; \
AND R3, R3, 0 ; clean these
AND R4, R4, 0 ; /

; create the linked stack pointer
LEA R6, linkedstack
INLOOP
GETC ; loop grabbing input

;; enter denotes the end of input
LD R7, nasciinl
ADD R7, R0, R7
BRZ CALCULATE

;; type X to quit
LD R7, nasciix
ADD R7, R0, R7
BRZ QUIT

;; check to see if it's a digit
LD R7, nascii9
ADD R7, R0, R7 ; gotta be less than 9
BRP NAN
LD R7, nascii0
ADD R2, R0, R7 ; and greater than 0
BRN NAN
;; it's a digit, so we'll echo it. It's in R2 now.
OUT

; time to tally up the number we're recording
ADD R1, R1, 0
BRZ SKIPSH ; lets not waste time on the first digit
;; shift over a digit
AND R3, R3, 0
ADD R3, R3, 10
AND R7, R7, 0
SHIFT ; this loop multiplies the previous digits by 10
ADD R7, R7, R1;
BRN TOOLARGE ; don't let the user type in something too big to record
ADD R3, R3, -1;
BRP SHIFT
ADD R1, R7, 0
SKIPSH
ADD R1, R1, R2 ; add the new digit to our recording number
JSR INLOOP

;-----------------------------------------------------------------

NAN ;; it's not a number
AND R3, R3, 0 ; set up R3 for the next function
LD R7, nasciimul ; is it multiplication?
ADD R7, R0, R7
BRZ qMUL
LD R7, nasciidiv ; no, division?
ADD R7, R0, R7
BRZ qDIV
LD R7, nasciisub ; nope, subtraction?
ADD R7, R0, R7
BRZ qSUB
LD R7, nasciiadd ; or addition?
ADD R7, R0, R7
BRZ qADD
;; it's not an operation, so lets ignore it and go back to input.
JSR INLOOP

;---------------------------------------------------------------

;; lets assemble an opcode based on which operation it is
qMUL ADD R3, R3, 1
qDIV ADD R3, R3, 1
qSUB ADD R3, R3, 1
qADD ADD R3, R3, 1
OUT
;; store it and the number in a new node on the linked stack
STR R1, R6, 0 ; store number in slot 1
AND R1, R1, 0 ; and clear it
STR R3, R6, 1 ; store operator in slot 2
ADD R6, R6, 3 ; increment the stack
STR R6, R6, -1 ; and provide a link to it in slot 3
;; and then back to input
JSR INLOOP

;---------------------------------------------------------------

DIV ;code 3
ADD R2, R2, 0
BRZ DIVERROR ; cannot divide by zero

AND R3, R3, 0
ADD R3, R3, -1 ; start a counter for the quotient
NOT R2, R2
ADD R2, R2, 1
DIVLOOP ADD R3, R3, 1 ; loop subtracting to divide
ADD R1, R1, R2
BRZP DIVLOOP
JSR SPUSH ; signed push
JSR MULDIV
DIVERROR
LEA R0, szError
PUTS
JSR START


MUL ;code 4
AND R3, R3, 0
MULLOOP ADD R3, R3, R1; ; loop adding for multiplication
BRN TOOLARGE ; don't let the product get too big to record
ADD R2, R2, -1;
BRNP MULLOOP
JSR SPUSH ; signed push
JSR MULDIV
TOOLARGE
LEA R0, szSizeError
PUTS
JSR START


SPUSH ;; by now, R3 contains the result, and R4 is the sign switch
ADD R4, R4, 0 ; check the signflag--
BRZ PUSH ; if it's set
NOT R3, R3 ; toggle the result's sign
ADD R3, R3, 1

PUSH ;; condenses the nodes in our linked stack. R5 should still contain the link
STR R3, R6, 0 ; store the sum
LDR R1, R5, 1 ; and lets squeeze in
STR R1, R6, 1 ; the next opcode.
LDR R5, R5, 2 ; R5 = R6->next->next;
STR R5, R6, 2 ; our condensed node will use the merging node's link
RET

;------------------------------------------------------------------------

LOADOPS ;; load operands for division and multiplication, and make them POSITIVE
AND R4, R4, 0 ; R4 is the sign flag: 0 = positive, -1/+1 = negative
LDR R1, R6, 0 ; load first operand
BRN LDOINV ; invert it and the sign flag if it's negative
LDR R5, R6, 2 ; get the link (keeping in !R5! for convenience)
LDR R2, R5, 0 ; and load the second operand from it
BRN LDOINV2 ; also invertable
RET
LDOINV
ADD R4, R4, -1 ; set inverted state
NOT R1, R1 ; invert R1
ADD R1, R1, 1 ;/
LDR R3, R6, 2
LDR R2, R5, 0 ; load second operand (link in R5)
BRN LDOINV2 ; if negative...
RET
LDOINV2
ADD R4, R4, 1 ; either set or revert inverted state
NOT R2, R2 ; and invert R2
ADD R2, R2, 1 ;/
RET

;---------------------------------------------------------------

CALCULATE ;; reads the linked stack and operates
STR R1, R6, 0 ; add the last number to the stack
AND R0, R0, 0 ; and plug the opcode cell in it,
STR R0, R6, 1 ; so we know this is the end
LEA R6, linkedstack ; back to the start of the stack

MULDIV ;; level one: multiplication and division
LDR R0, R6, 1 ; get next op
BRZ ADDSUB ;; if the opcode is 0, we've hit the end of the list
;; now lets load our operands and operate
JSR LOADOPS
ADD R0, R0, -3
BRP MUL
BRZ DIV
;; lower precedence, skip it for now
LDR R6, R6, 2 ; R6 = R6->next
JSR MULDIV ; loop


ADDSUB ;; handles addition and subtraction operations in the stack
LEA R6, linkedstack ; back to the top of the stack
ASLOOP
LDR R0, R6, 1 ; get next operation
BRZ PRINT

;; simple load operands, no sign switch
LDR R1, R6, 0 ; load first operand
LDR R5, R6, 2 ; get the link (keeping in !R5! as usual for convenience)
LDR R2, R5, 0 ; and load the second operand from it

;; check the opcode
ADD R0, R0, -1
BRZ ADDS
;; if we're not adding we're subtracting, so make the second operand negative
NOT R2, R2
ADD R2, R2, 1
ADDS ; and then add as usual
ADD R3, R1, R2
JSR SIGNMUX ; make sure it's a valid sum
JSR PUSH
JSR ASLOOP


;; the sum has passed the bounds if both operands have the same sign
;; and the sum has a different sign
SIGNMUX
ADD R1, R1, 0 ; check the first operand
BRN SMNEG
ADD R2, R2, 0 ; check the second operand
BRN CONDRET ; if it's not the same sign, the sum can't be invalid
ADD R3, R3, 0 ; check the sum
BRN TOOLARGE ; if +R1, +R2, and -R3, the sum is invalid
RET

SMNEG ADD R2, R2, 0 ; check second operand
BRZP CONDRET
ADD R3, R3, 0 ; and check the sum
BRZP TOOLARGE ; if -R1, -R2, and +R3, the sum is invalid
RET

PRINT
LD R0, asciieq ; print equals sign
OUT
LEA R0, buffer ; R0 is where we'll store the string
AND R4, R4, 0 ; leading zeroes switch, 0 if no digits recorded

LDR R1, R6, 0 ; R1 - number
BRZP POSITIVE
LD R2, asciimin ; stick on a '-' for negatives
STR R2, R0, 0
ADD R0, R0, 1
NOT R1, R1 ; and make em positive
ADD R1, R1, 1
POSITIVE
ADD R1, R1, 1 ; add 1 for no reason
LD R3, ntenthou ; R3 - place subtractor
JSR TESTDIGIT

LD R3, nthou ; set thousands place
JSR TESTDIGIT

LD R3, nhund ; hundreds
JSR TESTDIGIT

AND R3, R3, 0
ADD R3, R3, -10 ; tens
JSR TESTDIGIT

AND R3, R3, 0
ADD R3, R3, -1 ; and ones
JSR TESTDIGIT

AND R1, R1, 0 ; and then plug the end of the string
STR R1, R0, 0
LEA R0, buffer ; back to the start of the string
PUTS
LD R0, asciinl
OUT
JSR START ; we're finished! Lets start again, yay.

;-----------------------------------------------------------

;; converts each decimal digit of the binary number into ascii
TESTDIGIT
LD R2, ascii0m1 ; R2 - counter

ADD R5, R4, 0
BRP TDLOOP ; we've got digits, so we'll record zeroes

ADD R5, R1, R3 ; R5 temp
BRN CONDRET ; no need to record this leading zero

TDLOOP ADD R2, R2, 1 ; increment
ADD R1, R1, R3
BRP TDLOOP
STR R2, R0, 0 ; store the digit
NOT R3, R3 ; fix last subtraction
ADD R3, R3, 1 ; ^
ADD R1, R1, R3 ; ^
ADD R0, R0, 1 ; advance the string
ADD R4, R4, 1 ; we've recorded a digit, so zeroes can follow
RET


CONDRET ;; allows conditional BRs to return
RET

QUIT ;; jump here to end the program
HALT

; ascii codes to compare to
nascii0 .FILL -48
nascii9 .FILL -57
nasciinl .FILL -10
nasciiadd .FILL -43
nasciisub .FILL -45
nasciidiv .FILL -47
nasciimul .FILL -42
nasciix .FILL -120
asciimin .FILL 45
asciispace .FILL 32
asciieq .FILL 61
asciinl .FILL 10
ascii0m1 .FILL 47
asciicrt .FILL 62

; decimal places for conversion
ntenthou .FILL -10000
nthou .FILL -1000
nhund .FILL -100

buffer .BLKW 6 ; string to store the answer

linkedstack .BLKW 92 ; this stack will hold 30 operations before overflowing.
stackend .FILL xBEEF ; puts BEEF at the end of the stack, for the sake of those who stare at memory
; ...and are hungry.
.END
TrustyTony 888 pyMod Team Colleague Featured Poster

Luckyly, people do not seem to read my code (line 359 does not make sense as it allways just splits the line). The main routine have developed little and now includes browsing capability by giving the directory name instead of source file. So I post the main routine except the file saving parts, which I separated to own function, in the end this program invokes the simulator to be ready for testing (simulator must be in same directory as this program):

if __name__ == '__main__':

    simulate = os.path.join(os.path.realpath(os.curdir), 'Simulate.exe')
    fn = ''

    while (not os.path.isfile(fn)):
        print('\n\t' + '\n\t'.join(fn+'/' if os.path.isdir(fn) else fn
                                   for fn in os.listdir(os.curdir)
                            if fn.endswith('.asm') or os.path.isdir(fn)))
        print()
        # rstrip for Python3.2 CMD bug
        fn = input('Give name of code file or directory name to move to: ').rstrip()
        if os.path.isdir(fn):
            os.chdir(fn)

    verbose = input('Verbose Y/n? ').rstrip().lower() != 'n'

    # processing the lines
    orig = -1
    with open(fn) as codefile:
        for line in codefile:
            # discrard before .ORIG
            if orig == -1 and '.ORIG' not in line:
                continue
            # take out newline
            line = line.rstrip()
            # remove comments, do registers space separated instead of ','
            # in non-string lines to avoid extra logic for those
            words = (line.split(';')[0].split() if '"' in line
                     else line.split(';')[0].replace(',', ' ').split())
            if '.END' in words:
                # discard after .END
                break
            process_instruction(words)
            if verbose:
                # show binary form without label values in verbose mode
                print('\t', line)

    # .END was read or lines finished
    base = fn.rsplit('.', 1)[0] + '_py'
    do_output(base)
    subprocess.Popen([simulate, base + '.obj'])

I would be happy to listen any comments if somebody actually has run my code. At least couple of people upvoted it.

Doug_1 28 Newbie Poster

Very useful... I used your assembler as a starting point, added an interpreter, and created a complete language, with stepping debugger, tracing, break points, memory view, and more. See http://calicoproject.org/Calico_LC3 for more information.

Gribouillis commented: Congratulations to you and pytony ! +14
~s.o.s~ commented: Good stuff +14
Tcll 66 Posting Whiz in Training Featured Poster

finally, somebody who can show their programming skills awesomeface
lol jk
(this code made me gazm because of this (my language can finally be completed))

great work PyTony ;D

TrustyTony 888 pyMod Team Colleague Featured Poster

Better later than never, but this code is two years old and shows its age. Of course I nowadays would have used classes to avoid those ugly globals as I see that Doug has done by looking his source, even the Calico package does not start in this Windows XP laptop. One day if I am not busy I try to do clean up version of this original code with classes, I do not have time for that now, however.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.