def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted([ k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek(state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write(str(vartypes.value_to_uint(old_to_new[old_line]))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # write the indirect line numbers state.basic_state.bytecode.seek(0) while util.skip_to_read(state.basic_state.bytecode, ('\x0e',)) == '\x0e': # get the old g number jumpnum = vartypes.uint_to_value(bytearray(state.basic_state.bytecode.read(2))) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum in state.basic_state.line_numbers: newjump = jumpnum else: linum = get_line_number(state.basic_state.bytecode.tell()) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) state.basic_state.bytecode.seek(-2, 1) state.basic_state.bytecode.write(str(vartypes.value_to_uint(newjump))) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def __init__(self, greet, load): """ Initialise the interpreter session. """ # true if a prompt is needed on next cycle self.prompt = True # input mode is AUTO (used by AUTO) state.basic_state.auto_mode = False # interpreter is executing a command state.basic_state.parse_mode = False # interpreter is waiting for INPUT or LINE INPUT state.basic_state.input_mode = False # previous interpreter mode self.last_mode = False, False # syntax error prompt and EDIT state.basic_state.edit_prompt = False # initialise the display display.init() # initialise the console console.init_mode() # set up event handlers state.basic_state.events = events.Events() # load initial program if load: # on load, accept capitalised versions and default extension with disk.open_native_or_dos_filename(load) as progfile: program.load(progfile) # set up interpreter and memory model state reset.clear() # greeting and keys if greet: console.clear() console.write_line(greeting.format(version=plat.version, free=var.fre())) console.show_keys(True)
def spawn_shell(command): """ Run a SHELL subprocess. """ cmd = shell_command if command: cmd += ' -c "' + command + '"' p = pexpect.spawn(str(cmd)) while True: try: c = state.console_state.keyb.get_char() except error.Break: # ignore ctrl+break in SHELL pass if c == '\b': # BACKSPACE p.send('\x7f') elif c != '': p.send(c) while True: try: c = p.read_nonblocking(1, timeout=0) except: c = '' if c == '' or c == '\n': break elif c == '\r': console.write_line() elif c == '\b': if state.console_state.col != 1: console.set_pos(state.console_state.row, state.console_state.col - 1) else: console.write(c) if c == '' and not p.isalive(): return
def spawn_shell(command): """ Run a SHELL subprocess. """ cmd = shell_command if command: cmd += ' -c "' + command + '"' p = pexpect.spawn(str(cmd)) while True: try: c = state.console_state.keyb.get_char() except error.Break: # ignore ctrl+break in SHELL pass if c == '\b': # BACKSPACE p.send('\x7f') elif c != '': p.send(c) while True: try: c = p.read_nonblocking(1, timeout=0) except: c = '' if c == '' or c == '\n': break elif c == '\r': console.write_line() elif c == '\b': if state.console_state.col != 1: console.set_pos(state.console_state.row, state.console_state.col-1) else: console.write(c) if c == '' and not p.isalive(): return
def show_prompt(): """ Show the Ok or EDIT prompt, unless suppressed. """ if state.basic_state.execute_mode: return if state.basic_state.edit_prompt: linenum, tell = state.basic_state.edit_prompt program.edit(linenum, tell) state.basic_state.edit_prompt = False elif state.basic_state.prompt: console.start_line() console.write_line("Ok\xff")
def show_prompt(self): """ Show the Ok or EDIT prompt, unless suppressed. """ if state.basic_state.parse_mode: return if state.basic_state.edit_prompt: linenum, tell = state.basic_state.edit_prompt program.edit(linenum, tell) state.basic_state.edit_prompt = False elif self.prompt: console.start_line() console.write_line("Ok\xff")
def spawn_shell(command): """ Run a SHELL subprocess. """ global shell_output cmd = shell_command if command: cmd += ' /C "' + command + '"' p = subprocess.Popen( str(cmd).split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True ) outp = threading.Thread(target=process_stdout, args=(p, p.stdout)) outp.daemon = True outp.start() errp = threading.Thread(target=process_stdout, args=(p, p.stderr)) errp.daemon = True errp.start() word = "" while p.poll() == None or shell_output: if shell_output: lines, shell_output = shell_output.split("\r\n"), "" last = lines.pop() for line in lines: # progress visible - keep updating the backend # don't process anything but video events here backend.video.check_events() console.write_line(line) console.write(last) if p.poll() != None: # drain output then break continue try: c = state.console_state.keyb.get_char() except error.Break: pass if c in ("\r", "\n"): # shift the cursor left so that CMD.EXE's echo can overwrite # the command that's already there. Note that Wine's CMD.EXE # doesn't echo the command, so it's overwritten by the output... console.write("\x1D" * len(word)) p.stdin.write(word + "\r\n") word = "" elif c == "\b": # handle backspace if word: word = word[:-1] console.write("\x1D \x1D") elif c != "": # only send to pipe when enter is pressed # needed for Wine and to handle backspace properly word += c console.write(c) outp.join() errp.join()
def spawn_shell(command): """ Run a SHELL subprocess. """ global shell_output cmd = shell_command if command: cmd += ' /C "' + command + '"' p = subprocess.Popen(str(cmd).split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) outp = threading.Thread(target=process_stdout, args=(p, p.stdout)) outp.daemon = True outp.start() errp = threading.Thread(target=process_stdout, args=(p, p.stderr)) errp.daemon = True errp.start() word = '' while p.poll() is None or shell_output: if shell_output: lines, shell_output = shell_output.split('\r\n'), '' last = lines.pop() for line in lines: console.write_line(line) console.write(last) if p.poll() is not None: # drain output then break continue try: c = state.console_state.keyb.get_char() except error.Break: pass if c in ('\r', '\n'): # shift the cursor left so that CMD.EXE's echo can overwrite # the command that's already there. Note that Wine's CMD.EXE # doesn't echo the command, so it's overwritten by the output... console.write('\x1D' * len(word)) p.stdin.write(word + '\r\n') word = '' elif c == '\b': # handle backspace if word: word = word[:-1] console.write('\x1D \x1D') elif c != '': # only send to pipe when enter is pressed # needed for Wine and to handle backspace properly word += c console.write(c) outp.join() errp.join()
def spawn_shell(command): """ Run a SHELL subprocess. """ global shell_output cmd = shell_command if command: cmd += ' /C "' + command + '"' p = subprocess.Popen( str(cmd).split(), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) outp = threading.Thread(target=process_stdout, args=(p, p.stdout)) outp.daemon = True outp.start() errp = threading.Thread(target=process_stdout, args=(p, p.stderr)) errp.daemon = True errp.start() word = '' while p.poll() is None or shell_output: if shell_output: lines, shell_output = shell_output.split('\r\n'), '' last = lines.pop() for line in lines: console.write_line(line) console.write(last) if p.poll() is not None: # drain output then break continue try: c = state.console_state.keyb.get_char() except error.Break: pass if c in ('\r', '\n'): # shift the cursor left so that CMD.EXE's echo can overwrite # the command that's already there. Note that Wine's CMD.EXE # doesn't echo the command, so it's overwritten by the output... console.write('\x1D' * len(word)) p.stdin.write(word + '\r\n') word = '' elif c == '\b': # handle backspace if word: word = word[:-1] console.write('\x1D \x1D') elif c != '': # only send to pipe when enter is pressed # needed for Wine and to handle backspace properly word += c console.write(c) outp.join() errp.join()
def print_greeting(console): """ Print the greeting and the KEY row if we're not running a program. """ import var greeting = ('PC-BASIC {version} {note}\r' '(C) Copyright 2013--2015 Rob Hagemans.\r' '{free} Bytes free') # following GW, don't write greeting for redirected input # or command-line filter run if (not config.get('run') and not config.get('exec') and not config.get('input') and not config.get(0) and not config.get('interface') == 'none'): debugstr = ' [DEBUG mode]' if config.get('debug') else '' params = {'version': plat.version, 'note': debugstr, 'free': var.fre()} console.clear() console.write_line(greeting.format(**params)) console.show_keys(True)
def print_greeting(console): """ Print the greeting and the KEY row if we're not running a program. """ import var greeting = ( 'PC-BASIC {version} {note}\r' '(C) Copyright 2013--2015 Rob Hagemans.\r' '{free} Bytes free') # following GW, don't write greeting for redirected input # or command-line filter run if (not config.get('run') and not config.get('exec') and not config.get('input') and not config.get(0) and not config.get('interface') == 'none'): debugstr = ' [DEBUG mode]' if config.get('debug') else '' params = { 'version': plat.version, 'note': debugstr, 'free': var.fre()} console.clear() console.write_line(greeting.format(**params)) console.show_keys(True)
def input_console(prompt, readvar, newline): """ Read a list of variables for INPUT. """ # readvar is a list of (name, indices) tuples # we return a list of (name, indices, values) tuples while True: console.write(prompt) line = console.wait_screenline(write_endl=newline) inputstream = InputTextFile(line) # read the values and group them and the separators values, seps = zip(*[inputstream.read_var(v) for v in readvar]) # last separator not empty: there were too many values or commas # if there are Nones: there were too few or empty values if (seps[-1] or None in values): # good old Redo! console.write_line('?Redo from start') else: return [r + [v] for r, v in zip(readvar, values)]
def input_console(prompt, readvar, newline): """ Read a list of variables for INPUT. """ # readvar is a list of (name, indices) tuples # we return a list of (name, indices, values) tuples while True: console.write(prompt) line = console.wait_screenline(write_endl=newline) inputstream = InputTextFile(line) # read the values and group them and the separators values, seps = zip(*[inputstream.read_var(v) for v in readvar]) # last separator not empty: there were too many values or commas # if there are Nones: there were too few or empty values if (seps[-1] or None in values): # good old Redo! console.write_line('?Redo from start') else: return [ r + [v] for r, v in zip(readvar, values) ]
def input_console(prompt, readvar, newline): """ Read a list of variables for INPUT. """ # readvar is a list of (name, indices) tuples # we return a list of (name, indices, values) tuples while True: console.write(prompt) line = console.wait_screenline(write_endl=newline) inputstream = InputTextFile(line) # read the values and group them and the separators values, seps = zip(*[inputstream.read_var(v) for v in readvar]) # last separator not empty: there were too many values or commas # earlier separators empty: there were too few values # empty values will be converted to zero by str_to_value_keep # Nene means a conversion error occurred if (seps[-1] or '' in seps[:-1] or None in values): # good old Redo! console.write_line('?Redo from start') else: return [r + [v] for r, v in zip(readvar, values)]
def input_console(prompt, readvar, newline): """ Read a list of variables for INPUT. """ # readvar is a list of (name, indices) tuples # we return a list of (name, indices, values) tuples while True: console.write(prompt) line = console.wait_screenline(write_endl=newline) inputstream = InputTextFile(line) # read the values and group them and the separators values, seps = zip(*[inputstream.read_var(v) for v in readvar]) # last separator not empty: there were too many values or commas # earlier separators empty: there were too few values # empty values will be converted to zero by string_to_number # None means a conversion error occurred if (seps[-1] or '' in seps[:-1] or None in values): # good old Redo! console.write_line('?Redo from start') else: return [r + [v] for r, v in zip(readvar, values)]
def _search(self, trunk_req=None, filetypes_req=None): """ Play until a file header record is found for the given filename. """ try: while True: trunk, filetype, seg, offset, length = self.tapestream.open_read() if ((not trunk_req or trunk.rstrip() == trunk_req.rstrip()) and (not filetypes_req or filetype in filetypes_req)): message = "%s Found." % (trunk + '.' + filetype) if not state.basic_state.run_mode: console.write_line(message) logging.debug(timestamp(self.tapestream.counter()) + message) return trunk, filetype, seg, offset, length else: message = "%s Skipped." % (trunk + '.' + filetype) if not state.basic_state.run_mode: console.write_line(message) logging.debug(timestamp(self.tapestream.counter()) + message) except EndOfTape: # reached end-of-tape without finding appropriate file raise error.RunError(error.DEVICE_TIMEOUT)
def _handle_math_error(e): """ Handle Overflow or Division by Zero. """ if isinstance(e, ValueError): # math domain errors such as SQR(-1) raise error.RunError(error.IFC) elif isinstance(e, OverflowError): math_error = error.OVERFLOW elif isinstance(e, ZeroDivisionError): math_error = error.DIVISION_BY_ZERO else: raise e if state.basic_state.on_error: # also raises exception in error_handle_mode! # in that case, prints a normal error message raise error.RunError(math_error) else: # write a message & continue as normal console.write_line(error.RunError(math_error).message) # return max value for the appropriate float type if e.args and e.args[0] and isinstance(e.args[0], fp.Float): return fp.pack(e.args[0]) return fp.pack(fp.Single.max.copy())
def write(self, s): """ Write string s to SCRN: """ # writes to SCRN files should *not* be echoed do_echo = self.is_master self._col = state.console_state.col # take column 80+overflow into account if state.console_state.overflow: self._col += 1 # only break lines at the start of a new string. width 255 means unlimited width s_width = 0 newline = False # find width of first line in s for c in str(s): if c in ('\r', '\n'): newline = True break if c == '\b': # for lpt1 and files, nonprinting chars are not counted in LPOS; but chr$(8) will take a byte out of the buffer s_width -= 1 elif ord(c) >= 32: # nonprinting characters including tabs are not counted for WIDTH s_width += 1 if (self.width != 255 and state.console_state.row != state.console_state.screen.mode.height and self.col != 1 and self.col - 1 + s_width > self.width and not newline): console.write_line(do_echo=do_echo) self._col = 1 cwidth = state.console_state.screen.mode.width for c in str(s): if self.width <= cwidth and self.col > self.width: console.write_line(do_echo=do_echo) self._col = 1 if self.col <= cwidth or self.width <= cwidth: console.write(c, do_echo=do_echo) if c in ('\n', '\r'): self._col = 1 else: self._col += 1
def write(self, s): """ Write string s to SCRN: """ # writes to SCRN files should *not* be echoed do_echo = self.is_master self._col = state.console_state.col # take column 80+overflow into account if state.console_state.overflow: self._col += 1 # only break lines at the start of a new string. width 255 means unlimited width s_width = 0 newline = False # find width of first line in s for c in str(s): if c in ('\r', '\n'): newline = True break if c == '\b': # for lpt1 and files, nonprinting chars are not counted in LPOS; but chr$(8) will take a byte out of the buffer s_width -= 1 elif ord(c) >= 32: # nonprinting characters including tabs are not counted for WIDTH s_width += 1 if (self.width != 255 and state.console_state.row != state.console_state.screen.mode.height and self.col != 1 and self.col-1 + s_width > self.width and not newline): console.write_line(do_echo=do_echo) self._col = 1 cwidth = state.console_state.screen.mode.width for c in str(s): if self.width <= cwidth and self.col > self.width: console.write_line(do_echo=do_echo) self._col = 1 if self.col <= cwidth or self.width <= cwidth: console.write(c, do_echo=do_echo) if c in ('\n', '\r'): self._col = 1 else: self._col += 1
def files(self, pathmask): """ Write directory listing to console. """ # forward slashes - file not found # GW-BASIC sometimes allows leading or trailing slashes # and then does weird things I don't understand. if b'/' in bytes(pathmask): raise error.RunError(error.FILE_NOT_FOUND) if not self.path: # undefined disk drive: file not found raise error.RunError(error.FILE_NOT_FOUND) drivepath, relpath, mask = self._native_path_elements( pathmask, path_err=error.FILE_NOT_FOUND) path = os.path.join(drivepath, relpath) mask = mask.upper() or b'*.*' # output working dir in DOS format # NOTE: this is always the current dir, not the one being listed dir_elems = [ join_dosname(*short_name(path, e)) for e in self.cwd.split(os.sep) ] console.write_line(self.letter + b':\\' + b'\\'.join(dir_elems)) fils = [] if mask == b'.': dirs = [split_dosname((os.sep + relpath).split(os.sep)[-1:][0])] elif mask == b'..': dirs = [split_dosname((os.sep + relpath).split(os.sep)[-2:][0])] else: all_names = safe(os.listdir, path) dirs = [ filename_from_unicode(n) for n in all_names if os.path.isdir(os.path.join(path, n)) ] fils = [ filename_from_unicode(n) for n in all_names if not os.path.isdir(os.path.join(path, n)) ] # filter according to mask dirs = filter_names(path, dirs + [b'.', b'..'], mask) fils = filter_names(path, fils, mask) if not dirs and not fils: raise error.RunError(error.FILE_NOT_FOUND) # format and print contents output = ([(b'%-8s.%-3s' % (t, e) if (e or not t) else b'%-8s ' % t) + b'<DIR>' for t, e in dirs] + [(b'%-8s.%-3s' % (t, e) if e else b'%-8s ' % t) + b' ' for t, e in fils]) num = state.console_state.screen.mode.width // 20 while len(output) > 0: line = b' '.join(output[:num]) output = output[num:] console.write_line(line) # allow to break during dir listing & show names flowing on screen backend.check_events() console.write_line(b' %d Bytes free' % self.get_free())
def files(self, pathmask): """ Write directory listing to console. """ # forward slashes - file not found # GW-BASIC sometimes allows leading or trailing slashes # and then does weird things I don't understand. if b'/' in bytes(pathmask): raise error.RunError(error.FILE_NOT_FOUND) if not self.path: # undefined disk drive: file not found raise error.RunError(error.FILE_NOT_FOUND) drivepath, relpath, mask = self._native_path_elements(pathmask, path_err=error.FILE_NOT_FOUND) path = os.path.join(drivepath, relpath) mask = mask.upper() or b'*.*' # output working dir in DOS format # NOTE: this is always the current dir, not the one being listed dir_elems = [join_dosname(*short_name(path, e)) for e in self.cwd.split(os.sep)] console.write_line(self.letter + b':\\' + b'\\'.join(dir_elems)) fils = [] if mask == b'.': dirs = [split_dosname((os.sep+relpath).split(os.sep)[-1:][0])] elif mask == b'..': dirs = [split_dosname((os.sep+relpath).split(os.sep)[-2:][0])] else: all_names = safe(os.listdir, path) dirs = [filename_from_unicode(n) for n in all_names if os.path.isdir(os.path.join(path, n))] fils = [filename_from_unicode(n) for n in all_names if not os.path.isdir(os.path.join(path, n))] # filter according to mask dirs = filter_names(path, dirs + [b'.', b'..'], mask) fils = filter_names(path, fils, mask) if not dirs and not fils: raise error.RunError(error.FILE_NOT_FOUND) # format and print contents output = ( [(b'%-8s.%-3s' % (t, e) if (e or not t) else b'%-8s ' % t) + b'<DIR>' for t, e in dirs] + [(b'%-8s.%-3s' % (t, e) if e else b'%-8s ' % t) + b' ' for t, e in fils]) num = state.console_state.screen.mode.width // 20 while len(output) > 0: line = b' '.join(output[:num]) output = output[num:] console.write_line(line) # allow to break during dir listing & show names flowing on screen backend.check_events() console.write_line(b' %d Bytes free' % self.get_free())
def files(pathmask): """ Write directory listing to console. """ # forward slashes - file not found # GW-BASIC sometimes allows leading or trailing slashes # and then does weird things I don't understand. if '/' in str(pathmask): raise error.RunError(53) drive, drivepath, relpath, mask = native_path_elements(pathmask, err=53) path = os.path.join(drivepath, relpath) mask = mask.upper() or '*.*' # output working dir in DOS format # NOTE: this is always the current dir, not the one being listed dir_elems = [join_dosname(*short_name(path, e)) for e in state.io_state.drive_cwd[drive].split(os.sep)] console.write_line(drive + ':\\' + '\\'.join(dir_elems)) fils = '' if mask == '.': dirs = [split_dosname(dossify((os.sep+relpath).split(os.sep)[-1:][0]))] elif mask == '..': dirs = [split_dosname(dossify((os.sep+relpath).split(os.sep)[-2:][0]))] else: all_names = safe(os.listdir, path) dirs = [n for n in all_names if os.path.isdir(os.path.join(path, n))] fils = [n for n in all_names if not os.path.isdir(os.path.join(path, n))] # filter according to mask dirs = filter_names(path, dirs + ['.', '..'], mask) fils = filter_names(path, fils, mask) if not dirs and not fils: raise error.RunError(53) # format and print contents output = ( [('%-8s.%-3s' % (t, e) if (e or not t) else '%-8s ' % t) + '<DIR>' for t, e in dirs] + [('%-8s.%-3s' % (t, e) if e else '%-8s ' % t) + ' ' for t, e in fils]) num = state.console_state.screen.mode.width // 20 while len(output) > 0: line = ' '.join(output[:num]) output = output[num:] console.write_line(line) # allow to break during dir listing & show names flowing on screen backend.check_events() console.write_line(' %d Bytes free' % disk_free(path))
def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted( [k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek( state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write( str(vartypes.value_to_uint(old_to_new[old_line]))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[ old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # write the indirect line numbers state.basic_state.bytecode.seek(0) while util.skip_to_read(state.basic_state.bytecode, ('\x0e', )) == '\x0e': # get the old g number jumpnum = vartypes.uint_to_value( bytearray(state.basic_state.bytecode.read(2))) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum in state.basic_state.line_numbers: newjump = jumpnum else: linum = get_line_number(state.basic_state.bytecode.tell()) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) state.basic_state.bytecode.seek(-2, 1) state.basic_state.bytecode.write(str(vartypes.value_to_uint(newjump))) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def bluescreen(e): """ Display a modal exception message. """ state.console_state.screen.screen(0, 0, 0, 0, new_width=80) console.clear() console.init_mode() exc_type, exc_value, exc_traceback = sys.exc_info() # log the standard python error logging.error(''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))) # format the error more readably on the screen state.console_state.screen.set_border(4) state.console_state.screen.set_attr(0x70) console.write_line('EXCEPTION') state.console_state.screen.set_attr(15) if state.basic_state.run_mode: state.basic_state.bytecode.seek(-1, 1) program.edit(program.get_line_number(state.basic_state.bytecode.tell()), state.basic_state.bytecode.tell()) console.write_line('\n') else: state.basic_state.direct_line.seek(0) console.write_line(str(tokenise.detokenise_compound_statement(state.basic_state.direct_line)[0])+'\n') stack = traceback.extract_tb(exc_traceback) for s in stack[-4:]: stack_line = '{0}:{1}, {2}'.format( os.path.split(s[0])[-1], s[1], s[2]) stack_line_2 = ' {0}'.format(s[3]) state.console_state.screen.set_attr(15) console.write_line(stack_line) state.console_state.screen.set_attr(7) console.write_line(stack_line_2) exc_message = traceback.format_exception_only(exc_type, exc_value)[0] state.console_state.screen.set_attr(15) console.write('{0}:'.format(exc_type.__name__)) state.console_state.screen.set_attr(7) console.write_line(' {0}'.format(str(exc_value))) state.console_state.screen.set_attr(0x70) console.write_line( '\nThis is a bug in PC-BASIC.') state.console_state.screen.set_attr(7) console.write( 'Sorry about that. Please send the above messages to the bugs forum\nby e-mail to ') state.console_state.screen.set_attr(15) console.write( '*****@*****.**') state.console_state.screen.set_attr(7) console.write( ' or by filing a bug\nreport at ') state.console_state.screen.set_attr(15) console.write( 'https://github.com/robhagemans/pcbasic/issues') state.console_state.screen.set_attr(7) console.write_line( '. Please include') console.write_line('as much information as you can about what you were doing and how this happened.') console.write_line('Thank you!') state.console_state.screen.set_attr(7) flow.set_pointer(False)
def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted([ k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek(state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write(str(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(old_to_new[old_line])))) # write the indirect line numbers ins = state.basic_state.bytecode ins.seek(0) while util.skip_to_read(ins, (tk.T_UINT,)) == tk.T_UINT: # get the old g number jumpnum = vartypes.integer_to_int_unsigned(vartypes.bytes_to_integer(ins.read(2))) # handle exception for ERROR GOTO if jumpnum == 0: pos = ins.tell() # skip line number token ins.seek(-3, 1) if util.backskip_white(ins) == tk.GOTO and util.backskip_white(ins) == tk.ERROR: ins.seek(pos) continue ins.seek(pos) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum not in state.basic_state.line_numbers: linum = get_line_number(ins.tell()-1) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) newjump = jumpnum ins.seek(-2, 1) ins.write(str(vartypes.integer_to_bytes(vartypes.int_to_integer_unsigned(newjump)))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def renum(new_line, start_line, step): """ Renumber stored program. """ new_line = 10 if new_line is None else new_line start_line = 0 if start_line is None else start_line step = 10 if step is None else step # get a sorted list of line numbers keys = sorted( [k for k in state.basic_state.line_numbers.keys() if k >= start_line]) # assign the new numbers old_to_new = {} for old_line in keys: if old_line < 65535 and new_line > 65529: raise error.RunError(error.IFC) if old_line == 65536: break old_to_new[old_line] = new_line state.basic_state.last_stored = new_line new_line += step # write the new numbers for old_line in old_to_new: state.basic_state.bytecode.seek( state.basic_state.line_numbers[old_line]) # skip the \x00\xC0\xDE & overwrite line number state.basic_state.bytecode.read(3) state.basic_state.bytecode.write( str( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(old_to_new[old_line])))) # write the indirect line numbers ins = state.basic_state.bytecode ins.seek(0) while util.skip_to_read(ins, (tk.T_UINT, )) == tk.T_UINT: # get the old g number jumpnum = vartypes.integer_to_int_unsigned( vartypes.bytes_to_integer(ins.read(2))) # handle exception for ERROR GOTO if jumpnum == 0: pos = ins.tell() # skip line number token ins.seek(-3, 1) if util.backskip_white(ins) == tk.GOTO and util.backskip_white( ins) == tk.ERROR: ins.seek(pos) continue ins.seek(pos) try: newjump = old_to_new[jumpnum] except KeyError: # not redefined, exists in program? if jumpnum not in state.basic_state.line_numbers: linum = get_line_number(ins.tell() - 1) console.write_line('Undefined line ' + str(jumpnum) + ' in ' + str(linum)) newjump = jumpnum ins.seek(-2, 1) ins.write( str( vartypes.integer_to_bytes( vartypes.int_to_integer_unsigned(newjump)))) # rebuild the line number dictionary new_lines = {} for old_line in old_to_new: new_lines[ old_to_new[old_line]] = state.basic_state.line_numbers[old_line] del state.basic_state.line_numbers[old_line] state.basic_state.line_numbers.update(new_lines) # stop running if we were flow.set_pointer(False) # reset loop stacks state.basic_state.gosub_return = [] state.basic_state.for_next_stack = [] state.basic_state.while_wend_stack = [] # renumber error handler if state.basic_state.on_error: state.basic_state.on_error = old_to_new[state.basic_state.on_error] # renumber event traps for handler in state.basic_state.events.all: if handler.gosub: handler.set_jump(old_to_new[handler.gosub])
def write_line(self, inp=''): """ Write a string to the screen and follow by CR. """ self.write(inp) console.write_line(do_echo=self.is_master)
def write_line(self, inp=''): """ Write a string to the screen and follow by CR. """ self.write(inp) console.write_line(do_echo=(self.number==0))