use endian; use strings; export type Invalid = !void; type Tag = enum i8 { END = 0, BYTE = 1, SHORT = 2, INT = 3, LONG = 4, FLOAT = 5, DOUBLE = 6, BYTE_ARRAY = 7, STRING = 8, LIST = 9, COMPOUND = 10, INT_ARRAY = 11, LONG_ARRAY = 12, }; // XXX: these are duplicated from mcproto; not sure what to do about that. fn decode_nbytes(in: *[]u8, length: size) ([]u8 | Invalid) = { if (len(in) < length) return Invalid; const res = in[..length]; *in = in[length..]; return res; }; fn decode_byte(in: *[]u8) (i8 | Invalid) = decode_nbytes(in, 1)?[0]: i8; fn decode_short(in: *[]u8) (i16 | Invalid) = endian::begetu16(decode_nbytes(in, 2)?): i16; fn decode_int(in: *[]u8) (i32 | Invalid) = endian::begetu32(decode_nbytes(in, 4)?): i32; fn decode_long(in: *[]u8) (i64 | Invalid) = endian::begetu64(decode_nbytes(in, 8)?): i64; fn decode_bool(in: *[]u8) (bool | Invalid) = decode_byte(in)? != 0; fn decode_float(in: *[]u8) (f32 | Invalid) = *(&decode_int(in)?: *f32); fn decode_double(in: *[]u8) (f64 | Invalid) = *(&decode_long(in)?: *f64); fn decode_string(in: *[]u8) (str | Invalid) = { const length = decode_short(in)?; if (length < 0) return Invalid; // TODO: need to deal with braindead java utf8. match (strings::fromutf8(decode_nbytes(in, length: size)?)) { case let s: str => return strings::dup(s); case => return Invalid; }; }; // TODO: depth limit export fn load(in: *[]u8) ((str, Object) | Invalid) = { const tag = decode_byte(in)?: Tag; if (tag != Tag::COMPOUND) return Invalid; const name = decode_string(in)?; match (_load(in, tag)?) { case let obj: Object => return (name, obj); case => return Invalid; }; }; fn _load(in: *[]u8, tag: Tag) (Value | Invalid) = { switch (tag) { case Tag::BYTE => return decode_byte(in)?; case Tag::SHORT => return decode_short(in)?; case Tag::INT => return decode_int(in)?; case Tag::LONG => return decode_long(in)?; case Tag::FLOAT => return decode_float(in)?; case Tag::DOUBLE => return decode_double(in)?; case Tag::STRING => return decode_string(in)?; case Tag::BYTE_ARRAY => const length = decode_int(in)?; if (length < 0) return Invalid; return alloc(decode_nbytes(in, length: size)?...): []i8; case Tag::LIST => let success = false; const tag = decode_byte(in)?: Tag; const length = decode_int(in)?; if (length < 0) return Invalid; let list = alloc([]: [0]Value, length: size); defer if (!success) finish(list); for (let i = 0i32; i < length; i += 1) { static append(list, _load(in, tag)?); }; success = true; return list; case Tag::COMPOUND => let success = false; let object = newobject(0); defer if (!success) finish(object); for (true) { const tag = decode_byte(in)?: Tag; if (tag == Tag::END) break; const name = decode_string(in)?; defer free(name); set(&object, name, _load(in, tag)?); }; success = true; return object; case Tag::INT_ARRAY => let success = false; const length = decode_int(in)?; if (length < 0) return Invalid; let array = alloc([]: [0]i32, length: size); defer if (!success) free(array); for (let i = 0i32; i < length; i += 1) { static append(array, decode_int(in)?); }; success = true; return array; case Tag::LONG_ARRAY => let success = false; const length = decode_int(in)?; if (length < 0) return Invalid; let array = alloc([]: [0]i64, length: size); defer if (!success) free(array); for (let i = 0i32; i < length; i += 1) { static append(array, decode_long(in)?); }; success = true; return array; case => return Invalid; }; };