Exemple #1
0
def preprocessor_constants(lines):
    """
    Replaces constants usage with the defined vaule.

    Example:

        $const = 5
        MOV [2] $const

    Results in:

        MOV [2] 5

    :type lines: list[Line]
    """
    global automem_counter
    automem_counter = count()
    constants = {}

    for lineno, line in enumerate(lines):
        tokens = []
        iterator = iter(line.contents.split())
        is_assignment_line = False

        # Process all tokens in this line
        for token, next_token in neighborhood(iterator):
            if token[0] == '$':
                const_name = token[1:]

                if next_token == '=':
                    # Found assignment, store the associated value
                    is_assignment_line = True
                    value = next(iterator)

                    if const_name in constants:
                        warn('Redefined ${}'.format(const_name),
                             RedefinitionWarning, line)

                    if value == '[_]':
                        # Process auto increment memory
                        value = automem(line)

                    constants[const_name] = value

                else:
                    # Found usage of constant, replace with stored value
                    try:
                        tokens.append(constants[const_name])
                    except KeyError:
                        fatal_error('No such constant: ${}'.format(const_name),
                                    NoSuchConstantError, line)

            else:
                # Uninteresting token
                tokens.append(token)

        # Skip assignment lines
        if not is_assignment_line:
            debug('Constants:', constants)
            yield set_contents(line, ' '.join(tokens))
Exemple #2
0
def assemble(code):
    """
    :type code: list[Line]
    """
    assert isinstance(code, list)

    hexcode = []

    for line in code:
        iterator = iter(line.contents.split())

        for mnem in iterator:
            debug('Processing token:', mnem)

            # Look up token in instructions list
            try:
                instruction = instructions[mnem.upper()]
            except KeyError:
                fatal_error('Unknown mnemonic: {}'.format(mnem),
                            UnknownMnemonicError, line)
            debug('Instruction:', instruction)

            # Get arguments, look up arg count from first opcode
            opcode_def = list(instruction.values())[0]
            num_args = len(opcode_def)
            debug('Expected number of arguments:', num_args)

            arg_list = [next(iterator) for _ in range(num_args)]
            arg_types = [get_arg_type(arg) for arg in arg_list]

            debug('Arguments:', arg_list)
            debug('Argument types:', arg_types)

            # Find matching instruction
            opcode = None
            for op in instruction:
                # Check if the list of type arguments matches
                opcode_args = instruction[op]
                if opcode_args == tuple(arg_types):
                    opcode = op

            if opcode is None:
                arg_str = ', '.join(t.name for t in arg_types)
                msg = 'Unknown argument types for mnemonic {} and ' \
                      'given arguments: {}'.format(mnem, arg_str)
                fatal_error(msg, AssemblerSyntaxError, line)

            # Convert arguments to hex
            # 1. Strip '[ ]'
            arg_list = [arg.strip('[]') for arg in arg_list]

            # 2. Convert to hex
            arg_list = [to_hex(arg) for arg in arg_list]
            debug('Arguments (hex):', arg_list)

            # Finally, create the opcode/hex string
            hexcode.append('{} {}'.format(opcode, ' '.join(arg_list)).strip())
            debug('')

    return ' '.join(hexcode)
Exemple #3
0
def assemble(code):
    """
    :type code: list[Line]
    """
    assert isinstance(code, list)

    hexcode = []

    for line in code:
        iterator = iter(line.contents.split())

        for mnem in iterator:
            debug('Processing token:', mnem)

            # Look up token in instructions list
            try:
                instruction = instructions[mnem.upper()]
            except KeyError:
                fatal_error('Unknown mnemonic: {}'.format(mnem),
                            UnknownMnemonicError, line)
            debug('Instruction:', instruction)

            # Get arguments, look up arg count from first opcode
            opcode_def = list(instruction.values())[0]
            num_args = len(opcode_def)
            debug('Expected number of arguments:', num_args)

            arg_list = [next(iterator) for _ in range(num_args)]
            arg_types = [get_arg_type(arg) for arg in arg_list]

            debug('Arguments:', arg_list)
            debug('Argument types:', arg_types)

            # Find matching instruction
            opcode = None
            for op in instruction:
                # Check if the list of type arguments matches
                opcode_args = instruction[op]
                if opcode_args == tuple(arg_types):
                    opcode = op

            if opcode is None:
                arg_str = ', '.join(t.name for t in arg_types)
                msg = 'Unknown argument types for mnemonic {} and ' \
                      'given arguments: {}'.format(mnem, arg_str)
                fatal_error(msg, AssemblerSyntaxError, line)

            # Convert arguments to hex
            # 1. Strip '[ ]'
            arg_list = [arg.strip('[]') for arg in arg_list]

            # 2. Convert to hex
            arg_list = [to_hex(arg) for arg in arg_list]
            debug('Arguments (hex):', arg_list)

            # Finally, create the opcode/hex string
            hexcode.append('{} {}'.format(opcode, ' '.join(arg_list)).strip())
            debug('')

    return ' '.join(hexcode)
Exemple #4
0
def process_call(line, contents, subroutines):
    contents = contents.replace('@call', '')
    contents = contents.strip('()')

    parts = contents.split(',')
    name = parts[0]
    args = [s.strip(' )') for s in parts[1:]]

    if name not in subroutines:
        fatal_error('Unknown subroutine: {}'.format(name),
                    AssemblerNameError, line)

    if len(args) != subroutines[name]:
        msg = 'Wrong number of arguments: Expected {}, got {}'.format(
            subroutines[name], len(args)
        )
        fatal_error(msg, AssemblerException, line)

    debug('@call of {}'.format(name))

    for i, arg in enumerate(args):
        yield build_line('MOV $arg{} {}'.format(i, arg))

    counter = next(call_counter)
    yield build_line('MOV $jump_back :ret{}'.format(counter))
    yield build_line('JMP :{}'.format(name))
    yield build_line('ret{}:'.format(counter))
Exemple #5
0
def process_call(line, contents, subroutines):
    contents = contents.replace('@call', '')
    contents = contents.strip('()')

    parts = contents.split(',')
    name = parts[0]
    args = [s.strip(' )') for s in parts[1:]]

    if name not in subroutines:
        fatal_error('Unknown subroutine: {}'.format(name), AssemblerNameError,
                    line)

    if len(args) != subroutines[name]:
        msg = 'Wrong number of arguments: Expected {}, got {}'.format(
            subroutines[name], len(args))
        fatal_error(msg, AssemblerException, line)

    debug('@call of {}'.format(name))

    for i, arg in enumerate(args):
        yield build_line('MOV $arg{} {}'.format(i, arg))

    counter = next(call_counter)
    yield build_line('MOV $jump_back :ret{}'.format(counter))
    yield build_line('JMP :{}'.format(name))
    yield build_line('ret{}:'.format(counter))
Exemple #6
0
def automem(line):
    counter = next(automem_counter)

    if counter >= MEMORY_SIZE:
        fatal_error('[_]: No more memory slots left!',
                    AssemblerException, line)

    return '[{}]'.format(counter)
Exemple #7
0
    def instr_jump(self, dest):
        """ Move the instruction pointer to dest. """
        assert dest is not None, 'Tried to jump to None'

        if self.prev_instr_pointer == dest:
            fatal_error('Stuck in infinite loop!', VirtualRuntimeError)

        self.prev_instr_pointer = self.instr_pointer

        self.instr_pointer = dest
        self.jumping = True
Exemple #8
0
def preprocessor_subroutine(lines):
    reset_counters()

    subroutines = collect_definitions(lines)

    if not subroutines:
        # Check, if there are calls w/o definition
        for line in lines:
            if '@call' in line.contents:
                fatal_error('@call without subroutine definition',
                            AssemblerException, line)

        yield from lines
        return

    debug('{} subroutines found: {}'.format(len(subroutines), subroutines))

    # Build preamble
    yield build_line('$return = [_]')
    yield build_line('$jump_back = [_]')

    for i in range(max(subroutines.values())):
        yield build_line('$arg{} = [_]'.format(i))

    # Process start()/end()/call()
    in_subroutine = False
    call_count = 0

    for line in lines:
        #: :type: str
        contents = line.contents.strip()

        if contents.startswith('@call'):
            yield from process_call(line, contents, subroutines)
            call_count += 1

        elif contents.startswith('@start('):
            if in_subroutine:
                assert False

            in_subroutine = True
            yield from _subroutine_process_start(line, contents)

        elif contents.startswith('@end()'):
            if not in_subroutine:
                assert False

            debug('@end')

            in_subroutine = False
            yield Line(0, '<subroutine>', '', 'JMP $jump_back')

        else:
            yield line
Exemple #9
0
def preprocessor_subroutine(lines):
    reset_counters()

    subroutines = collect_definitions(lines)

    if not subroutines:
        # Check, if there are calls w/o definition
        for line in lines:
            if '@call' in line.contents:
                fatal_error('@call without subroutine definition',
                            AssemblerException, line)

        yield from lines
        return

    debug('{} subroutines found: {}'.format(len(subroutines), subroutines))

    # Build preamble
    yield build_line('$return = [_]')
    yield build_line('$jump_back = [_]')

    for i in range(max(subroutines.values())):
        yield build_line('$arg{} = [_]'.format(i))

    # Process start()/end()/call()
    in_subroutine = False
    call_count = 0

    for line in lines:
        #: :type: str
        contents = line.contents.strip()

        if contents.startswith('@call'):
            yield from process_call(line, contents, subroutines)
            call_count += 1

        elif contents.startswith('@start('):
            if in_subroutine:
                assert False

            in_subroutine = True
            yield from _subroutine_process_start(line, contents)

        elif contents.startswith('@end()'):
            if not in_subroutine:
                assert False

            debug('@end')

            in_subroutine = False
            yield Line(0, '<subroutine>', '', 'JMP $jump_back')

        else:
            yield line
Exemple #10
0
def preprocessor_import(lines):
    """
    Process import directives.

    Example:

    a.asm:
        APRINT '!'

    b.asm:
        #include a.asm
        HALT

    Results in:

        APRINT '!'
        HALT

    Note: A file will be imported only once. A file cannot import the importing
    file.

    :type lines: list[Line]
    """
    lines = list(lines)
    included = set()  # Files we already included

    while lines:
        # Take the first line from the stack
        line = lines.pop(0)
        contents = line.contents.strip()

        if contents.startswith('#import'):
            path = contents.split('#import')[1].strip()

            if path not in included:
                # Put the new contents to the top of the stack
                to_include = None
                try:
                    to_include = read_file(path)
                except FileNotFoundError:
                    fatal_error('File not found: {}'.format(path),
                                FileNotFoundError, line)

                # Insert lines into the current position
                lines[0:0] = [
                    Line(i, path, l.strip(), l.strip())
                    for i, l in enumerate(to_include)
                ]
                included.add(path)
        else:
            # No includes to process, yield the line
            yield line
Exemple #11
0
def preprocessor_import(lines):
    """
    Process import directives.

    Example:

    a.asm:
        APRINT '!'

    b.asm:
        #include a.asm
        HALT

    Results in:

        APRINT '!'
        HALT

    Note: A file will be imported only once. A file cannot import the importing
    file.

    :type lines: list[Line]
    """
    lines = list(lines)
    included = set()  # Files we already included

    while lines:
        # Take the first line from the stack
        line = lines.pop(0)
        contents = line.contents.strip()

        if contents.startswith('#import'):
            path = contents.split('#import')[1].strip()

            if path not in included:
                # Put the new contents to the top of the stack
                to_include = None
                try:
                    to_include = read_file(path)
                except FileNotFoundError:
                    fatal_error('File not found: {}'.format(path),
                                FileNotFoundError, line)

                # Insert lines into the current position
                lines[0:0] = [Line(i, path, l.strip(), l.strip())
                              for i, l in enumerate(to_include)]
                included.add(path)
        else:
            # No includes to process, yield the line
            yield line
Exemple #12
0
def preprocessor_labels(lines):
    """
    Replace labels with the referenced instruction number.

    Example:

        label:
        GOTO :label

    Results in:

        GOTO 0

    :type lines: list[Line]
    """
    labels = collect_labels(lines)

    # Update references
    for line in lines:
        tokens = []

        for token in line.contents.split():

            # Label usage
            if token[0] == ':':
                label_name = token[1:]

                try:
                    instruction_no = labels[label_name]
                except KeyError:
                    fatal_error('No such label: {}'.format(label_name),
                                NoSuchLabelError, line)
                else:
                    tokens.append(str(instruction_no))

            # Label definitions
            elif token[-1] == ':':
                # Skip
                continue

            else:
                tokens.append(token)

        # If there any tokens left, yield them
        if tokens:
            debug('Labels:', labels)
            yield set_contents(line, ' '.join(tokens))
Exemple #13
0
def collect_labels(lines):
    labels = {}

    token_count = 0

    # Pass 1: Collect labels
    for line in lines:
        tokens = line.contents.split()

        for index, token in enumerate(tokens):
            if token[-1] == ':':
                # Label definition
                label = token[:-1]

                if label in labels:
                    fatal_error('Redefinition of label: ' + label,
                                RedefinitionError, line)

                labels[label] = token_count + index - len(labels)

        token_count += len(tokens)

    return labels
Exemple #14
0
    def run(self, asm, filename=None, preprocess=True):
        start = timer()

        # Tokenize code
        if preprocess:
            self.tokens = assembler.assembler_to_hex(asm, filename)

        self.tokens = self.tokens.split()

        # Main loop
        while self.running:
            self.jumping = False

            # Check bounds of instr_pointer
            if self.instr_pointer >= len(self.tokens):
                fatal_error('Reached end of code without seeing HALT',
                            MissingHaltError, exit_func=self.halt)

            # Get current opcode
            opcode = self.tokens[self.instr_pointer]
            mnem = opcodes[opcode].upper()

            if self.debug:
                print('Instruction: {} ({})'.format(mnem, opcode))

            # Look up instruction
            instruction_class = self.instructions[mnem]
            instruction = instruction_class(self)
            instruction_spec = instructions[mnem]
            annotations = get_annotations(instruction)

            # Look up number of arguments
            num_args = len(list(instruction_spec.values())[0])

            if self.debug:
                print('Number of args:', num_args)
                print('Argument spec:', instruction_spec[opcode])

            # Collect arguments
            if num_args:
                try:
                    args = [self.process_arg(i, annotations, opcode)
                            for i in range(num_args)]
                except IndexError:
                    msg = 'Unexpectedly reached EOF. Maybe an argument is ' \
                          'missing or a messed up jump occured'
                    fatal_error(msg, VirtualRuntimeError, exit_func=self.halt)
            else:
                args = []

            if self.debug:
                print('Arguments:', args)

            # Run instruction
            return_value = instruction(*args)

            # Process return value
            try:
                return_type = annotations['return']
            except KeyError:
                return_type = None
            self.process_return_value(return_type, return_value)

            # Increase counters
            self.ticks += 1
            if not self.jumping:
                self.prev_instr_pointer = self.instr_pointer
                self.instr_pointer += 1  # Skip current opcode
                self.instr_pointer += num_args  # Skip arguments

            if self.debug:
                print('Memory:', self.memory)
                print()
                print()

        if self.debug:
            print()
            print('Exited after {} ticks in {:.5}s'.format(self.ticks,
                                                           timer() - start))

        return self.output.getvalue()