Skip to content

[WIP] Transition to AsyncIO #244

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pcbasic/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
(c) 2013--2023 Rob Hagemans
This file is released under the GNU GPL version 3 or later.
"""
import asyncio

from .main import main, script_entry_point_guard

with script_entry_point_guard():
main()
asyncio.run(main())

17 changes: 8 additions & 9 deletions pcbasic/basic/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
(c) 2013--2023 Rob Hagemans
This file is released under the GNU GPL version 3 or later.
"""

import os
import io

Expand Down Expand Up @@ -81,7 +80,7 @@ def bind_file(self, file_name_or_object, name=None, create=False):
# not resolved, try to use/create as internal name
return NameWrapper(self._impl.codepage, file_name_or_object)

def execute(self, command, as_type=None):
async def execute(self, command, as_type=None):
"""Execute a BASIC statement."""
self.start()
if as_type is None:
Expand All @@ -92,7 +91,7 @@ def execute(self, command, as_type=None):
for cmd in command.splitlines():
if isinstance(cmd, text_type):
cmd = self._impl.codepage.unicode_to_bytes(cmd)
self._impl.execute(cmd)
await self._impl.execute(cmd)
self.remove_pipes(output_streams=output)
return output.getvalue()

Expand All @@ -106,13 +105,13 @@ def remove_pipes(self, input_streams=None, output_streams=None):
self.start()
self._impl.io_streams.remove_pipes(input_streams, output_streams)

def evaluate(self, expression):
async def evaluate(self, expression):
"""Evaluate a BASIC expression."""
self.start()
with self._impl.io_streams.activate():
if isinstance(expression, text_type):
expression = self._impl.codepage.unicode_to_bytes(expression)
return self._impl.evaluate(expression)
return await self._impl.evaluate(expression)

def set_variable(self, name, value):
"""Set a variable in memory."""
Expand Down Expand Up @@ -153,16 +152,16 @@ def get_pixels(self):
self.start()
return self._impl.display.vpage.pixels[:, :].to_rows()

def greet(self):
async def greet(self):
"""Emit the interpreter greeting and show the key bar."""
self.start()
self._impl.execute(implementation.GREETING)
await self._impl.execute(implementation.GREETING)

def interact(self):
async def interact(self):
"""Interactive interpreter session."""
self.start()
with self._impl.io_streams.activate():
self._impl.interact()
await self._impl.interact()

def suspend(self, session_filename):
"""Save session object to file."""
Expand Down
32 changes: 16 additions & 16 deletions pcbasic/basic/basicevents.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,18 @@ def pen_(self, args):
command, = args
self.command(self.pen, command)

def strig_(self, args):
async def strig_(self, args):
"""STRIG: switch on/off fire button event handling."""
num = values.to_int(next(args))
command, = args
num = values.to_int(await anext(args))
command, = [_ async for _ in args]
error.range_check(0, 255, num)
if num in (0, 2, 4, 6):
self.command(self.strig[num//2], command)

def com_(self, args):
async def com_(self, args):
"""COM: switch on/off serial port event handling."""
num = values.to_int(next(args))
command, = args
num = values.to_int(await anext(args))
command, = [_ async for _ in args]
error.range_check(0, 2, num)
if num > 0:
self.command(self.com[num-1], command)
Expand All @@ -138,11 +138,11 @@ def timer_(self, args):
command, = args
self.command(self.timer, command)

def key_(self, args):
async def key_(self, args):
"""KEY: switch on/off keyboard events."""
num = values.to_int(next(args))
num = values.to_int(await anext(args))
error.range_check(0, 255, num)
command, = args
command, = [_ async for _ in args]
# others are ignored
if num >= 1 and num <= 20:
self.command(self.key[num-1], command)
Expand All @@ -152,16 +152,16 @@ def play_(self, args):
command, = args
self.command(self.play, command)

def on_event_gosub_(self, args):
async def on_event_gosub_(self, args):
"""ON .. GOSUB: define event trapping subroutine."""
token = next(args)
num = next(args)
jumpnum = next(args)
token = await anext(args)
num = await anext(args)
jumpnum = await anext(args)
print(token, num, jumpnum)
if jumpnum == 0:
jumpnum = None
elif jumpnum not in self._program.line_numbers:
raise error.BASICError(error.UNDEFINED_LINE_NUMBER)
list(args)
if token == tk.KEY:
keynum = values.to_int(num)
error.range_check(1, len(self.key), keynum)
Expand Down Expand Up @@ -232,10 +232,10 @@ def check_input(self, signal):
"""Check and trigger PLAY (music queue) events."""
play_now = self._sound.tones_waiting()
if self._sound.multivoice:
if (self.last > play_now and play_now < self.trig):
if self.last > play_now and play_now < self.trig:
self.trigger()
else:
if (self.last >= self.trig and play_now < self.trig):
if self.last >= self.trig and play_now < self.trig:
self.trigger()
self.last = play_now
return False
Expand Down
14 changes: 7 additions & 7 deletions pcbasic/basic/clock.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ def get_time_ms(self):

def timer_(self, args):
"""TIMER: get clock ticks since midnight."""
list(args)
# precision of GWBASIC TIMER is about 1/20 of a second
timer = float(self.get_time_ms()//50) / 20.
return self._values.new_single().from_value(timer)

def time_(self, args):
async def time_(self, args):
"""TIME: Set the system time offset."""
timestr = values.next_string(args)
list(args)
timestr = await values.next_string(args)
# noinspection PyStatementEffect
[e async for e in args]
# allowed formats: hh hh:mm hh:mm:ss where hh 0-23, mm 0-59, ss 0-59
now = datetime.datetime.now() + self.time_offset
strlist = timestr.replace(b'.', b':').split(b':')
Expand All @@ -60,9 +60,9 @@ def time_(self, args):
timelist[0], timelist[1], timelist[2], now.microsecond)
self.time_offset += newtime - now

def date_(self, args):
async def date_(self, args):
"""DATE: Set the system date offset."""
datestr = values.next_string(args)
datestr = await values.next_string(args)
# allowed formats:
# mm/dd/yy or mm-dd-yy mm 0--12 dd 0--31 yy 80--00--77
# mm/dd/yyyy or mm-dd-yyyy yyyy 1980--2099
Expand Down Expand Up @@ -91,7 +91,7 @@ def date_(self, args):
)
except ValueError:
raise error.BASICError(error.IFC)
list(args)
[e async for e in args]
self.time_offset += newtime - now

def time_fn_(self, args):
Expand Down
2 changes: 2 additions & 0 deletions pcbasic/basic/codepage.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ def __init__(self, stream, codepage, preserve=()):
"""Set up codec."""
self._conv = codepage.get_converter(preserve)
self._stream = stream
self.writable = True

def write(self, s):
"""Write bytes to codec stream."""
Expand All @@ -253,6 +254,7 @@ def __init__(self, stream, codepage):
self._codepage = codepage
self._stream = stream
self._buffer = b''
self.writable = False

def read(self, n=-1):
"""Read n bytes from stream with codepage conversion."""
Expand Down
30 changes: 15 additions & 15 deletions pcbasic/basic/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,22 @@ def set_pos(self, row, col):
##########################################################################
# interaction

def read_line(self, prompt=b'', write_endl=True, is_input=False):
async def read_line(self, prompt=b'', write_endl=True, is_input=False):
"""Enter interactive mode and read string from console."""
self.write(prompt)
await self.write(prompt)
# disconnect the wrap between line with the prompt and previous line
if self._text_screen.current_row > 1:
self._text_screen.set_wrap(self._text_screen.current_row-1, False)
# read from start in direct entry mode, from prompt in input mode
prompt_width = 0 if not is_input else self._text_screen.current_col - 1
try:
# give control to user for interactive mode
prompt_row, left, right = self._interact(prompt_width, is_input=is_input)
prompt_row, left, right = await self._interact(prompt_width, is_input=is_input)
except error.Break:
# x0E CR LF is printed to redirects at break
self._io_streams.write(b'\x0e')
# while only a line break appears on the console
self.write_line()
await self.write_line()
raise
# get contents of the logical line
if not is_input:
Expand All @@ -142,13 +142,13 @@ def read_line(self, prompt=b'', write_endl=True, is_input=False):
self._text_screen.move_to_end()
# echo the CR, if requested
if write_endl:
self.write_line()
await self.write_line()
# to the parser/INPUT, only the first 255 chars are returned
# with trailing whitespace removed
outstr = outstr[:255].rstrip(b' \t\n')
return outstr

def _interact(self, prompt_width, is_input=False):
async def _interact(self, prompt_width, is_input=False):
"""Manage the interactive mode."""
# force cursor visibility in all case
self._cursor.set_override(True)
Expand All @@ -161,7 +161,7 @@ def _interact(self, prompt_width, is_input=False):
furthest_right = self._text_screen.current_col
while True:
# get one e-ASCII or dbcs code
d = self._keyboard.get_fullchar_block()
d = await self._keyboard.get_fullchar_block()
if not d:
# input stream closed
raise error.Exit()
Expand All @@ -179,7 +179,7 @@ def _interact(self, prompt_width, is_input=False):
break
elif d == b'\a':
# BEL, CTRL+G
self._sound.beep()
await self._sound.beep()
elif d == b'\b':
# BACKSPACE, CTRL+H
self._text_screen.backspace(start_row, furthest_left)
Expand Down Expand Up @@ -264,7 +264,7 @@ def _set_overwrite_mode(self, new_overwrite):
##########################################################################
# output

def write(self, s, do_echo=True):
async def write(self, s, do_echo=True):
"""Write a string to the screen at the current position."""
if not s:
# don't disconnect line wrap if no output
Expand Down Expand Up @@ -293,7 +293,7 @@ def write(self, s, do_echo=True):
self._text_screen.newline(wrap=False)
elif c == b'\a':
# BEL
self._sound.beep()
await self._sound.beep()
elif c == b'\x0B':
# HOME
self._text_screen.set_pos(1, 1, scroll_ok=False)
Expand All @@ -318,11 +318,11 @@ def write(self, s, do_echo=True):
last = c
self._text_screen.write_chars(b''.join(out_chars), do_scroll_down=False)

def write_line(self, s=b'', do_echo=True):
async def write_line(self, s=b'', do_echo=True):
"""Write a string to the screen and end with a newline."""
self.write(b'%s\r' % (s,), do_echo)
await self.write(b'%s\r' % (s,), do_echo)

def list_line(self, line, newline, set_text_position=None):
async def list_line(self, line, newline, set_text_position=None):
"""Print a line from a program listing or EDIT prompt."""
# no wrap if 80-column line, clear row before printing.
# replace LF CR with LF
Expand All @@ -335,9 +335,9 @@ def list_line(self, line, newline, set_text_position=None):
# when using LIST, we *do* print LF as a wrap
self._text_screen.newline(wrap=True)
self._text_screen.clear_line(self._text_screen.current_row, 1)
self.write(l)
await self.write(l)
if newline:
self.write_line()
await self.write_line()
# remove wrap after 80-column program line
if len(line) == self.width and self._text_screen.current_row > 2:
self._text_screen.set_wrap(self._text_screen.current_row-2, False)
Expand Down
Loading