def parse_variable(ins): """ Helper function: parse a variable or array element. """ name = util.parse_scalar(ins) indices = [] if util.skip_white_read_if(ins, ('[', '(')): # it's an array, read indices while True: indices.append(vartypes.pass_int_unpack(parse_expression(ins))) if not util.skip_white_read_if(ins, (',',)): break util.require_read(ins, (']', ')')) return name, indices
def parse_variable(ins): """ Helper function: parse a variable or array element. """ name = util.parse_scalar(ins) indices = [] if util.skip_white_read_if(ins, ('[', '(')): # it's an array, read indices while True: indices.append(vartypes.pass_int_unpack(parse_expression(ins))) if not util.skip_white_read_if(ins, (',', )): break util.require_read(ins, (']', ')')) return name, indices
def value_fn(ins): """ FN: get value of user-defined function. """ fnname = util.get_var_name(ins) try: varnames, fncode = state.basic_state.functions[fnname] except KeyError: raise error.RunError(error.UNDEFINED_USER_FUNCTION) # save existing vars varsave = {} for name in varnames: if name in state.basic_state.variables: # copy the *value* - set_var is in-place it's safe for FOR loops varsave[name] = state.basic_state.variables[name][:] # read variables if util.skip_white_read_if(ins, ('(',)): exprs = parse_expr_list(ins, len(varnames), err=error.STX) if None in exprs: raise error.RunError(error.STX) for i in range(len(varnames)): var.set_var(varnames[i], exprs[i]) util.require_read(ins, (')',)) # execute the code fns = StringIO(fncode) fns.seek(0) value = parse_expression(fns) # restore existing vars for name in varsave: # re-assign the stored value state.basic_state.variables[name][:] = varsave[name] return value
def value_point(ins): """ POINT: get pixel attribute at screen location. """ util.require_read(ins, ('(',)) arg0 = parse_expression(ins) screen = state.console_state.screen if util.skip_white_read_if(ins, (',',)): # two-argument mode arg1 = parse_expression(ins) util.require_read(ins, (')',)) if screen.mode.is_text_mode: raise error.RunError(error.IFC) return vartypes.int_to_integer_signed(screen.drawing.point( (fp.unpack(vartypes.pass_single(arg0)), fp.unpack(vartypes.pass_single(arg1)), False))) else: # single-argument mode util.require_read(ins, (')',)) try: x, y = screen.drawing.last_point fn = vartypes.pass_int_unpack(arg0) if fn == 0: return vartypes.int_to_integer_signed(x) elif fn == 1: return vartypes.int_to_integer_signed(y) elif fn == 2: fx, _ = screen.drawing.get_window_logical(x, y) return fp.pack(fx) elif fn == 3: _, fy = screen.drawing.get_window_logical(x, y) return fp.pack(fy) except AttributeError: return vartypes.null('%')
def value_point(ins): """ POINT: get pixel attribute at screen location. """ util.require_read(ins, ('(', )) arg0 = parse_expression(ins) screen = state.console_state.screen if util.skip_white_read_if(ins, (',', )): # two-argument mode arg1 = parse_expression(ins) util.require_read(ins, (')', )) if screen.mode.is_text_mode: raise error.RunError(error.IFC) return vartypes.int_to_integer_signed( screen.drawing.point( (fp.unpack(vartypes.pass_single(arg0)), fp.unpack(vartypes.pass_single(arg1)), False))) else: # single-argument mode util.require_read(ins, (')', )) try: x, y = screen.drawing.last_point fn = vartypes.pass_int_unpack(arg0) if fn == 0: return vartypes.int_to_integer_signed(x) elif fn == 1: return vartypes.int_to_integer_signed(y) elif fn == 2: fx, _ = screen.drawing.get_window_logical(x, y) return fp.pack(fx) elif fn == 3: _, fy = screen.drawing.get_window_logical(x, y) return fp.pack(fy) except AttributeError: return vartypes.null('%')
def value_erdev(ins): """ ERDEV$: device error string; not implemented. """ logging.warning("ERDEV or ERDEV$ function not implemented.") if util.skip_white_read_if(ins, ('$', )): return vartypes.null('$') else: return vartypes.null('%')
def value_fn(ins): """ FN: get value of user-defined function. """ fnname = util.get_var_name(ins) try: varnames, fncode = state.basic_state.functions[fnname] except KeyError: raise error.RunError(error.UNDEFINED_USER_FUNCTION) # save existing vars varsave = {} for name in varnames: if name in state.basic_state.variables: # copy the *value* - set_var is in-place it's safe for FOR loops varsave[name] = state.basic_state.variables[name][:] # read variables if util.skip_white_read_if(ins, ('(', )): exprs = parse_expr_list(ins, len(varnames), err=error.STX) if None in exprs: raise error.RunError(error.STX) for i in range(len(varnames)): var.set_var(varnames[i], exprs[i]) util.require_read(ins, (')', )) # execute the code fns = StringIO(fncode) fns.seek(0) value = parse_expression(fns) # restore existing vars for name in varsave: # re-assign the stored value state.basic_state.variables[name][:] = varsave[name] return value
def value_erdev(ins): """ ERDEV$: device error string; not implemented. """ logging.warning("ERDEV or ERDEV$ function not implemented.") if util.skip_white_read_if(ins, ('$',)): return vartypes.null('$') else: return vartypes.null('%')
def parse_file_number(ins, file_mode='IOAR'): """ Helper function: parse a file number and retrieve the file object. """ screen = None if util.skip_white_read_if(ins, ('#', )): number = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(0, 255, number) screen = devices.get_file(number, file_mode) util.require_read(ins, (',', )) return screen
def parse_file_number(ins, file_mode='IOAR'): """ Helper function: parse a file number and retrieve the file object. """ screen = None if util.skip_white_read_if(ins, ('#',)): number = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(0, 255, number) screen = devices.get_file(number, file_mode) util.require_read(ins, (',',)) return screen
def value_fn(ins): """ FN: get value of user-defined function. """ fnname = util.parse_scalar(ins) # recursion is not allowed as there's no way to terminate it if fnname in state.basic_state.user_function_parsing: raise error.RunError(error.OUT_OF_MEMORY) state.basic_state.user_function_parsing.add(fnname) try: varnames, fncode = state.basic_state.functions[fnname] except KeyError: raise error.RunError(error.UNDEFINED_USER_FUNCTION) # save existing vars varsave = {} for name in varnames: if name in state.basic_state.variables: # copy the *value* - set_var is in-place it's safe for FOR loops varsave[name] = state.basic_state.variables[name][:] # read variables if util.skip_white_read_if(ins, ('(',)): exprs = [] while True: exprs.append(parse_expression(ins)) if not util.skip_white_read_if(ins, (',',)): break if len(exprs) != len(varnames): raise error.RunError(error.STX) for name, value in zip(varnames, exprs): var.set_scalar(name, value) util.require_read(ins, (')',)) # execute the code fns = StringIO(fncode) fns.seek(0) value = parse_expression(fns) # restore existing vars for name in varsave: # re-assign the stored value state.basic_state.variables[name][:] = varsave[name] state.basic_state.user_function_parsing.remove(fnname) return vartypes.pass_type(fnname[-1], value)
def get_var_or_array_name(ins): """ Helper function: parse a variable or array name. """ name = util.get_var_name(ins) indices = [] if util.skip_white_read_if(ins, ('[', '(')): # it's an array, read indices indices = parse_int_list(ins, 255, 9) # subscript out of range while len(indices) > 0 and indices[-1] is None: indices = indices[:-1] if None in indices: raise error.RunError(error.STX) util.require_read(ins, (']', ')')) return name, indices
def value_fn(ins): """ FN: get value of user-defined function. """ fnname = util.parse_scalar(ins) # recursion is not allowed as there's no way to terminate it if fnname in state.basic_state.user_function_parsing: raise error.RunError(error.OUT_OF_MEMORY) state.basic_state.user_function_parsing.add(fnname) try: varnames, fncode = state.basic_state.functions[fnname] except KeyError: raise error.RunError(error.UNDEFINED_USER_FUNCTION) # save existing vars varsave = {} for name in varnames: if name in state.basic_state.variables: # copy the *value* - set_var is in-place it's safe for FOR loops varsave[name] = state.basic_state.variables[name][:] # read variables if util.skip_white_read_if(ins, ('(', )): exprs = [] while True: exprs.append(parse_expression(ins)) if not util.skip_white_read_if(ins, (',', )): break if len(exprs) != len(varnames): raise error.RunError(error.STX) for name, value in zip(varnames, exprs): var.set_scalar(name, value) util.require_read(ins, (')', )) # execute the code fns = StringIO(fncode) fns.seek(0) value = parse_expression(fns) # restore existing vars for name in varsave: # re-assign the stored value state.basic_state.variables[name][:] = varsave[name] state.basic_state.user_function_parsing.remove(fnname) return value
def parse_expr_list(ins, size, err=5, separators=(',',), allow_last_empty=False): """ Helper function : parse a list of expressions. """ output = [] while True: output.append(parse_expression(ins, allow_empty=True)) if not util.skip_white_read_if(ins, separators): break if len(output) > size: raise error.RunError(err) # can't end on a comma: Missing Operand if not allow_last_empty and output and output[-1] == None: raise error.RunError(22) while len(output) < size: output.append(None) return output
def value_input(ins): """ INPUT$: get characters from the keyboard or a file. """ util.require_read(ins, ('$',)) util.require_read(ins, ('(',)) num = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(1, 255, num) infile = state.io_state.kybd_file if util.skip_white_read_if(ins, (',',)): infile = devices.get_file(parse_file_number_opthash(ins)) util.require_read(ins, (')',)) word = bytearray(infile.read_raw(num)) if len(word) < num: # input past end raise error.RunError(error.INPUT_PAST_END) return state.basic_state.strings.store(word)
def value_input(ins): """ INPUT$: get characters from the keyboard or a file. """ util.require_read(ins, ('$',)) util.require_read(ins, ('(',)) num = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(1, 255, num) infile = backend.kybd_file if util.skip_white_read_if(ins, (',',)): infile = iolayer.get_file(parse_file_number_opthash(ins)) util.require_read(ins, (')',)) word = vartypes.pack_string(bytearray(infile.read_raw(num))) if len(word) < num: # input past end raise error.RunError(62) return word
def value_input(ins): """ INPUT$: get characters from the keyboard or a file. """ util.require_read(ins, ('$', )) util.require_read(ins, ('(', )) num = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(1, 255, num) infile = state.io_state.kybd_file if util.skip_white_read_if(ins, (',', )): infile = devices.get_file(parse_file_number_opthash(ins)) util.require_read(ins, (')', )) word = bytearray(infile.read_raw(num)) if len(word) < num: # input past end raise error.RunError(error.INPUT_PAST_END) return state.basic_state.strings.store(word)
def value_varptr(ins): """ VARPTR, VARPTR$: get memory address for variable or FCB. """ dollar = util.skip_white_read_if(ins, ('$',)) util.require_read(ins, ('(',)) if (not dollar) and util.skip_white(ins) == '#': filenum = parse_file_number_opthash(ins) var_ptr = machine.varptr_file(filenum) else: name, indices = get_var_or_array_name(ins) var_ptr = machine.varptr(name, indices) util.require_read(ins, (')',)) if var_ptr < 0: raise error.RunError(error.IFC) if dollar: return vartypes.pack_string(bytearray(chr(var.byte_size[name[-1]])) + vartypes.value_to_uint(var_ptr)) else: return vartypes.pack_int(var_ptr)
def value_varptr(ins): """ VARPTR, VARPTR$: get memory address for variable or FCB. """ dollar = util.skip_white_read_if(ins, ('$',)) util.require_read(ins, ('(',)) if (not dollar) and util.skip_white(ins) == '#': filenum = parse_file_number_opthash(ins) var_ptr = machine.varptr_file(filenum) else: name, indices = parse_variable(ins) var_ptr = machine.varptr(name, indices) util.require_read(ins, (')',)) if var_ptr < 0: raise error.RunError(error.IFC) var_ptr = vartypes.int_to_integer_unsigned(var_ptr) if dollar: return state.basic_state.strings.store(chr(vartypes.byte_size[name[-1]]) + vartypes.integer_to_bytes(var_ptr)) else: return var_ptr
def value_mid(ins): """ MID$: get substring. """ util.require_read(ins, ('(',)) s = var.copy_str(vartypes.pass_string(parse_expression(ins))) util.require_read(ins, (',',)) start = vartypes.pass_int_unpack(parse_expression(ins)) if util.skip_white_read_if(ins, (',',)): num = vartypes.pass_int_unpack(parse_expression(ins)) else: num = len(s) util.require_read(ins, (')',)) util.range_check(1, 255, start) util.range_check(0, 255, num) if num == 0 or start > len(s): return vartypes.null('$') start -= 1 stop = start + num stop = min(stop, len(s)) return state.basic_state.strings.store(s[start:stop])
def value_varptr(ins): """ VARPTR, VARPTR$: get memory address for variable or FCB. """ dollar = util.skip_white_read_if(ins, ('$', )) util.require_read(ins, ('(', )) if (not dollar) and util.skip_white(ins) == '#': filenum = parse_file_number_opthash(ins) var_ptr = machine.varptr_file(filenum) else: name, indices = get_var_or_array_name(ins) var_ptr = machine.varptr(name, indices) util.require_read(ins, (')', )) if var_ptr < 0: raise error.RunError(error.IFC) if dollar: return vartypes.pack_string( bytearray(chr(var.byte_size[name[-1]])) + vartypes.value_to_uint(var_ptr)) else: return vartypes.pack_int(var_ptr)
def value_mid(ins): """ MID$: get substring. """ util.require_read(ins, ('(', )) s = var.copy_str(vartypes.pass_string(parse_expression(ins))) util.require_read(ins, (',', )) start = vartypes.pass_int_unpack(parse_expression(ins)) if util.skip_white_read_if(ins, (',', )): num = vartypes.pass_int_unpack(parse_expression(ins)) else: num = len(s) util.require_read(ins, (')', )) util.range_check(1, 255, start) util.range_check(0, 255, num) if num == 0 or start > len(s): return vartypes.null('$') start -= 1 stop = start + num stop = min(stop, len(s)) return state.basic_state.strings.store(s[start:stop])
def parse_expr_list(ins, size, err=error.IFC, separators=(',', ), allow_last_empty=False): """ Helper function : parse a list of expressions. """ output = [] while True: output.append(parse_expression(ins, allow_empty=True)) if not util.skip_white_read_if(ins, separators): break if len(output) > size: raise error.RunError(err) # can't end on a comma: Missing Operand if not allow_last_empty and output and output[-1] is None: raise error.RunError(error.MISSING_OPERAND) while len(output) < size: output.append(None) return output
def value_varptr(ins): """ VARPTR, VARPTR$: get memory address for variable or FCB. """ dollar = util.skip_white_read_if(ins, ('$', )) util.require_read(ins, ('(', )) if (not dollar) and util.skip_white(ins) == '#': filenum = parse_file_number_opthash(ins) var_ptr = machine.varptr_file(filenum) else: name, indices = parse_variable(ins) var_ptr = machine.varptr(name, indices) util.require_read(ins, (')', )) if var_ptr < 0: raise error.RunError(error.IFC) var_ptr = vartypes.int_to_integer_unsigned(var_ptr) if dollar: return state.basic_state.strings.store( chr(vartypes.byte_size[name[-1]]) + vartypes.integer_to_bytes(var_ptr)) else: return var_ptr
def value_screen(ins): """ SCREEN: get char or attribute at a location. """ util.require_read(ins, ('(',)) row = vartypes.pass_int_unpack(parse_expression(ins)) util.require_read(ins, (',',), err=error.IFC) col = vartypes.pass_int_unpack(parse_expression(ins)) z = 0 if util.skip_white_read_if(ins, (',',)): z = vartypes.pass_int_unpack(parse_expression(ins)) cmode = state.console_state.screen.mode util.range_check(1, cmode.height, row) if state.console_state.view_set: util.range_check(state.console_state.view_start, state.console_state.scroll_height, row) util.range_check(1, cmode.width, col) util.range_check(0, 255, z) util.require_read(ins, (')',)) if z and not cmode.is_text_mode: return vartypes.null('%') else: return vartypes.int_to_integer_signed(state.console_state.screen.apage.get_char_attr(row, col, z!=0))
def value_screen(ins): """ SCREEN: get char or attribute at a location. """ util.require_read(ins, ('(', )) row = vartypes.pass_int_unpack(parse_expression(ins)) util.require_read(ins, (',', ), err=error.IFC) col = vartypes.pass_int_unpack(parse_expression(ins)) z = 0 if util.skip_white_read_if(ins, (',', )): z = vartypes.pass_int_unpack(parse_expression(ins)) cmode = state.console_state.screen.mode util.range_check(1, cmode.height, row) if state.console_state.view_set: util.range_check(state.console_state.view_start, state.console_state.scroll_height, row) util.range_check(1, cmode.width, col) util.range_check(0, 255, z) util.require_read(ins, (')', )) if z and not cmode.is_text_mode: return vartypes.null('%') else: return vartypes.int_to_integer_signed( state.console_state.screen.apage.get_char_attr(row, col, z != 0))
def parse_file_number_opthash(ins): """ Helper function: parse a file number, with optional hash. """ util.skip_white_read_if(ins, ('#', )) number = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(0, 255, number) return number
def parse_file_number_opthash(ins): """ Helper function: parse a file number, with optional hash. """ util.skip_white_read_if(ins, ('#',)) number = vartypes.pass_int_unpack(parse_expression(ins)) util.range_check(0, 255, number) return number