From 9c4183fe8071100ffbe0a6d4d58859895924f5a9 Mon Sep 17 00:00:00 2001 From: Craig Thomas Date: Thu, 3 Oct 2019 15:32:56 -0400 Subject: [PATCH] Add new unit test framework. Add unit tests for get_value. Split out assembler from statement class. New structure for source files. Fix test import, update README. Move assembler to main directory. Refactor functions for Statement plus unit tests. Fixed all bugs. Fix failing unit tests. Added more unit tests, and updated coverage ignores. --- .gitignore | 2 + .travis.yml | 14 ++ README.md | 115 +++++---- assembler.py | 67 ++++++ chip8asm/chip8asm.py | 499 ---------------------------------------- chip8asm/exceptions.py | 37 +++ chip8asm/program.py | 126 ++++++++++ chip8asm/statement.py | 371 +++++++++++++++++++++++++++++ codecov.yml | 22 ++ requirements.txt | 2 + test/__init__.py | 0 test/test_exceptions.py | 45 ++++ test/test_program.py | 42 ++++ test/test_statement.py | 290 +++++++++++++++++++++++ 14 files changed, 1084 insertions(+), 548 deletions(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 assembler.py delete mode 100644 chip8asm/chip8asm.py create mode 100644 chip8asm/exceptions.py create mode 100644 chip8asm/program.py create mode 100644 chip8asm/statement.py create mode 100644 codecov.yml create mode 100644 requirements.txt create mode 100644 test/__init__.py create mode 100644 test/test_exceptions.py create mode 100644 test/test_program.py create mode 100644 test/test_statement.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b32d8aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +.idea \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..af23d59 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: python +sudo: false +python: + - 3.6 +addons: + apt: + packages: + - python-dev +virtalenv: + system_site_packages: true +script: + - coverage run -m nose +after_success: + - bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" diff --git a/README.md b/README.md index 7d93721..07fca94 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # (Super) Chip 8 Assembler -[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Build Status](https://img.shields.io/travis/craigthomas/Chip8Assembler?style=flat-square)](https://travis-ci.org/craigthomas/Chip8Assembler) +[![Codecov](https://img.shields.io/codecov/c/gh/craigthomas/Chip8Assembler?style=flat-square)](https://codecov.io/gh/craigthomas/Chip8Assembler) +[![Codacy Badge](https://img.shields.io/codacy/grade/f100b6deb9bf4729a2c55ef12fb695c9?style=flat-square)](https://www.codacy.com/app/craig-thomas/Chip8Assembler?utm_source=github.com&utm_medium=referral&utm_content=craigthomas/Chip8Python&utm_campaign=Badge_Grade) +[![Dependencies](https://img.shields.io/librariesio/github/craigthomas/Chip8Assembler?style=flat-square)](https://libraries.io/github/craigthomas/Chip8Assembler) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT) ## Table of Contents @@ -8,9 +12,9 @@ 2. [Requirements](#requirements) 3. [Installation](#installation) 4. [Usage](#usage) - 1. [Print Symbol Table](#print-symbol-table) - 2. [Print Assembled Statements](#print-assembled-statements) - 3. [Input Format](#input-format) + 1. [Input Format](#input-format) + 2. [Print Symbol Table](#print-symbol-table) + 3. [Print Assembled Statements](#print-assembled-statements) 5. [Mnemonic Table](#mnemonic-table) 1. [Chip 8 Mnemonics](#chip-8-mnemonics) 2. [Super Chip 8 Mnemonics](#super-chip-8-mnemonics) @@ -21,70 +25,40 @@ ## What is it? -This project is a (Super) Chip 8 assembler written in Python 2.7. The assembler will +This project is a (Super) Chip 8 assembler written in Python 3.6. The assembler will take valid Super Chip 8 assembly statements and generate a binary file containing the correct machine instructions. ## Requirements -The only requirements for this project is: - -* [Python 2.7](http://www.python.org) +In order to run the assembler, you will need to use Python 3.6 or greater. If you wish +to clone the repository for development, you will need Git. ## Installation -To install the source files, simply clone the repository in the directory -of your choice: +To install the source files, download the latest release from the +[releases](https://github.com/craigthomas/Chip8Assembler/releases) section of +the repository and unzip the contents in a directory of your choice. Or, +clone the repository in the directory of your choice with: git clone https://github.com/craigthomas/Chip8Assembler.git + +Next, you will need to install the required packages for the file: + + pip install -r requirements.txt ## Usage To run the assembler: - python chip8asm/chip8asm.py input_file -o output_file + python assembler.py input_file --output output_file This will assemble the instructions found in file `input_file` and will generate the associated Chip 8 machine instructions in binary format in `output_file`. -### Print Symbol Table - -To print the symbol table that is generated during assembly, use the `-s` switch: - - python chip8asm/chip8asm.py test.asm -s - -Which will have the following output: - - -- Symbol Table -- - start 0x200 - data1 0x209 - data 0x208 - -### Print Assembled Statements - -To print out the assembled version of the program, use the `-p` switch: - - python chip8asm/chip8asm.py test.asm -p - -Which will have the following output: - - -- Assembled Statements -- - 0x0200 6100 start LOAD r1,$0 # Clear contents of register 1 - 0x0202 7101 ADD r1,$1 # Add 1 to the register - 0x0204 310A SKE r1,$A # Check to see if we are at 10 - 0x0206 1200 JUMP start # Jump back to the start - 0x0208 1208 end JUMP end # Loop forever - -With this output, the first column is the offset in hex where the statement starts, -the second column contains the full machine-code operand, the third column is the -user-supplied label for the statement, the forth column is the mnemonic, the fifth -column is the register values of other numeric or label data the operation will -work on, and the fifth column is the comment string. - - ### Input Format The input file needs to follow the format below: @@ -94,9 +68,9 @@ The input file needs to follow the format below: Where: * `LABEL` is a 15 character label for the statement -* `MNEMONIC` is a Chip 8 operation mnemonic from the table below -* `OPERANDS` are registers, values or labels, as described in detail below -* `COMMENT` is a 30 character comment describing the statement +* `MNEMONIC` is a Chip 8 operation mnemonic from the [Mnemonic Table](#mnemonic-table) below +* `OPERANDS` are registers, values or labels, as described in the [Operands](#operands) section +* `COMMENT` is a 30 character comment describing the statement (may have a `#` preceding it) An example file: @@ -111,6 +85,49 @@ An example file: data1 FDB $FBEE Two byte piece of data +### Print Symbol Table + +To print the symbol table that is generated during assembly, use the `--symbols` +switch: + + python assembler.py test.asm --symbols + +Which will have the following output: + + -- Symbol Table -- + 0x0200 clear + 0x0202 start + 0x020A end + 0x020C data + 0x020E data1 + + +### Print Assembled Statements + +To print out the assembled version of the program, use the `--print` switch: + + python assembler.py test.asm --print + +Which will have the following output: + + -- Assembled Statements -- + 0x0200 0000 # A comment line that contains nothing + 0x0200 00E0 clear CLR # + 0x0202 6100 start LOAD r1,$0 # Clear contents of register 1 + 0x0204 7101 ADD r1,$1 # Add 1 to the register + 0x0206 310A SKE r1,$A # Check to see if we are at 10 + 0x0208 1202 JUMP start # Jump back to the start + 0x020A 120A end JUMP end # Loop forever + 0x020C 001A data FCB $1A # One byte piece of data + 0x020E FBEE data1 FDB $FBEE # Two byte piece of data + +With this output, the first column is the offset in hex where the statement starts, +the second column contains the full machine-code operand, the third column is the +user-supplied label for the statement, the forth column is the mnemonic, the fifth +column is the register values of other numeric or label data the operation will +work on, and the fifth column is the comment string. + + ## Mnemonic Table The assembler supports mnemonics for both the Chip 8 and Super Chip 8 language diff --git a/assembler.py b/assembler.py new file mode 100644 index 0000000..da46025 --- /dev/null +++ b/assembler.py @@ -0,0 +1,67 @@ +""" +Copyright (C) 2014-2018 Craig Thomas +This project uses an MIT style license - see LICENSE for details. + +A Chip 8 assembler - see the README.md file for details. +""" +# I M P O R T S ############################################################### + +import argparse + +from chip8asm.program import Program + +# F U N C T I O N S ########################################################### + + +def parse_arguments(): + """ + Parses the command-line arguments passed to the assembler. + """ + parser = argparse.ArgumentParser( + description="Assemble or disassemble " + "machine language code for the Chip8. See README.md for more " + "information, and LICENSE for terms of use." + ) + parser.add_argument("filename", help="the name of the file to examine") + parser.add_argument( + "--symbols", action="store_true", help="print out the symbol table" + ) + parser.add_argument( + "--print", action="store_true", + help="print out the assembled statements when finished" + ) + parser.add_argument( + "--output", metavar="FILE", help="stores the assembled program in FILE") + return parser.parse_args() + + +def main(args): + """ + Runs the assembler with the specified arguments. + + @param args: the arguments to the main function + @type: namedtuple + """ + program = Program() + program.parse_file(args.filename) + program.translate_statements() + program.set_addresses() + program.fix_opcodes() + + if args.symbols: + print("-- Symbol Table --") + for symbol, value in program.get_symbol_table().items(): + print("0x{} {}".format(value[2:].rjust(4, '0').upper(), symbol)) + + if args.print: + print("-- Assembled Statements --") + for statement in program.get_statements(): + print(statement) + + if args.output: + program.save_binary_file(args.output) + + +main(parse_arguments()) + +# E N D O F F I L E ####################################################### diff --git a/chip8asm/chip8asm.py b/chip8asm/chip8asm.py deleted file mode 100644 index 2673879..0000000 --- a/chip8asm/chip8asm.py +++ /dev/null @@ -1,499 +0,0 @@ -""" -Copyright (C) 2014-2018 Craig Thomas -This project uses an MIT style license - see LICENSE for details. - -A Chip 8 assembler - see the README.md file for details. -""" -# I M P O R T S ############################################################### - -import argparse -import re -import sys - -# C O N S T A N T S ########################################################### - -LABEL = "label" -OP = "op" -OPERANDS = "operands" -COMMENT = "comment" -SOURCE = "source" -TARGET = "target" -NUMERIC = "numeric" -SOURCE_REG = "s" -TARGET_REG = "t" -NUMERIC_REG = "n" - -# Opcode translation -OPERATIONS = { - "SYS": - {OP: "0nnn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 3}, - "CLR": - {OP: "00E0", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "RTS": - {OP: "00EE", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "JUMP": - {OP: "1nnn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 3}, - "CALL": - {OP: "2nnn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 3}, - "SKE": - {OP: "3snn", OPERANDS: 2, SOURCE: 1, TARGET: 0, NUMERIC: 2}, - "SKNE": - {OP: "4snn", OPERANDS: 2, SOURCE: 1, TARGET: 0, NUMERIC: 2}, - "SKRE": - {OP: "5st0", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "LOAD": - {OP: "6snn", OPERANDS: 2, SOURCE: 1, TARGET: 0, NUMERIC: 2}, - "ADD": - {OP: "7snn", OPERANDS: 2, SOURCE: 1, TARGET: 0, NUMERIC: 2}, - "MOVE": - {OP: "8st0", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "OR": - {OP: "8st1", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "AND": - {OP: "8st2", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "XOR": - {OP: "8st3", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "ADDR": - {OP: "8st4", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "SUB": - {OP: "8st5", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "SHR": - {OP: "8st6", OPERANDS: 1, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "SUBN": - {OP: "8st7", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "SHL": - {OP: "8stE", OPERANDS: 1, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "SKRNE": - {OP: "9st0", OPERANDS: 2, SOURCE: 1, TARGET: 1, NUMERIC: 0}, - "LOADI": - {OP: "Annn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 3}, - "JUMPI": - {OP: "Bnnn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 3}, - "RAND": - {OP: "Ctnn", OPERANDS: 2, SOURCE: 0, TARGET: 1, NUMERIC: 2}, - "DRAW": - {OP: "Dstn", OPERANDS: 3, SOURCE: 1, TARGET: 1, NUMERIC: 1}, - "SKPR": - {OP: "Es9E", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "SKUP": - {OP: "EsA1", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "MOVED": - {OP: "Ft07", OPERANDS: 1, SOURCE: 0, TARGET: 1, NUMERIC: 0}, - "KEYD": - {OP: "Ft0A", OPERANDS: 1, SOURCE: 0, TARGET: 1, NUMERIC: 0}, - "LOADD": - {OP: "Fs15", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "LOADS": - {OP: "Fs18", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "ADDI": - {OP: "Fs1E", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "LDSPR": - {OP: "Fs29", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "BCD": - {OP: "Fs33", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "STOR": - {OP: "Fs55", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "READ": - {OP: "Fs65", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - # Super Chip 8 Instructions - "SCRD": - {OP: "00Cn", OPERANDS: 1, SOURCE: 0, TARGET: 0, NUMERIC: 1}, - "SCRR": - {OP: "00FB", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "SCRL": - {OP: "00FC", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "EXIT": - {OP: "00FD", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "EXTD": - {OP: "00FE", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "EXTE": - {OP: "00FF", OPERANDS: 0, SOURCE: 0, TARGET: 0, NUMERIC: 0}, - "SRPL": - {OP: "Fs75", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0}, - "LRPL": - {OP: "Fs85", OPERANDS: 1, SOURCE: 1, TARGET: 0, NUMERIC: 0} -} - -# Pseudo operations -FCB = "FCB" -FDB = "FDB" -PSEUDO_OPERATIONS = [FCB, FDB] - -# Pattern to parse a single line -ASM_LINE_REGEX = re.compile( - r"(?P