Compare commits

...

15 Commits

Author SHA1 Message Date
leebehrens 8bb47a45a0
Merge 4e950d6ef5 into 74e2576894 2024-04-13 14:26:58 -07:00
Mike Naberezny 74e2576894 Back to .dev0 until next release 2024-04-12 14:03:26 -07:00
Mike Naberezny 9455a5c70e Prepare 1.2.0 release 2024-04-12 13:00:50 -07:00
Mike Naberezny 1870d65982 Fix interactive assembly on Python 3
Closes #81
Closes #78
Closes #65
Closes #64
Closes #63
2024-04-12 12:25:12 -07:00
Mike Naberezny 8dce37f6b8 Fix regular expression warnings on Python 3.12 2024-04-12 12:21:46 -07:00
Mike Naberezny 85ed46fd68 Fix crash formatting traceback 2024-04-12 11:14:09 -07:00
Mike Naberezny 2b837bbd4f Avoid Node.js 16 deprecation warning on GitHub Actions 2024-04-12 11:13:00 -07:00
Mike Naberezny 6ccdbe9c07 Update copyright year to 2024 2024-04-12 11:11:16 -07:00
Mike Naberezny 95e152d6cb Add changelog entry for b710c742ac 2023-11-18 17:04:42 -08:00
Mike Naberezny d547dbc07c Test with Python 3.12 on CI 2023-11-18 16:43:21 -08:00
Mike Naberezny 9ae3871388 Fix running Python 3.4 and 3.5 on CI 2023-11-18 16:34:11 -08:00
Mike Naberezny 55eef25998 Fix running Python 2 on CI 2023-11-18 15:06:23 -08:00
Mike Naberezny db247b9765 Revert "Remove support for Python 2"
This reverts commit ca02d12fc6.
2023-11-18 15:03:03 -08:00
leebehrens 4e950d6ef5 Delete settings.json 2019-06-09 13:00:53 -05:00
leebehrens edcdeceebe Added `go` command, help, tests
Added support for `go` command to continue execution at the current PC, help for `go`, and tests
2019-06-09 12:45:39 -05:00
13 changed files with 294 additions and 35 deletions

View File

@ -2,26 +2,105 @@ name: Run all tests
on: [push, pull_request]
env:
PIP: "env PIP_DISABLE_PIP_VERSION_CHECK=1
PYTHONWARNINGS=ignore:DEPRECATION
pip --no-cache-dir"
jobs:
build:
tests_py27:
runs-on: ubuntu-20.04
container: python:2.7
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Show Python version
run: python -V
- name: Install dependencies
run: $PIP install setuptools pexpect
- name: Run the tests
run: python setup.py test -q
- name: Run the end-to-end tests
run: END_TO_END=1 python setup.py test -q
build_py34:
runs-on: ubuntu-20.04
container: python:3.4
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v3
# does not work with actions/checkout@v4:
# /usr/bin/docker exec 289170dbefc90d2ba94a09f3dcb70c42c1e1b29a5e877cd533b26ebf73ca82fa sh -c "cat /etc/*release | grep ^ID"
# /__e/node20/bin/node: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.27' not found (required by /__e/node20/bin/node)
# /__e/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found (required by /__e/node20/bin/node)
# /__e/node20/bin/node: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.25' not found (required by /__e/node20/bin/node)
- name: Show Python version
run: python -V
- name: Install dependencies
run: $PIP install setuptools pexpect
- name: Run the tests
run: python setup.py test -q
- name: Run the end-to-end tests
run: END_TO_END=1 python setup.py test -q
build_py35:
runs-on: ubuntu-20.04
container: python:3.5
strategy:
fail-fast: false
steps:
- uses: actions/checkout@v4
- name: Show Python version
run: python -V
- name: Install dependencies
run: $PIP install setuptools pexpect
- name: Run the tests
run: python setup.py test -q
- name: Run the end-to-end tests
run: END_TO_END=1 python setup.py test -q
build_py3x:
strategy:
fail-fast: false
matrix:
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11]
os: [ubuntu-20.04, windows-2019]
python-version: [3.6, 3.7, 3.8, 3.9, "3.10", 3.11, 3.12]
os: [ubuntu-20.04]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Show Python version
run: python -V
- name: Install dependencies
run: $PIP install setuptools pexpect
- name: Run the tests
run: python setup.py test -q
- name: Run the end-to-end tests
run: END_TO_END=1 python setup.py test -q

View File

@ -1,13 +1,17 @@
2.0.0.dev0 (Next Release)
1.3.0.dev0 (Next Release)
-------------------------
- Support for some older Python versions has been dropped. Py65
now requires Python 3.6 or later.
1.2.0 (2024-04-12)
------------------
- Fixed a bug with character input that would cause characters to be
dropped when pasting in larger amounts of text. This makes it possible
to paste programs into EhBASIC and Taliforth. Patch by SamCoVT.
- Fixed interactive assembly on Python 3.
- Fixed regular expression warnings on Python 3.12.
- The ``fill`` command in the monitor now shows an error message if an
address or value is out of range.
@ -22,6 +26,8 @@
- Fixed assembly and disassembly of 65C02 instruction $64 (``STZ $12``).
Patch by Patrick Surry.
- Removed use of the ``asyncore`` module deprecated in Python 3.10.
1.1.0 (2018-07-01)
------------------

View File

@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2008-2018, Mike Naberezny and contributors.
Copyright (c) 2008-2024, Mike Naberezny and contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -20,9 +20,9 @@ Installation
Py65 packages are `available <http://pypi.python.org/pypi/py65>`_ on the
Python Package Index (PyPI). You download them from there or you can
use ``pip3`` to automatically install or upgrade Py65::
use ``pip`` to install Py65::
$ pip3 install -U py65
$ pip install setuptools py65
Devices
-------

View File

@ -49,7 +49,7 @@ copyright = u'2008-%d, Mike Naberezny and contributors' % year
# built documents.
#
# The short X.Y version.
version = '1.1.0.dev0'
version = '1.3.0.dev0'
# The full version, including alpha/beta/rc tags.
release = version

View File

@ -92,7 +92,7 @@ class Assembler:
and parsing the address part using AddressParser. The result of
the normalization is a tuple of two strings (opcode, operand).
"""
statement = ' '.join(str.split(statement))
statement = ' '.join(statement.split())
# normalize target in operand
match = self.Statement.match(statement)

21
py65/compat.py Normal file
View File

@ -0,0 +1,21 @@
import sys
PY2 = sys.version_info[0] == 2
if PY2:
unicode = unicode
def as_string(s, encoding='utf-8'):
if isinstance(s, unicode):
return s
else:
return s.decode(encoding)
else:
unicode = str
def as_string(s, encoding='utf-8'):
if isinstance(s, str):
return s
else:
return s.decode(encoding)

View File

@ -22,8 +22,6 @@ import shlex
import sys
import traceback
from urllib.request import urlopen
from py65.devices.mpu6502 import MPU as NMOS6502
from py65.devices.mpu65c02 import MPU as CMOS65C02
from py65.devices.mpu65org16 import MPU as V65Org16
@ -34,6 +32,11 @@ from py65.utils import console
from py65.utils.conversions import itoa
from py65.memory import ObservableMemory
try:
from urllib2 import urlopen
except ImportError: # Python 3
from urllib.request import urlopen
class Monitor(cmd.Cmd):
Microprocessors = {'6502': NMOS6502, '65C02': CMOS65C02,
@ -160,8 +163,8 @@ class Monitor(cmd.Cmd):
result = cmd.Cmd.onecmd(self, line)
except KeyboardInterrupt:
self._output("Interrupt")
except Exception as e:
error = ''.join(traceback.format_exception(e))
except Exception:
error = ''.join(traceback.format_exception(*sys.exc_info()))
self._output(error)
if not line.startswith("quit"):
@ -199,6 +202,7 @@ class Monitor(cmd.Cmd):
'f': 'fill',
'>': 'fill',
'g': 'goto',
'go': 'go',
'h': 'help',
'?': 'help',
'l': 'load',
@ -236,7 +240,7 @@ class Monitor(cmd.Cmd):
line = command
break
pattern = '^%s\s+' % re.escape(shortcut)
pattern = r'^%s\s+' % re.escape(shortcut)
matches = re.match(pattern, line)
if matches:
start, end = matches.span()
@ -491,6 +495,14 @@ class Monitor(cmd.Cmd):
self._mpu.pc = self._address_parser.number(args)
brks = [0x00] # BRK
self._run(stopcodes=brks)
def help_go(self):
self._output("go")
self._output("Continue execution.")
def do_go(self, args):
brks = [0x00] # BRK
self._run(stopcodes=brks)
def _run(self, stopcodes):
stopcodes = set(stopcodes)
@ -577,7 +589,7 @@ class Monitor(cmd.Cmd):
if args == '':
return
pairs = re.findall('([^=,\s]*)=([^=,\s]*)', args)
pairs = re.findall(r'([^=,\s]*)=([^=,\s]*)', args)
if pairs == []:
return self._output("Syntax error: %s" % args)

88
py65/tests/end_to_end.py Normal file
View File

@ -0,0 +1,88 @@
import os
import signal
import sys
import tempfile
import unittest
from py65.compat import unicode
# end-to-test tests are slow so only run them when asked
if 'END_TO_END' in os.environ:
if sys.platform == "win32":
raise NotImplementedError()
else:
import pexpect
BaseTestCase = unittest.TestCase
else:
BaseTestCase = object
class EndToEndTests(BaseTestCase):
def _spawn(self):
mon = pexpect.spawn(
sys.executable,
['-u', '-m', 'py65.monitor'],
encoding='utf-8'
)
mon.expect_exact(unicode("Py65 Monitor"))
self.addCleanup(mon.kill, signal.SIGINT)
return mon
def test_putc(self):
mon = self._spawn()
mon.sendline(unicode("add_label f001 putc"))
mon.sendline(unicode("a c000 lda #'H"))
mon.sendline(unicode("a c002 sta putc"))
mon.sendline(unicode("a c005 lda #'I"))
mon.sendline(unicode("a c007 sta putc"))
mon.sendline(unicode("a c00a brk"))
mon.sendline(unicode("g c000"))
mon.expect_exact(unicode("HI"))
mon.sendline(unicode("q"))
def test_getc(self):
mon = self._spawn()
mon.sendline(unicode("add_label f004 getc"))
mon.sendline(unicode("a c000 ldx #0"))
mon.sendline(unicode("a c002 lda getc"))
mon.sendline(unicode("a c005 beq c002"))
mon.sendline(unicode("a c007 cmp #'!"))
mon.sendline(unicode("a c009 bne c00c"))
mon.sendline(unicode("a c00b brk"))
mon.sendline(unicode("a c00c sta 1000,x"))
mon.sendline(unicode("a c00f inx"))
mon.sendline(unicode("a c010 jmp c002"))
mon.sendline(unicode("g c000"))
mon.send(unicode("HELLO!"))
mon.expect_exact(unicode("6502:"))
mon.sendline(unicode("m 1000:1004"))
mon.expect_exact(unicode("48 45 4c 4c 4f"))
def test_assemble_interactive(self):
mon = self._spawn()
mon.sendline(unicode("assemble 0"))
mon.expect_exact(unicode("$0000"))
mon.sendline(unicode("lda $1234"))
mon.expect_exact(unicode("ad 34 12"))
mon.expect_exact(unicode("$0003"))
mon.sendline(unicode("sta $4567"))
mon.expect_exact(unicode("8d 67 45"))
mon.sendline(unicode("invalid"))
mon.expect_exact(unicode("?Syntax"))
mon.sendline()
mon.sendline(unicode("quit"))
if __name__ == '__main__':
unittest.main()

View File

@ -2,11 +2,13 @@ import unittest
import sys
import os
import tempfile
from io import StringIO
from py65.monitor import Monitor
try:
from StringIO import StringIO
except ImportError: # Python 3
from io import StringIO
class MonitorTests(unittest.TestCase):
@ -539,6 +541,46 @@ class MonitorTests(unittest.TestCase):
mon.do_goto('0')
self.assertEqual(0x02, mon._mpu.pc)
# go
def test_shortcut_for_go(self):
stdout = StringIO()
mon = Monitor(stdout=stdout)
mon.do_help('go')
out = stdout.getvalue()
self.assertTrue(out.startswith('go'))
def test_go_with_breakpoints_stops_execution_at_breakpoint(self):
stdout = StringIO()
mon = Monitor(stdout=stdout)
mon._breakpoints = [ 0x03 ]
mon._mpu.memory = [ 0xEA, 0xEA, 0xEA, 0xEA ]
mon._mpu.pc = 0x00
mon.do_go('')
out = stdout.getvalue()
self.assertTrue(out.startswith("Breakpoint 0 reached"))
self.assertEqual(0x03, mon._mpu.pc)
def test_go_with_breakpoints_stops_execution_at_brk(self):
stdout = StringIO()
mon = Monitor(stdout=stdout)
mon._breakpoints = [ 0x02 ]
mon._mpu.memory = [ 0xEA, 0xEA, 0x00, 0xEA ]
mon._mpu.pc = 0x00
mon.do_go('')
self.assertEqual(0x02, mon._mpu.pc)
def test_go_without_breakpoints_stops_execution_at_brk(self):
stdout = StringIO()
mon = Monitor(stdout=stdout)
mon._breakpoints = []
mon._mpu.memory = [ 0xEA, 0xEA, 0x00, 0xEA ]
mon._mpu.pc = 0x00
mon.do_go('')
self.assertEqual(0x02, mon._mpu.pc)
# help
def test_help_without_args_shows_documented_commands(self):

View File

@ -61,7 +61,7 @@ class AddressParser(object):
return self.labels[num]
else:
matches = re.match('^([^\s+-]+)\s*([+\-])\s*([$+%]?\d+)$', num)
matches = re.match(r'^([^\s+-]+)\s*([+\-])\s*([$+%]?\d+)$', num)
if matches:
label, sign, offset = matches.groups()
@ -88,7 +88,7 @@ class AddressParser(object):
"""Parse a string containing an address or a range of addresses
into a tuple of (start address, end address)
"""
matches = re.match('^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses)
matches = re.match(r'^([^:,]+)\s*[:,]+\s*([^:,]+)$', addresses)
if matches:
start, end = map(self.number, matches.groups(0))
else:

View File

@ -1,5 +1,7 @@
import sys
from py65.compat import as_string
if sys.platform[:3] == "win":
import msvcrt
@ -24,10 +26,7 @@ if sys.platform[:3] == "win":
is available. Does not echo the character. The stdin argument is
for function signature compatibility and is ignored.
"""
c = msvcrt.getch()
if isinstance(c, bytes): # Python 3
c = c.decode('latin-1')
return c
return as_string(msvcrt.getch())
def getch_noblock(stdin):
""" Read one character from the Windows console without blocking.
@ -36,8 +35,8 @@ if sys.platform[:3] == "win":
available, an empty string is returned.
"""
if msvcrt.kbhit():
return getch(stdin)
return ''
return as_string(getch(stdin))
return u''
else:
import termios
@ -157,7 +156,7 @@ else:
# use select to make sure there is at least one char to read.
rd,wr,er = select([stdin], [], [], 0.01)
if rd != []:
char = stdin.read(1)
char = as_string(stdin.read(1))
except KeyboardInterrupt:
# Pass along a CTRL-C interrupt.
raise
@ -180,7 +179,7 @@ else:
# use select to make sure there is at least one char to read.
rd,wr,er = select([stdin], [], [], 0.01)
if rd != []:
char = stdin.read(1)
char = as_string(stdin.read(1))
except KeyboardInterrupt:
# Pass along a CTRL-C interrupt.
raise
@ -200,6 +199,7 @@ def line_input(prompt='', stdin=sys.stdin, stdout=sys.stdout):
is useful in modes like the interactive assembler.
"""
stdout.write(prompt)
stdout.flush()
line = ''
while True:
char = getch(stdin)
@ -211,6 +211,7 @@ def line_input(prompt='', stdin=sys.stdin, stdout=sys.stdout):
line = line[:-1]
stdout.write("\r%s\r%s%s" %
(' ' * (len(prompt + line) + 5), prompt, line))
stdout.flush()
elif code == 0x1b: # escape
pass
else:

View File

@ -1,11 +1,16 @@
__version__ = '2.0.0.dev0'
__version__ = '1.3.0.dev0'
import sys
py_version = sys.version_info[:2]
PY3 = py_version[0] == 3
if py_version < (3, 6):
raise RuntimeError('On Python 3, Py65 requires Python 3.6 or later')
if PY3:
if py_version < (3, 4):
raise RuntimeError('On Python 3, Py65 requires Python 3.4 or later')
else:
if py_version < (2, 7):
raise RuntimeError('On Python 2, Py65 requires Python 2.7 or later')
from setuptools import setup, find_packages
@ -19,13 +24,18 @@ CLASSIFIERS = [
'Natural Language :: English',
'Operating System :: POSIX',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Assembly',
'Topic :: Software Development :: Assemblers',
'Topic :: Software Development :: Disassemblers',