# Start of values.py. # Hacky parser/reader/writer for values written in Futhark syntax. # Used for reading stdin when compiling standalone programs with the # Python code generator. import numpy as np import string import struct import sys class ReaderInput: def __init__(self, f): self.f = f self.lookahead_buffer = [] def get_char(self): if len(self.lookahead_buffer) == 0: return self.f.read(1) else: c = self.lookahead_buffer[0] self.lookahead_buffer = self.lookahead_buffer[1:] return c def unget_char(self, c): self.lookahead_buffer = [c] + self.lookahead_buffer def get_chars(self, n): n1 = min(n, len(self.lookahead_buffer)) s = b''.join(self.lookahead_buffer[:n1]) self.lookahead_buffer = self.lookahead_buffer[n1:] n2 = n - n1 if n2 > 0: s += self.f.read(n2) return s def peek_char(self): c = self.get_char() if c: self.unget_char(c) return c def skip_spaces(f): c = f.get_char() while c != None: if c.isspace(): c = f.get_char() elif c == b'-': # May be line comment. if f.peek_char() == b'-': # Yes, line comment. Skip to end of line. while (c != b'\n' and c != None): c = f.get_char() else: break else: break if c: f.unget_char(c) def parse_specific_char(f, expected): got = f.get_char() if got != expected: f.unget_char(got) raise ValueError return True def parse_specific_string(f, s): # This funky mess is intended, and is caused by the fact that if `type(b) == # bytes` then `type(b[0]) == int`, but we need to match each element with a # `bytes`, so therefore we make each character an array element b = s.encode('utf8') bs = [b[i:i+1] for i in range(len(b))] read = [] try: for c in bs: parse_specific_char(f, c) read.append(c) return True except ValueError: for c in read[::-1]: f.unget_char(c) raise def optional(p, *args): try: return p(*args) except ValueError: return None def optional_specific_string(f, s): c = f.peek_char() # This funky mess is intended, and is caused by the fact that if `type(b) == # bytes` then `type(b[0]) == int`, but we need to match each element with a # `bytes`, so therefore we make each character an array element b = s.encode('utf8') bs = [b[i:i+1] for i in range(len(b))] if c == bs[0]: return parse_specific_string(f, s) else: return False def sepBy(p, sep, *args): elems = [] x = optional(p, *args) if x != None: elems += [x] while optional(sep, *args) != None: x = p(*args) elems += [x] return elems # Assumes '0x' has already been read def parse_hex_int(f): s = b'' c = f.get_char() while c != None: if c in b'01234556789ABCDEFabcdef': s += c c = f.get_char() elif c == b'_': c = f.get_char() # skip _ else: f.unget_char(c) break return str(int(s, 16)).encode('utf8') # ugh def parse_int(f): s = b'' c = f.get_char() if c == b'0' and f.peek_char() in b'xX': c = f.get_char() # skip X return parse_hex_int(f) else: while c != None: if c.isdigit(): s += c c = f.get_char() elif c == b'_': c = f.get_char() # skip _ else: f.unget_char(c) break if len(s) == 0: raise ValueError return s def parse_int_signed(f): s = b'' c = f.get_char() if c == b'-' and f.peek_char().isdigit(): return c + parse_int(f) else: if c != b'+': f.unget_char(c) return parse_int(f) def read_str_comma(f): skip_spaces(f) parse_specific_char(f, b',') return b',' def read_str_int(f, s): skip_spaces(f) x = int(parse_int_signed(f)) optional_specific_string(f, s) return x def read_str_uint(f, s): skip_spaces(f) x = int(parse_int(f)) optional_specific_string(f, s) return x def read_str_i8(f): return np.int8(read_str_int(f, 'i8')) def read_str_i16(f): return np.int16(read_str_int(f, 'i16')) def read_str_i32(f): return np.int32(read_str_int(f, 'i32')) def read_str_i64(f): return np.int64(read_str_int(f, 'i64')) def read_str_u8(f): return np.uint8(read_str_int(f, 'u8')) def read_str_u16(f): return np.uint16(read_str_int(f, 'u16')) def read_str_u32(f): return np.uint32(read_str_int(f, 'u32')) def read_str_u64(f): return np.uint64(read_str_int(f, 'u64')) def read_char(f): skip_spaces(f) parse_specific_char(f, b'\'') c = f.get_char() parse_specific_char(f, b'\'') return c def read_str_hex_float(f, sign): int_part = parse_hex_int(f) parse_specific_char(f, b'.') frac_part = parse_hex_int(f) parse_specific_char(f, b'p') exponent = parse_int(f) int_val = int(int_part, 16) frac_val = float(int(frac_part, 16)) / (16 ** len(frac_part)) exp_val = int(exponent) total_val = (int_val + frac_val) * (2.0 ** exp_val) if sign == b'-': total_val = -1 * total_val return float(total_val) def read_str_decimal(f): skip_spaces(f) c = f.get_char() if (c == b'-'): sign = b'-' else: f.unget_char(c) sign = b'' # Check for hexadecimal float c = f.get_char() if (c == '0' and (f.peek_char() in ['x', 'X'])): f.get_char() return read_str_hex_float(f, sign) else: f.unget_char(c) bef = optional(parse_int, f) if bef == None: bef = b'0' parse_specific_char(f, b'.') aft = parse_int(f) elif optional(parse_specific_char, f, b'.'): aft = parse_int(f) else: aft = b'0' if (optional(parse_specific_char, f, b'E') or optional(parse_specific_char, f, b'e')): expt = parse_int_signed(f) else: expt = b'0' return float(sign + bef + b'.' + aft + b'E' + expt) def read_str_f32(f): skip_spaces(f) try: parse_specific_string(f, 'f32.nan') return np.float32(np.nan) except ValueError: try: parse_specific_string(f, 'f32.inf') return np.float32(np.inf) except ValueError: try: parse_specific_string(f, '-f32.inf') return np.float32(-np.inf) except ValueError: x = read_str_decimal(f) optional_specific_string(f, 'f32') return x def read_str_f64(f): skip_spaces(f) try: parse_specific_string(f, 'f64.nan') return np.float64(np.nan) except ValueError: try: parse_specific_string(f, 'f64.inf') return np.float64(np.inf) except ValueError: try: parse_specific_string(f, '-f64.inf') return np.float64(-np.inf) except ValueError: x = read_str_decimal(f) optional_specific_string(f, 'f64') return x def read_str_bool(f): skip_spaces(f) if f.peek_char() == b't': parse_specific_string(f, 'true') return True elif f.peek_char() == b'f': parse_specific_string(f, 'false') return False else: raise ValueError def read_str_empty_array(f, type_name, rank): parse_specific_string(f, 'empty') parse_specific_char(f, b'(') dims = [] for i in range(rank): parse_specific_string(f, '[') dims += [int(parse_int(f))] parse_specific_string(f, ']') if np.product(dims) != 0: raise ValueError parse_specific_string(f, type_name) parse_specific_char(f, b')') return tuple(dims) def read_str_array_elems(f, elem_reader, type_name, rank): skip_spaces(f) try: parse_specific_char(f, b'[') except ValueError: return read_str_empty_array(f, type_name, rank) else: xs = sepBy(elem_reader, read_str_comma, f) skip_spaces(f) parse_specific_char(f, b']') return xs def read_str_array_helper(f, elem_reader, type_name, rank): def nested_row_reader(_): return read_str_array_helper(f, elem_reader, type_name, rank-1) if rank == 1: row_reader = elem_reader else: row_reader = nested_row_reader return read_str_array_elems(f, row_reader, type_name, rank) def expected_array_dims(l, rank): if rank > 1: n = len(l) if n == 0: elem = [] else: elem = l[0] return [n] + expected_array_dims(elem, rank-1) else: return [len(l)] def verify_array_dims(l, dims): if dims[0] != len(l): raise ValueError if len(dims) > 1: for x in l: verify_array_dims(x, dims[1:]) def read_str_array(f, elem_reader, type_name, rank, bt): elems = read_str_array_helper(f, elem_reader, type_name, rank) if type(elems) == tuple: # Empty array return np.empty(elems, dtype=bt) else: dims = expected_array_dims(elems, rank) verify_array_dims(elems, dims) return np.array(elems, dtype=bt) ################################################################################ READ_BINARY_VERSION = 2 # struct format specified at # https://docs.python.org/2/library/struct.html#format-characters def mk_bin_scalar_reader(t): def bin_reader(f): fmt = FUTHARK_PRIMTYPES[t]['bin_format'] size = FUTHARK_PRIMTYPES[t]['size'] return struct.unpack('<' + fmt, f.get_chars(size))[0] return bin_reader read_bin_i8 = mk_bin_scalar_reader('i8') read_bin_i16 = mk_bin_scalar_reader('i16') read_bin_i32 = mk_bin_scalar_reader('i32') read_bin_i64 = mk_bin_scalar_reader('i64') read_bin_u8 = mk_bin_scalar_reader('u8') read_bin_u16 = mk_bin_scalar_reader('u16') read_bin_u32 = mk_bin_scalar_reader('u32') read_bin_u64 = mk_bin_scalar_reader('u64') read_bin_f32 = mk_bin_scalar_reader('f32') read_bin_f64 = mk_bin_scalar_reader('f64') read_bin_bool = mk_bin_scalar_reader('bool') def read_is_binary(f): skip_spaces(f) c = f.get_char() if c == b'b': bin_version = read_bin_u8(f) if bin_version != READ_BINARY_VERSION: panic(1, "binary-input: File uses version %i, but I only understand version %i.\n", bin_version, READ_BINARY_VERSION) return True else: f.unget_char(c) return False FUTHARK_PRIMTYPES = { 'i8': {'binname' : b" i8", 'size' : 1, 'bin_reader': read_bin_i8, 'str_reader': read_str_i8, 'bin_format': 'b', 'numpy_type': np.int8 }, 'i16': {'binname' : b" i16", 'size' : 2, 'bin_reader': read_bin_i16, 'str_reader': read_str_i16, 'bin_format': 'h', 'numpy_type': np.int16 }, 'i32': {'binname' : b" i32", 'size' : 4, 'bin_reader': read_bin_i32, 'str_reader': read_str_i32, 'bin_format': 'i', 'numpy_type': np.int32 }, 'i64': {'binname' : b" i64", 'size' : 8, 'bin_reader': read_bin_i64, 'str_reader': read_str_i64, 'bin_format': 'q', 'numpy_type': np.int64}, 'u8': {'binname' : b" u8", 'size' : 1, 'bin_reader': read_bin_u8, 'str_reader': read_str_u8, 'bin_format': 'B', 'numpy_type': np.uint8 }, 'u16': {'binname' : b" u16", 'size' : 2, 'bin_reader': read_bin_u16, 'str_reader': read_str_u16, 'bin_format': 'H', 'numpy_type': np.uint16 }, 'u32': {'binname' : b" u32", 'size' : 4, 'bin_reader': read_bin_u32, 'str_reader': read_str_u32, 'bin_format': 'I', 'numpy_type': np.uint32 }, 'u64': {'binname' : b" u64", 'size' : 8, 'bin_reader': read_bin_u64, 'str_reader': read_str_u64, 'bin_format': 'Q', 'numpy_type': np.uint64 }, 'f32': {'binname' : b" f32", 'size' : 4, 'bin_reader': read_bin_f32, 'str_reader': read_str_f32, 'bin_format': 'f', 'numpy_type': np.float32 }, 'f64': {'binname' : b" f64", 'size' : 8, 'bin_reader': read_bin_f64, 'str_reader': read_str_f64, 'bin_format': 'd', 'numpy_type': np.float64 }, 'bool': {'binname' : b"bool", 'size' : 1, 'bin_reader': read_bin_bool, 'str_reader': read_str_bool, 'bin_format': 'b', 'numpy_type': bool } } def read_bin_read_type(f): read_binname = f.get_chars(4) for (k,v) in FUTHARK_PRIMTYPES.items(): if v['binname'] == read_binname: return k panic(1, "binary-input: Did not recognize the type '%s'.\n", read_binname) def numpy_type_to_type_name(t): for (k,v) in FUTHARK_PRIMTYPES.items(): if v['numpy_type'] == t: return k raise Exception('Unknown Numpy type: {}'.format(t)) def read_bin_ensure_scalar(f, expected_type): dims = read_bin_i8(f) if dims != 0: panic(1, "binary-input: Expected scalar (0 dimensions), but got array with %i dimensions.\n", dims) bin_type = read_bin_read_type(f) if bin_type != expected_type: panic(1, "binary-input: Expected scalar of type %s but got scalar of type %s.\n", expected_type, bin_type) # ------------------------------------------------------------------------------ # General interface for reading Primitive Futhark Values # ------------------------------------------------------------------------------ def read_scalar(f, ty): if read_is_binary(f): read_bin_ensure_scalar(f, ty) return FUTHARK_PRIMTYPES[ty]['bin_reader'](f) return FUTHARK_PRIMTYPES[ty]['str_reader'](f) def read_array(f, expected_type, rank): if not read_is_binary(f): str_reader = FUTHARK_PRIMTYPES[expected_type]['str_reader'] return read_str_array(f, str_reader, expected_type, rank, FUTHARK_PRIMTYPES[expected_type]['numpy_type']) bin_rank = read_bin_u8(f) if bin_rank != rank: panic(1, "binary-input: Expected %i dimensions, but got array with %i dimensions.\n", rank, bin_rank) bin_type_enum = read_bin_read_type(f) if expected_type != bin_type_enum: panic(1, "binary-input: Expected %iD-array with element type '%s' but got %iD-array with element type '%s'.\n", rank, expected_type, bin_rank, bin_type_enum) shape = [] elem_count = 1 for i in range(rank): bin_size = read_bin_u64(f) elem_count *= bin_size shape.append(bin_size) bin_fmt = FUTHARK_PRIMTYPES[bin_type_enum]['bin_format'] # We first read the expected number of types into a bytestring, # then use np.fromstring. This is because np.fromfile does not # work on things that are insufficiently file-like, like a network # stream. bytes = f.get_chars(elem_count * FUTHARK_PRIMTYPES[expected_type]['size']) arr = np.fromstring(bytes, dtype=FUTHARK_PRIMTYPES[bin_type_enum]['numpy_type']) arr.shape = shape return arr if sys.version_info >= (3,0): input_reader = ReaderInput(sys.stdin.buffer) else: input_reader = ReaderInput(sys.stdin) import re def read_value(type_desc, reader=input_reader): """Read a value of the given type. The type is a string representation of the Futhark type.""" m = re.match(r'((?:\[\])*)([a-z0-9]+)$', type_desc) if m: dims = int(len(m.group(1))/2) basetype = m.group(2) assert basetype in FUTHARK_PRIMTYPES, "Unknown type: {}".format(type_desc) if dims > 0: return read_array(reader, basetype, dims) else: return read_scalar(reader, basetype) return (dims, basetype) def end_of_input(entry, f=input_reader): skip_spaces(f) if f.get_char() != b'': panic(1, "Expected EOF on stdin after reading input for \"%s\".", entry) def write_value_text(v, out=sys.stdout): if type(v) == np.uint8: out.write("%uu8" % v) elif type(v) == np.uint16: out.write("%uu16" % v) elif type(v) == np.uint32: out.write("%uu32" % v) elif type(v) == np.uint64: out.write("%uu64" % v) elif type(v) == np.int8: out.write("%di8" % v) elif type(v) == np.int16: out.write("%di16" % v) elif type(v) == np.int32: out.write("%di32" % v) elif type(v) == np.int64: out.write("%di64" % v) elif type(v) in [np.bool, np.bool_]: if v: out.write("true") else: out.write("false") elif type(v) == np.float32: if np.isnan(v): out.write('f32.nan') elif np.isinf(v): if v >= 0: out.write('f32.inf') else: out.write('-f32.inf') else: out.write("%.6ff32" % v) elif type(v) == np.float64: if np.isnan(v): out.write('f64.nan') elif np.isinf(v): if v >= 0: out.write('f64.inf') else: out.write('-f64.inf') else: out.write("%.6ff64" % v) elif type(v) == np.ndarray: if np.product(v.shape) == 0: tname = numpy_type_to_type_name(v.dtype) out.write('empty({}{})'.format(''.join(['[{}]'.format(d) for d in v.shape]), tname)) else: first = True out.write('[') for x in v: if not first: out.write(', ') first = False write_value(x, out=out) out.write(']') else: raise Exception("Cannot print value of type {}: {}".format(type(v), v)) type_strs = { np.dtype('int8'): b' i8', np.dtype('int16'): b' i16', np.dtype('int32'): b' i32', np.dtype('int64'): b' i64', np.dtype('uint8'): b' u8', np.dtype('uint16'): b' u16', np.dtype('uint32'): b' u32', np.dtype('uint64'): b' u64', np.dtype('float32'): b' f32', np.dtype('float64'): b' f64', np.dtype('bool'): b'bool'} def construct_binary_value(v): t = v.dtype shape = v.shape elems = 1 for d in shape: elems *= d num_bytes = 1 + 1 + 1 + 4 + len(shape) * 8 + elems * t.itemsize bytes = bytearray(num_bytes) bytes[0] = np.int8(ord('b')) bytes[1] = 2 bytes[2] = np.int8(len(shape)) bytes[3:7] = type_strs[t] for i in range(len(shape)): bytes[7+i*8:7+(i+1)*8] = np.int64(shape[i]).tostring() bytes[7+len(shape)*8:] = np.ascontiguousarray(v).tostring() return bytes def write_value_binary(v, out=sys.stdout): if sys.version_info >= (3,0): out = out.buffer out.write(construct_binary_value(v)) def write_value(v, out=sys.stdout, binary=False): if binary: return write_value_binary(v, out=out) else: return write_value_text(v, out=out) # End of values.py.