snbt
This commit is contained in:
parent
85ba301f52
commit
081bdb7062
9 changed files with 970 additions and 3 deletions
|
@ -13,7 +13,7 @@ Files will have their filetype auto guessed based on their file extension: `.nbt
|
||||||
The current supported file formats are
|
The current supported file formats are
|
||||||
|
|
||||||
- `nbt` - Named Binary Tag
|
- `nbt` - Named Binary Tag
|
||||||
- `snbt` - String Named Binary Tag (TODO!!)
|
- `snbt` - String Named Binary Tag
|
||||||
- `json` - Java Script Object Notation (lossy)
|
- `json` - Java Script Object Notation (lossy)
|
||||||
|
|
||||||
#### complementation
|
#### complementation
|
||||||
|
|
|
@ -73,15 +73,19 @@ void parse_short_args(flags_t *flags, char *arg, int len) {
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
flags->fin = SNBT;
|
flags->fin = SNBT;
|
||||||
|
flags->__set_fin = true;
|
||||||
break;
|
break;
|
||||||
case 'S':
|
case 'S':
|
||||||
flags->fout = SNBT;
|
flags->fout = SNBT;
|
||||||
|
flags->__set_fout = true;
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
flags->fin = NBT;
|
flags->fin = NBT;
|
||||||
|
flags->__set_fin = true;
|
||||||
break;
|
break;
|
||||||
case 'N':
|
case 'N':
|
||||||
flags->fout = NBT;
|
flags->fout = NBT;
|
||||||
|
flags->__set_fout = true;
|
||||||
break;
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
flags->help = true;
|
flags->help = true;
|
||||||
|
|
|
@ -160,7 +160,7 @@ static bool json_print_data(const tag_t *tag, const stream_t *stream, int depth)
|
||||||
|
|
||||||
static bool json_print_impl(const tag_t *tag, const stream_t *stream, int depth) {
|
static bool json_print_impl(const tag_t *tag, const stream_t *stream, int depth) {
|
||||||
if (tag->name_len > 0) {
|
if (tag->name_len > 0) {
|
||||||
printi(stream, depth, "\"%.*s\":\t", tag->name_len, tag->name);
|
printi(stream, depth, "\"%.*s\": ", tag->name_len, tag->name);
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < depth; i++)
|
for (int i = 0; i < depth; i++)
|
||||||
printi(stream, 0, "\t");
|
printi(stream, 0, "\t");
|
||||||
|
|
|
@ -627,7 +627,6 @@ static bool json_read_value(tag_t *tag, const stream_t *stream, token_t *first)
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool json_read(tag_t *tag, const stream_t *stream) {
|
bool json_read(tag_t *tag, const stream_t *stream) {
|
||||||
return json_read_value(tag, stream, NULL);
|
return json_read_value(tag, stream, NULL);
|
||||||
}
|
}
|
||||||
|
|
210
src/snbt/print.c
Normal file
210
src/snbt/print.c
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
#include "snbt.h"
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static char buf[1024];
|
||||||
|
|
||||||
|
__attribute__((format(printf, 3, 4)))
|
||||||
|
static bool printi(const stream_t *stream, int depth, const char *format, ...) {
|
||||||
|
for (int i = 0; i < depth; i++)
|
||||||
|
if (stream_write(stream, "\t", 1) == false)
|
||||||
|
return false;
|
||||||
|
va_list list;
|
||||||
|
va_start(list, format);
|
||||||
|
int len;
|
||||||
|
if ((len = vsnprintf(buf, 1024, format, list)) < 0)
|
||||||
|
return false;
|
||||||
|
if (stream_write(stream, buf, len) == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_impl(const tag_t *tag, const stream_t *stream, int depth);
|
||||||
|
|
||||||
|
static bool snbt_print_byte_array(const tagdata_t *data, const stream_t *stream) {
|
||||||
|
if (printi(stream, 0, "[B;") == false)
|
||||||
|
return false;
|
||||||
|
for (int32_t i = 0; i < data->b_arr.size; i++) {
|
||||||
|
if (i != 0)
|
||||||
|
if (printi(stream, 0, ",") == false)
|
||||||
|
return false;
|
||||||
|
if (printi(stream, 0, "%hhdb", data->b_arr.data[i]) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "]") == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_int_array(const tagdata_t *data, const stream_t *stream) {
|
||||||
|
if (printi(stream, 0, "[I;") == false)
|
||||||
|
return false;
|
||||||
|
for (int32_t i = 0; i < data->i_arr.size; i++) {
|
||||||
|
if (i != 0)
|
||||||
|
if (printi(stream, 0, ",") == false)
|
||||||
|
return false;
|
||||||
|
if (printi(stream, 0, "%d", data->i_arr.data[i]) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "]") == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_long_array(const tagdata_t *data, const stream_t *stream) {
|
||||||
|
if (printi(stream, 0, "[L;") == false)
|
||||||
|
return false;
|
||||||
|
for (int32_t i = 0; i < data->l_arr.size; i++) {
|
||||||
|
if (i != 0)
|
||||||
|
if (printi(stream, 0, ",") == false)
|
||||||
|
return false;
|
||||||
|
if (printi(stream, 0, "%ldL", data->l_arr.data[i]) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "]") == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_string_unquoted(const char *text, uint16_t len) {
|
||||||
|
for (uint16_t i = 0; i < len; i++) {
|
||||||
|
char c = text[i];
|
||||||
|
if (snbt_allowed_ident(c))
|
||||||
|
continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_string_impl(const stream_t *stream, int depth, char *text, uint16_t len) {
|
||||||
|
if (len > 0 && snbt_string_unquoted(text, len)) {
|
||||||
|
if (printi(stream, depth, "%.*s", len, text) == false)
|
||||||
|
return false;
|
||||||
|
} else if (len > 0) {
|
||||||
|
if (printi(stream, depth, "\"") == false)
|
||||||
|
return false;
|
||||||
|
for (uint16_t i = 0; i < len; i++) {
|
||||||
|
char c = text[i];
|
||||||
|
if (c == '\\' || c == '"') {
|
||||||
|
if (printi(stream, 0, "\\%c", c) == false)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (printi(stream, 0, "%c", c) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "\"") == false)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (printi(stream, depth, "''") == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_string(const tagdata_t *data, const stream_t *stream) {
|
||||||
|
char *text = data->string.data;
|
||||||
|
uint16_t len = data->string.size;
|
||||||
|
return snbt_print_string_impl(stream, 0, text, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_compound(const tagdata_t *data, const stream_t *stream, int depth) {
|
||||||
|
if (printi(stream, 0, "{\n") == false)
|
||||||
|
return false;
|
||||||
|
bool first = true;
|
||||||
|
for (uint32_t i = 0; i < data->compound.capacity; i++) {
|
||||||
|
if (data->compound.entries[i].name == NULL)
|
||||||
|
continue;
|
||||||
|
if (!first && printi(stream, 0, ",\n") == false)
|
||||||
|
return false;
|
||||||
|
first = false;
|
||||||
|
if (snbt_print_impl(&data->compound.entries[i], stream, depth + 1) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "\n") == false || printi(stream, depth, "}") == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_list(const tagdata_t *data, const stream_t *stream, int depth) {
|
||||||
|
if (printi(stream, 0, "[\n") == false)
|
||||||
|
return false;
|
||||||
|
for (int32_t i = 0; i < data->list.size; i++) {
|
||||||
|
if (i != 0 && printi(stream, 0, ",\n") == false)
|
||||||
|
return false;
|
||||||
|
if (snbt_print_impl(&data->list.tags[i], stream, depth + 1) == false)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (printi(stream, 0, "\n") == false || printi(stream, depth, "]") == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_data(const tag_t *tag, const stream_t *stream, int depth) {
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
switch (tag->type) {
|
||||||
|
case TAG_BYTE:
|
||||||
|
ok = printi(stream, 0, "%hhdb", tag->data.b);
|
||||||
|
break;
|
||||||
|
case TAG_SHORT:
|
||||||
|
ok = printi(stream, 0, "%hds", tag->data.s);
|
||||||
|
break;
|
||||||
|
case TAG_INT:
|
||||||
|
ok = printi(stream, 0, "%d", tag->data.i);
|
||||||
|
break;
|
||||||
|
case TAG_LONG:
|
||||||
|
ok = printi(stream, 0, "%ldL", tag->data.l);
|
||||||
|
break;
|
||||||
|
case TAG_FLOAT:
|
||||||
|
ok = printi(stream, 0, "%.9gf", tag->data.f);
|
||||||
|
break;
|
||||||
|
case TAG_DOUBLE:
|
||||||
|
ok = printi(stream, 0, "%.17g", tag->data.d);
|
||||||
|
break;
|
||||||
|
case TAG_BYTE_ARRAY:
|
||||||
|
ok = snbt_print_byte_array(&tag->data, stream);
|
||||||
|
break;
|
||||||
|
case TAG_STRING:
|
||||||
|
ok = snbt_print_string(&tag->data, stream);
|
||||||
|
break;
|
||||||
|
case TAG_LIST:
|
||||||
|
ok = snbt_print_list(&tag->data, stream, depth);
|
||||||
|
break;
|
||||||
|
case TAG_COMPOUND:
|
||||||
|
ok = snbt_print_compound(&tag->data, stream, depth);
|
||||||
|
break;
|
||||||
|
case TAG_INT_ARRAY:
|
||||||
|
ok = snbt_print_int_array(&tag->data, stream);
|
||||||
|
break;
|
||||||
|
case TAG_LONG_ARRAY:
|
||||||
|
ok = snbt_print_long_array(&tag->data, stream);
|
||||||
|
break;
|
||||||
|
case TAG_END:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_print_impl(const tag_t *tag, const stream_t *stream, int depth) {
|
||||||
|
if (tag->name_len > 0) {
|
||||||
|
if (snbt_print_string_impl(stream, depth, tag->name, tag->name_len) == false)
|
||||||
|
return false;
|
||||||
|
if (printi(stream, 0, ": ") == false)
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < depth; i++)
|
||||||
|
printi(stream, 0, "\t");
|
||||||
|
}
|
||||||
|
return snbt_print_data(tag, stream, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool snbt_print(const tag_t *tag, const stream_t *stream) {
|
||||||
|
if (snbt_print_impl(tag, stream, 0) == false)
|
||||||
|
return false;
|
||||||
|
if (stream_write(stream, "\n", 1) == false)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
740
src/snbt/read.c
Normal file
740
src/snbt/read.c
Normal file
|
@ -0,0 +1,740 @@
|
||||||
|
#include "snbt.h"
|
||||||
|
#include "../lib.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TOK_RBRACE,
|
||||||
|
TOK_LBRACE,
|
||||||
|
TOK_RBRACK,
|
||||||
|
TOK_LBRACK,
|
||||||
|
TOK_COMMA,
|
||||||
|
TOK_COLON,
|
||||||
|
TOK_SEMI_COLON,
|
||||||
|
TOK_STRING,
|
||||||
|
TOK_IDENT,
|
||||||
|
} tokentype_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
char *data;
|
||||||
|
uint16_t len;
|
||||||
|
} text;
|
||||||
|
double d;
|
||||||
|
int64_t l;
|
||||||
|
} tokendata_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
tokentype_t type;
|
||||||
|
tokendata_t data;
|
||||||
|
} token_t;
|
||||||
|
|
||||||
|
static char ret = '\0';
|
||||||
|
|
||||||
|
static void snbt_free_token(token_t *token) {
|
||||||
|
if (token->type == TOK_IDENT || token->type == TOK_STRING) {
|
||||||
|
free(token->data.text.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_parse_string(token_t *token, const stream_t *stream, char delimiter) {
|
||||||
|
int len = 0;
|
||||||
|
int capacity = 8;
|
||||||
|
char *buf = xalloc(capacity * sizeof(char));
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
char c;
|
||||||
|
if (stream_read(stream, &c, 1) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == delimiter)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (c == '\\') {
|
||||||
|
if (stream_read(stream, &c, 1) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c != '\\' && c != delimiter) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
buf = xrealloc(buf, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[len++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
token->data.text.data = xalloc(len * sizeof(char));
|
||||||
|
token->data.text.len = len;
|
||||||
|
memcpy(token->data.text.data, buf, len * sizeof(char));
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool snbt_allowed_ident(char c) {
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
return true;
|
||||||
|
if (c >= 'a' && c <= 'z')
|
||||||
|
return true;
|
||||||
|
if (c >= 'A' && c <= 'Z')
|
||||||
|
return true;
|
||||||
|
if (c == '_' || c == '-' || c == '.' || c == '+')
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_parse_ident(token_t *token, const stream_t *stream, char first) {
|
||||||
|
int len = 0;
|
||||||
|
int capacity = 8;
|
||||||
|
char *buf = xalloc(capacity * sizeof(char));
|
||||||
|
|
||||||
|
buf[len++] = first;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
char c;
|
||||||
|
if (stream_read(stream, &c, 1) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snbt_allowed_ident(c) == false) {
|
||||||
|
if (len == 0) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ret = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
buf = xrealloc(buf, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[len++] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
token->data.text.data = xalloc(len * sizeof(char));
|
||||||
|
token->data.text.len = len;
|
||||||
|
memcpy(token->data.text.data, buf, len * sizeof(char));
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_next_token(token_t *token, const stream_t *stream) {
|
||||||
|
|
||||||
|
memset(token, 0, sizeof(token_t));
|
||||||
|
|
||||||
|
char c;
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
retry:
|
||||||
|
|
||||||
|
c = ret;
|
||||||
|
ret = '\0';
|
||||||
|
|
||||||
|
if (c == '\0' && stream_read(stream, &c, 1) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
case '\n':
|
||||||
|
case '\r':
|
||||||
|
goto retry;
|
||||||
|
case '{':
|
||||||
|
token->type = TOK_LBRACE;
|
||||||
|
break;
|
||||||
|
case '}':
|
||||||
|
token->type = TOK_RBRACE;
|
||||||
|
break;
|
||||||
|
case '[':
|
||||||
|
token->type = TOK_LBRACK;
|
||||||
|
break;
|
||||||
|
case ']':
|
||||||
|
token->type = TOK_RBRACK;
|
||||||
|
break;
|
||||||
|
case ':':
|
||||||
|
token->type = TOK_COLON;
|
||||||
|
break;
|
||||||
|
case ';':
|
||||||
|
token->type = TOK_SEMI_COLON;
|
||||||
|
break;
|
||||||
|
case ',':
|
||||||
|
token->type = TOK_COMMA;
|
||||||
|
break;
|
||||||
|
case '"':
|
||||||
|
case '\'':
|
||||||
|
token->type = TOK_STRING;
|
||||||
|
ok = snbt_parse_string(token, stream, c);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token->type = TOK_IDENT;
|
||||||
|
ok = snbt_parse_ident(token, stream, c);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_value(tag_t *tag, const stream_t *stream, token_t *first);
|
||||||
|
static bool snbt_convert_ident(tag_t *tag, token_t token);
|
||||||
|
|
||||||
|
static bool snbt_read_byte_array(tag_t *tag, const stream_t *stream) {
|
||||||
|
int capacity = 8;
|
||||||
|
int len = 0;
|
||||||
|
int8_t *buf = xalloc(capacity * sizeof(int8_t));
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
tag_t value;
|
||||||
|
if (snbt_read_value(&value, stream, NULL) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.type != TAG_BYTE) {
|
||||||
|
tag_free(&value);
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
buf = xrealloc(buf, capacity * sizeof(int8_t));
|
||||||
|
}
|
||||||
|
buf[len++] = value.data.b;
|
||||||
|
token_t token;
|
||||||
|
if (snbt_next_token(&token, stream) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token.type == TOK_COMMA)
|
||||||
|
continue;
|
||||||
|
if (token.type == TOK_RBRACK)
|
||||||
|
break;
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tag->type = TAG_BYTE_ARRAY;
|
||||||
|
tag->data.b_arr.data = xalloc(len * sizeof(int8_t));
|
||||||
|
tag->data.b_arr.size = len;
|
||||||
|
memcpy(tag->data.b_arr.data, buf, len * sizeof(int8_t));
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_int_array(tag_t *tag, const stream_t *stream) {
|
||||||
|
int capacity = 8;
|
||||||
|
int len = 0;
|
||||||
|
int32_t *buf = xalloc(capacity * sizeof(int32_t));
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
tag_t value;
|
||||||
|
if (snbt_read_value(&value, stream, NULL) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.type != TAG_INT) {
|
||||||
|
tag_free(&value);
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
buf = xrealloc(buf, capacity * sizeof(int32_t));
|
||||||
|
}
|
||||||
|
buf[len++] = value.data.i;
|
||||||
|
token_t token;
|
||||||
|
if (snbt_next_token(&token, stream) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token.type == TOK_COMMA)
|
||||||
|
continue;
|
||||||
|
if (token.type == TOK_RBRACK)
|
||||||
|
break;
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tag->type = TAG_INT_ARRAY;
|
||||||
|
tag->data.i_arr.data = xalloc(len * sizeof(int32_t));
|
||||||
|
tag->data.i_arr.size = len;
|
||||||
|
memcpy(tag->data.i_arr.data, buf, len * sizeof(int32_t));
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_long_array(tag_t *tag, const stream_t *stream) {
|
||||||
|
int capacity = 8;
|
||||||
|
int len = 0;
|
||||||
|
int64_t *buf = xalloc(capacity * sizeof(int64_t));
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
tag_t value;
|
||||||
|
if (snbt_read_value(&value, stream, NULL) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.type != TAG_LONG) {
|
||||||
|
tag_free(&value);
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
buf = xrealloc(buf, capacity * sizeof(int64_t));
|
||||||
|
}
|
||||||
|
buf[len++] = value.data.l;
|
||||||
|
token_t token;
|
||||||
|
if (snbt_next_token(&token, stream) == false) {
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (token.type == TOK_COMMA)
|
||||||
|
continue;
|
||||||
|
if (token.type == TOK_RBRACK)
|
||||||
|
break;
|
||||||
|
free(buf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tag->type = TAG_LONG_ARRAY;
|
||||||
|
tag->data.l_arr.data = xalloc(len * sizeof(int64_t));
|
||||||
|
tag->data.l_arr.size = len;
|
||||||
|
memcpy(tag->data.l_arr.data, buf, len * sizeof(int64_t));
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_array(tag_t *tag, tagtype_t type, const stream_t *stream) {
|
||||||
|
switch(type) {
|
||||||
|
case TAG_BYTE:
|
||||||
|
return snbt_read_byte_array(tag, stream);
|
||||||
|
case TAG_INT:
|
||||||
|
return snbt_read_int_array(tag, stream);
|
||||||
|
case TAG_LONG:
|
||||||
|
return snbt_read_long_array(tag, stream);
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_list(tag_t *tag, tag_t *first, token_t *tok_sav, const stream_t *stream) {
|
||||||
|
int capacity = 8;
|
||||||
|
int len = 0;
|
||||||
|
tag_t *tags = xalloc(sizeof(tag_t) * capacity);
|
||||||
|
tagtype_t type = TAG_END;
|
||||||
|
|
||||||
|
if (first != NULL) {
|
||||||
|
first->name_len = 0;
|
||||||
|
first->name = "";
|
||||||
|
tags[len++] = *first;
|
||||||
|
type = first->type;
|
||||||
|
goto endcheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tok_sav == NULL) {
|
||||||
|
if (snbt_next_token(tok_sav, stream) == false) {
|
||||||
|
free(tags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (tok_sav->type == TOK_RBRACK) {
|
||||||
|
goto end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while(1) {
|
||||||
|
|
||||||
|
tag_t value;
|
||||||
|
token_t check;
|
||||||
|
|
||||||
|
if (snbt_read_value(&value, stream, tok_sav) == false) {
|
||||||
|
snbt_free_token(tok_sav);
|
||||||
|
free(tags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tok_sav = NULL;
|
||||||
|
|
||||||
|
if (type != TAG_END && value.type != type) {
|
||||||
|
free(tags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len == capacity) {
|
||||||
|
capacity *= 2;
|
||||||
|
tags = xrealloc(tags, capacity * sizeof(tag_t));
|
||||||
|
}
|
||||||
|
|
||||||
|
tags[len++] = value;
|
||||||
|
|
||||||
|
endcheck:
|
||||||
|
|
||||||
|
if (snbt_next_token(&check, stream) == false) {
|
||||||
|
free(tags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (check.type == TOK_COMMA)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (check.type == TOK_RBRACK)
|
||||||
|
break;
|
||||||
|
|
||||||
|
free(tags);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
end:
|
||||||
|
tag->type = TAG_LIST;
|
||||||
|
tag->data.list.type = type;
|
||||||
|
tag->data.list.size = len;
|
||||||
|
tag->data.list.tags = xalloc(len * sizeof(tag_t));
|
||||||
|
memcpy(tag->data.list.tags, tags, len *sizeof(tag_t));
|
||||||
|
free(tags);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_collection(tag_t *tag, const stream_t *stream) {
|
||||||
|
|
||||||
|
token_t first;
|
||||||
|
|
||||||
|
if (snbt_next_token(&first, stream) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (first.type == TOK_RBRACK) {
|
||||||
|
tag->type = TAG_LIST;
|
||||||
|
tag->data.list.type = TAG_END;
|
||||||
|
tag->data.list.size = 0;
|
||||||
|
tag->data.list.tags = NULL;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.type == TOK_IDENT && first.data.text.len == 1) {
|
||||||
|
tagtype_t type;
|
||||||
|
char c = first.data.text.data[0];
|
||||||
|
switch (c) {
|
||||||
|
case 'B':
|
||||||
|
type = TAG_BYTE;
|
||||||
|
break;
|
||||||
|
case 'I':
|
||||||
|
type = TAG_INT;
|
||||||
|
break;
|
||||||
|
case 'L':
|
||||||
|
type = TAG_LONG;
|
||||||
|
break;
|
||||||
|
default: {
|
||||||
|
tag_t new;
|
||||||
|
if (snbt_convert_ident(&new, first) == false) {
|
||||||
|
snbt_free_token(&first);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return snbt_read_list(tag, &new, NULL, stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
token_t second;
|
||||||
|
if (snbt_next_token(&second, stream) == false) {
|
||||||
|
snbt_free_token(&first);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (second.type == TOK_COMMA) {
|
||||||
|
tag_t new;
|
||||||
|
new.type = TAG_STRING;
|
||||||
|
new.data.string.data = first.data.text.data;
|
||||||
|
new.data.string.size = first.data.text.len;
|
||||||
|
return snbt_read_list(tag, &new, NULL, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
snbt_free_token(&first);
|
||||||
|
|
||||||
|
if (second.type == TOK_SEMI_COLON) {
|
||||||
|
return snbt_read_array(tag, type, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
snbt_free_token(&second);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return snbt_read_list(tag, NULL, &first, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_compound(tagdata_t *data, const stream_t *stream) {
|
||||||
|
map_t map;
|
||||||
|
map_init(&map);
|
||||||
|
|
||||||
|
token_t next = {0};
|
||||||
|
if (snbt_next_token(&next, stream) == false) {
|
||||||
|
snbt_free_token(&next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type == TOK_RBRACE) {
|
||||||
|
data->compound = map;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
|
||||||
|
if (next.type != TOK_STRING && next.type != TOK_IDENT) {
|
||||||
|
map_free(&map);
|
||||||
|
snbt_free_token(&next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *name = next.data.text.data;
|
||||||
|
int name_len = next.data.text.len;
|
||||||
|
|
||||||
|
if (name_len < 1) {
|
||||||
|
map_free(&map);
|
||||||
|
free(name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snbt_next_token(&next, stream) == false || next.type != TOK_COLON) {
|
||||||
|
map_free(&map);
|
||||||
|
free(name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag_t value;
|
||||||
|
if (snbt_read_value(&value, stream, NULL) == false) {
|
||||||
|
map_free(&map);
|
||||||
|
free(name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value.name = name;
|
||||||
|
value.name_len = name_len;
|
||||||
|
|
||||||
|
map_put(&map, &value);
|
||||||
|
|
||||||
|
if (snbt_next_token(&next, stream) == false) {
|
||||||
|
map_free(&map);
|
||||||
|
snbt_free_token(&next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.type == TOK_COMMA) {
|
||||||
|
if (snbt_next_token(&next, stream) == false) {
|
||||||
|
map_free(&map);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else if (next.type == TOK_RBRACE) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
map_free(&map);
|
||||||
|
snbt_free_token(&next);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data->compound = map;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_convert_decimal(tag_t *tag, char *text) {
|
||||||
|
uint16_t len = strlen(text);
|
||||||
|
|
||||||
|
char *end = NULL;
|
||||||
|
|
||||||
|
double d = strtod(text, &end);
|
||||||
|
|
||||||
|
char c = *end;
|
||||||
|
bool check1 = end == &text[len - 1] && (
|
||||||
|
c == 'f' ||
|
||||||
|
c == 'F' ||
|
||||||
|
c == 'd' ||
|
||||||
|
c == 'D'
|
||||||
|
);
|
||||||
|
bool check2 = end == &text[len] && c == '\0';
|
||||||
|
if (!check1 && !check2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag->type == TAG_FLOAT) {
|
||||||
|
tag->data.f = (float)d;
|
||||||
|
} else {
|
||||||
|
tag->data.d = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_convert_int(tag_t *tag, char *text) {
|
||||||
|
uint16_t len = strlen(text);
|
||||||
|
|
||||||
|
char *end = NULL;
|
||||||
|
|
||||||
|
uint64_t i = strtol(text, &end, 10);
|
||||||
|
|
||||||
|
char c = *end;
|
||||||
|
bool check1 = end == &text[len - 1] && (
|
||||||
|
c == 'b' ||
|
||||||
|
c == 'B' ||
|
||||||
|
c == 's' ||
|
||||||
|
c == 'S' ||
|
||||||
|
c == 'l' ||
|
||||||
|
c == 'L'
|
||||||
|
);
|
||||||
|
bool check2 = end == &text[len] && c == '\0';
|
||||||
|
if (!check1 && !check2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag->type == TAG_BYTE) {
|
||||||
|
tag->data.b = (uint8_t)i;
|
||||||
|
} else if (tag->type == TAG_SHORT) {
|
||||||
|
tag->data.s = (uint16_t)i;
|
||||||
|
} else if (tag->type == TAG_INT) {
|
||||||
|
tag->data.i = (uint32_t)i;
|
||||||
|
} else {
|
||||||
|
tag->data.l = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_convert_ident(tag_t *tag, token_t token) {
|
||||||
|
uint16_t len = token.data.text.len + 1;
|
||||||
|
char *text = xalloc(len * sizeof(char));
|
||||||
|
memcpy(text, token.data.text.data, len - 1);
|
||||||
|
text[len - 1] = '\0';
|
||||||
|
|
||||||
|
if (len == 4 && memcmp(text, "true", 4) == 0) {
|
||||||
|
tag->type = TAG_BYTE;
|
||||||
|
tag->data.b = 1;
|
||||||
|
} else if (len == 5 && memcmp(text, "false", 5) == 0) {
|
||||||
|
tag->type = TAG_BYTE;
|
||||||
|
tag->data.b = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to parse as a number lol he he ha ha
|
||||||
|
char end = text[len - 2];
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
|
||||||
|
switch (end) {
|
||||||
|
case 'f':
|
||||||
|
case 'F':
|
||||||
|
tag->type = TAG_FLOAT;
|
||||||
|
ok = snbt_convert_decimal(tag, text);
|
||||||
|
break;
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
tag->type = TAG_DOUBLE;
|
||||||
|
ok = snbt_convert_decimal(tag, text);
|
||||||
|
break;
|
||||||
|
case 'b':
|
||||||
|
case 'B':
|
||||||
|
tag->type = TAG_BYTE;
|
||||||
|
ok = snbt_convert_int(tag, text);
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
case 'S':
|
||||||
|
tag->type = TAG_SHORT;
|
||||||
|
ok = snbt_convert_int(tag, text);
|
||||||
|
break;
|
||||||
|
case 'l':
|
||||||
|
case 'L':
|
||||||
|
tag->type = TAG_LONG;
|
||||||
|
ok = snbt_convert_int(tag, text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
free(text);
|
||||||
|
snbt_free_token(&token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// unkown type try long >> double >> string (fallback)
|
||||||
|
|
||||||
|
tag->type = TAG_INT;
|
||||||
|
if (snbt_convert_int(tag, text) == true) {
|
||||||
|
free(text);
|
||||||
|
snbt_free_token(&token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag->type = TAG_DOUBLE;
|
||||||
|
if (snbt_convert_decimal(tag, text) == true) {
|
||||||
|
free(text);
|
||||||
|
snbt_free_token(&token);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag->type = TAG_STRING;
|
||||||
|
tag->data.string.data = token.data.text.data;
|
||||||
|
tag->data.string.size = token.data.text.len;
|
||||||
|
free(text);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool snbt_read_value(tag_t *tag, const stream_t *stream, token_t *first) {
|
||||||
|
|
||||||
|
token_t token;
|
||||||
|
|
||||||
|
if (first != NULL)
|
||||||
|
token = *first;
|
||||||
|
else if (snbt_next_token(&token, stream) == false)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
tag->name = "";
|
||||||
|
tag->name_len = 0;
|
||||||
|
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
switch (token.type) {
|
||||||
|
case TOK_RBRACK:
|
||||||
|
case TOK_RBRACE:
|
||||||
|
case TOK_COLON:
|
||||||
|
case TOK_SEMI_COLON:
|
||||||
|
case TOK_COMMA:
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
case TOK_LBRACK:
|
||||||
|
tag->type = TAG_LIST;
|
||||||
|
ok = snbt_read_collection(tag, stream);
|
||||||
|
break;
|
||||||
|
case TOK_LBRACE:
|
||||||
|
tag->type = TAG_COMPOUND;
|
||||||
|
ok = snbt_read_compound(&tag->data, stream);
|
||||||
|
break;
|
||||||
|
case TOK_STRING:
|
||||||
|
tag->type = TAG_STRING;
|
||||||
|
tag->data.string.data = token.data.text.data;
|
||||||
|
tag->data.string.size = token.data.text.len;
|
||||||
|
break;
|
||||||
|
case TOK_IDENT:
|
||||||
|
ok = snbt_convert_ident(tag, token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool snbt_read(tag_t *tag, const stream_t *stream) {
|
||||||
|
return snbt_read_value(tag, stream, NULL);
|
||||||
|
}
|
9
src/snbt/snbt.h
Normal file
9
src/snbt/snbt.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../tag.h"
|
||||||
|
#include "../stream.h"
|
||||||
|
|
||||||
|
bool snbt_read(tag_t *tag, const stream_t *stream);
|
||||||
|
bool snbt_print(const tag_t *tag, const stream_t *stream);
|
||||||
|
|
||||||
|
bool snbt_allowed_ident(char c);
|
|
@ -7,6 +7,8 @@
|
||||||
typedef struct {
|
typedef struct {
|
||||||
FILE *__file;
|
FILE *__file;
|
||||||
bool __alloc;
|
bool __alloc;
|
||||||
|
char peakbuf[16];
|
||||||
|
int peakamt;
|
||||||
} stream_t;
|
} stream_t;
|
||||||
|
|
||||||
stream_t stream_open(const char *path, const char* mode);
|
stream_t stream_open(const char *path, const char* mode);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
#include "map.h"
|
#include "map.h"
|
||||||
#include "nbt/nbt.h"
|
#include "nbt/nbt.h"
|
||||||
|
#include "snbt/snbt.h"
|
||||||
#include "json/json.h"
|
#include "json/json.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
@ -47,6 +48,7 @@ bool tag_read(tag_t *tag, const stream_t *stream, format_t format) {
|
||||||
case NBT:
|
case NBT:
|
||||||
return nbt_read(tag, stream);
|
return nbt_read(tag, stream);
|
||||||
case SNBT:
|
case SNBT:
|
||||||
|
return snbt_read(tag, stream);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +61,7 @@ bool tag_print(tag_t *tag, const stream_t *stream, format_t format) {
|
||||||
case NBT:
|
case NBT:
|
||||||
return nbt_print(tag, stream);
|
return nbt_print(tag, stream);
|
||||||
case SNBT:
|
case SNBT:
|
||||||
|
return snbt_print(tag, stream);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue