def run_once(): """ Read-eval-print loop: run once. """ try: while True: state.basic_state.last_mode = state.basic_state.execute_mode, state.basic_state.auto_mode if state.basic_state.execute_mode: try: # may raise Break backend.check_events() handle_basic_events() if not statements.parse_statement(): state.basic_state.execute_mode = False except error.Break as e: handle_break(e) elif state.basic_state.auto_mode: try: # auto step, checks events auto_step() except error.Break: state.basic_state.auto_mode = False else: show_prompt() try: # input loop, checks events line = console.wait_screenline(from_start=True, alt_replace=True) state.basic_state.prompt = not store_line(line) except error.Break: state.basic_state.prompt = False continue # change loop modes if switch_mode(): break except error.RunError as e: handle_error(e) state.basic_state.prompt = True
def paint(self, lcoord, pattern, c, border, background): """ Fill an area defined by a border attribute with a tiled pattern. """ # 4-way scanline flood fill: http://en.wikipedia.org/wiki/Flood_fill # flood fill stops on border colour in all directions; it also stops on scanlines in fill_colour # pattern tiling stops at intervals that equal the pattern to be drawn, unless this pattern is # also equal to the background pattern. c, border = self.get_attr_index(c), self.get_attr_index(border) solid = pattern == None if not solid: tile = self.screen.mode.build_tile(pattern) if pattern else None back = self.screen.mode.build_tile(background) if background else None else: tile, back = [[c] * 8], None bound_x0, bound_y0, bound_x1, bound_y1 = self.get_view() x, y = self.view_coords(*self.get_window_physical(*lcoord)) line_seed = [(x, x, y, 0)] # paint nothing if seed is out of bounds if x < bound_x0 or x > bound_x1 or y < bound_y0 or y > bound_y1: return self.last_point = x, y # paint nothing if we start on border attrib if self.screen.get_pixel(x, y) == border: return while len(line_seed) > 0: # consider next interval x_start, x_stop, y, ydir = line_seed.pop() # extend interval as far as it goes to left and right x_left = x_start - len(self.screen.get_until(x_start - 1, bound_x0 - 1, y, border)) x_right = x_stop + len(self.screen.get_until(x_stop + 1, bound_x1 + 1, y, border)) # check next scanlines and add intervals to the list if ydir == 0: if y + 1 <= bound_y1: line_seed = self.check_scanline(line_seed, x_left, x_right, y + 1, c, tile, back, border, 1) if y - 1 >= bound_y0: line_seed = self.check_scanline(line_seed, x_left, x_right, y - 1, c, tile, back, border, -1) else: # check the same interval one scanline onward in the same direction if y + ydir <= bound_y1 and y + ydir >= bound_y0: line_seed = self.check_scanline(line_seed, x_left, x_right, y + ydir, c, tile, back, border, ydir) # check any bit of the interval that was extended one scanline backward # this is where the flood fill goes around corners. if y - ydir <= bound_y1 and y - ydir >= bound_y0: line_seed = self.check_scanline( line_seed, x_left, x_start - 1, y - ydir, c, tile, back, border, -ydir ) line_seed = self.check_scanline( line_seed, x_stop + 1, x_right, y - ydir, c, tile, back, border, -ydir ) # draw the pixels for the current interval if solid: self.screen.fill_interval(x_left, x_right, y, tile[0][0]) else: interval = tile_to_interval(x_left, x_right, y, tile) self.screen.put_interval(self.screen.apagenum, x_left, y, interval) # show progress if y % 4 == 0: backend.check_events() self.last_attr = c
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 run_once(): """ Read-eval-print loop: run once. """ try: while True: state.basic_state.last_mode = state.basic_state.execute_mode, state.basic_state.auto_mode if state.basic_state.execute_mode: try: # may raise Break backend.check_events() handle_basic_events() # returns True if more statements to parse if not statements.parse_statement(): state.basic_state.execute_mode = False except error.RunError as e: trap_error(e) except error.Break as e: # ctrl-break stops foreground and background sound state.console_state.sound.stop_all_sound() handle_break(e) elif state.basic_state.auto_mode: try: # auto step, checks events auto_step() except error.Break: # ctrl+break, ctrl-c both stop background sound state.console_state.sound.stop_all_sound() state.basic_state.auto_mode = False else: show_prompt() try: # input loop, checks events line = console.wait_screenline(from_start=True) state.basic_state.prompt = not store_line(line) except error.Break: state.console_state.sound.stop_all_sound() state.basic_state.prompt = False continue # change loop modes if switch_mode(): break except error.RunError as e: handle_error(e) state.basic_state.prompt = True except error.Exit: raise except Exception as e: if debug.debug_mode: raise bluescreen(e)
def list_line(line, newline=True): """ Print a line from a program listing or EDIT prompt. """ # no wrap if 80-column line, clear row before printing. # flow of listing is visible on screen backend.check_events() cuts = line.split('\n') for i, l in enumerate(cuts): # clear_line looks back along wraps, use clear_rest_of_line instead clear_rest_of_line(state.console_state.row, 1) write(str(l)) if i != len(cuts)-1: write('\n') if newline: write_line() # remove wrap after 80-column program line if len(line) == state.console_state.screen.mode.width and state.console_state.row > 2: state.console_state.screen.apage.row[state.console_state.row-3].wrap = False
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 list_line(line, newline=True): """ Print a line from a program listing or EDIT prompt. """ # no wrap if 80-column line, clear row before printing. # flow of listing is visible on screen backend.check_events() cuts = line.split('\n') for i, l in enumerate(cuts): # clear_line looks back along wraps, use clear_rest_of_line instead clear_rest_of_line(state.console_state.row, 1) write(str(l)) if i != len(cuts) - 1: write('\n') if newline: write_line() # remove wrap after 80-column program line if len( line ) == state.console_state.screen.mode.width and state.console_state.row > 2: state.console_state.screen.apage.row[state.console_state.row - 3].wrap = False
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 paint(self, lcoord, pattern, c, border, background): """ Fill an area defined by a border attribute with a tiled pattern. """ # 4-way scanline flood fill: http://en.wikipedia.org/wiki/Flood_fill # flood fill stops on border colour in all directions; it also stops on scanlines in fill_colour # pattern tiling stops at intervals that equal the pattern to be drawn, unless this pattern is # also equal to the background pattern. c, border = self.get_attr_index(c), self.get_attr_index(border) solid = (pattern is None) if not solid: tile = self.screen.mode.build_tile(pattern) if pattern else None back = self.screen.mode.build_tile( background) if background else None else: tile, back = [[c] * 8], None bound_x0, bound_y0, bound_x1, bound_y1 = self.get_view() x, y = self.view_coords(*self.get_window_physical(*lcoord)) line_seed = [(x, x, y, 0)] # paint nothing if seed is out of bounds if x < bound_x0 or x > bound_x1 or y < bound_y0 or y > bound_y1: return self.last_point = x, y # paint nothing if we start on border attrib if self.screen.get_pixel(x, y) == border: return while len(line_seed) > 0: # consider next interval x_start, x_stop, y, ydir = line_seed.pop() # extend interval as far as it goes to left and right x_left = x_start - len( self.screen.get_until(x_start - 1, bound_x0 - 1, y, border)) x_right = x_stop + len( self.screen.get_until(x_stop + 1, bound_x1 + 1, y, border)) # check next scanlines and add intervals to the list if ydir == 0: if y + 1 <= bound_y1: line_seed = self.check_scanline(line_seed, x_left, x_right, y + 1, c, tile, back, border, 1) if y - 1 >= bound_y0: line_seed = self.check_scanline(line_seed, x_left, x_right, y - 1, c, tile, back, border, -1) else: # check the same interval one scanline onward in the same direction if y + ydir <= bound_y1 and y + ydir >= bound_y0: line_seed = self.check_scanline(line_seed, x_left, x_right, y + ydir, c, tile, back, border, ydir) # check any bit of the interval that was extended one scanline backward # this is where the flood fill goes around corners. if y - ydir <= bound_y1 and y - ydir >= bound_y0: line_seed = self.check_scanline(line_seed, x_left, x_start - 1, y - ydir, c, tile, back, border, -ydir) line_seed = self.check_scanline(line_seed, x_stop + 1, x_right, y - ydir, c, tile, back, border, -ydir) # draw the pixels for the current interval if solid: self.screen.fill_interval(x_left, x_right, y, tile[0][0]) else: interval = tile_to_interval(x_left, x_right, y, tile) self.screen.put_interval(self.screen.apagenum, x_left, y, interval) # show progress if y % 4 == 0: backend.check_events() self.last_attr = c