futhark-0.7.3: rts/python/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):
s = b''
for _ in range(n):
s += self.get_char()
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:
map(f.unget_char, read[::-1])
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 string.hexdigits:
s += c
c = f.get_char()
elif c == '_':
c = f.get_char() # skip _
else:
f.unget_char(c)
break
return str(int(s, 16))
def parse_int(f):
s = b''
c = f.get_char()
if c == b'0' and f.peek_char() in [b'x', b'X']:
c = f.get_char() # skip X
s += parse_hex_int(f)
else:
while c != None:
if c.isdigit():
s += c
c = f.get_char()
elif c == '_':
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():
s = c + parse_int(f)
else:
if c != b'+':
f.unget_char(c)
s = parse_int(f)
return s
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'(')
for i in range(rank):
parse_specific_string(f, '[]')
parse_specific_string(f, type_name)
parse_specific_char(f, b')')
return None
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-1)
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 elems == None:
# Empty array
return np.empty([0]*rank, 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': np.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='<'+bin_fmt)
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 write_value(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(['[]' for _ in v.shape[1:]]), 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))
################################################################################
### end of values.py
################################################################################