diff options
author | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:11:21 +0200 |
---|---|---|
committer | Lassi Pulkkinen <lassi@pulk.fi> | 2024-10-31 03:51:35 +0200 |
commit | ae44478b30d890fe0fb04022f44d474dcdcc3f9d (patch) | |
tree | 5f462459ae4b47d22114eed717d1382d08cf4dfe /nbt |
Diffstat (limited to 'nbt')
-rw-r--r-- | nbt/load.ha | 152 | ||||
-rw-r--r-- | nbt/value.ha | 141 |
2 files changed, 293 insertions, 0 deletions
diff --git a/nbt/load.ha b/nbt/load.ha new file mode 100644 index 0000000..9e54bbb --- /dev/null +++ b/nbt/load.ha @@ -0,0 +1,152 @@ +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; + }; +}; diff --git a/nbt/value.ha b/nbt/value.ha new file mode 100644 index 0000000..cf4aa17 --- /dev/null +++ b/nbt/value.ha @@ -0,0 +1,141 @@ +use hash::fnv; +use htab; +use strings; + +export type Value = ( + i8 | i16 | i32 | i64 | f32 | f64 + | []i8 | []i32 | []i64 | str + | []Value | Object); + +export type Object = struct { + table: htab::table, +}; + +type Entry = (str, Value); + +fn eq_fn(ctx: *opaque, key: *opaque) bool = + *(ctx: *str) == *(key: *str); + +fn _get(object: *Object, hash: u64, key: str) nullable *Entry = + htab::get(&object.table, hash, &eq_fn, &key, size(Entry)): + nullable *Entry; + +export fn newobject(cap: size) Object = + Object { table = htab::new(cap, size(Entry)) }; + +export fn count(object: *Object) size = + return htab::count(&object.table); + +export fn get(object: *Object, key: str) nullable *Value = { + const hash = fnv::string64(key); + match (_get(object, hash, key)) { + case let entry: *Entry => + return &entry.1; + case null => + return null; + }; +}; + +export fn set(object: *Object, key: str, value: Value) void = { + const hash = fnv::string64(key); + match (_get(object, hash, key)) { + case let entry: *Entry => + finish(entry.1); + entry.1 = value; + case null => + const entry = htab::add(&object.table, hash, size(Entry)): + *Entry; + *entry = (strings::dup(key), value); + }; +}; + +export fn del(object: *Object, key: str) (Value | void) = { + const hash = fnv::string64(key); + match (_get(object, hash, key)) { + case let entry: *Entry => + free(entry.0); + const value = entry.1; + htab::del(&object.table, entry, size(Entry)); + return value; + case null => void; + }; +}; + +export fn _clear(object: *Object) void = { + let it = htab::iter(&object.table); + for (true) match (htab::next(&it, size(Entry)): nullable *Entry) { + case let entry: *Entry => + free(entry.0); + finish(entry.1); + case null => break; + }; +}; + +export fn clear(object: *Object) void = { + _clear(object); + htab::clear(&object.table, size(Entry)); +}; + +export type Iterator = struct { + iter: htab::iterator, +}; + +export fn iter(object: *Object) Iterator = + Iterator { iter = htab::iter(&object.table) }; + +export fn next(it: *Iterator) ((str, *Value) | void) = { + match (htab::next(&it.iter, size(Entry)): nullable *Entry) { + case let entry: *Entry => + return (entry.0, &entry.1); + case null => void; + }; +}; + +export fn dup(value: *Value) Value = { + match (*value) { + case let array: []i8 => + return alloc(array...); + case let array: []i32 => + return alloc(array...); + case let array: []i64 => + return alloc(array...); + case let string: str => + return strings::dup(string); + case let list: []Value => + let list_ = alloc([]: [0]Value, len(list)); + for (let i = 0z; i < len(list); i += 1) { + append(list_, dup(&list[i])); + }; + return list_; + case let object: Object => + let object_ = newobject(count(&object)); + let it = iter(&object); + for (true) match (next(&it)) { + case let entry: (str, *Value) => + set(&object_, entry.0, dup(entry.1)); + case void => break; + }; + return object_; + }; +}; + +export fn finish(value: Value) void = { + match (value) { + case (i8 | i16 | i32 | i64 | f32 | f64) => void; + case let array: []i8 => + free(array); + case let array: []i32 => + free(array); + case let array: []i64 => + free(array); + case let string: str => + free(string); + case let list: []Value => + for (let i = 0z; i < len(list); i += 1) { + finish(list[i]); + }; + free(list); + case let object: Object => + _clear(&object); + }; +}; |