summaryrefslogtreecommitdiff
path: root/nbt
diff options
context:
space:
mode:
Diffstat (limited to 'nbt')
-rw-r--r--nbt/load.ha152
-rw-r--r--nbt/value.ha141
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);
+ };
+};