Fix interactive assembly on Python 3

Closes #81
Closes #78
Closes #65
Closes #64
Closes #63
This commit is contained in:
Mike Naberezny 2024-04-12 12:24:29 -07:00
parent 8dce37f6b8
commit 1870d65982
6 changed files with 137 additions and 13 deletions

View File

@ -21,11 +21,14 @@ jobs:
run: python -V run: python -V
- name: Install dependencies - name: Install dependencies
run: $PIP install setuptools run: $PIP install setuptools pexpect
- name: Run the tests - name: Run the tests
run: python setup.py test -q 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: build_py34:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
container: python:3.4 container: python:3.4
@ -44,11 +47,14 @@ jobs:
run: python -V run: python -V
- name: Install dependencies - name: Install dependencies
run: $PIP install setuptools run: $PIP install setuptools pexpect
- name: Run the tests - name: Run the tests
run: python setup.py test -q 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: build_py35:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
container: python:3.5 container: python:3.5
@ -62,11 +68,14 @@ jobs:
run: python -V run: python -V
- name: Install dependencies - name: Install dependencies
run: $PIP install setuptools run: $PIP install setuptools pexpect
- name: Run the tests - name: Run the tests
run: python setup.py test -q 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: build_py3x:
strategy: strategy:
fail-fast: false fail-fast: false
@ -88,7 +97,10 @@ jobs:
run: python -V run: python -V
- name: Install dependencies - name: Install dependencies
run: $PIP install setuptools run: $PIP install setuptools pexpect
- name: Run the tests - name: Run the tests
run: python setup.py test -q 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

@ -5,6 +5,8 @@
dropped when pasting in larger amounts of text. This makes it possible dropped when pasting in larger amounts of text. This makes it possible
to paste programs into EhBASIC and Taliforth. Patch by SamCoVT. to paste programs into EhBASIC and Taliforth. Patch by SamCoVT.
- Fixed interactive assembly on Python 3.
- Fixed regular expression warnings on Python 3.12. - Fixed regular expression warnings on Python 3.12.
- The ``fill`` command in the monitor now shows an error message if an - The ``fill`` command in the monitor now shows an error message if an

View File

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

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

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