Files
xtchain/tools/includes/xtchain.h
Aiken Harris 9931e89679
All checks were successful
Builds / XTchain (full, linux) (push) Successful in 1h41m44s
Builds / XTchain (full, windows) (push) Successful in 1h43m17s
Builds / XTchain (minimal, linux) (push) Successful in 1m46s
Builds / XTchain (minimal, windows) (push) Successful in 1m40s
Update toolchain and add NLS converter
2026-10-02 18:21:17 +02:00

1804 lines
40 KiB
C

/**
* PROJECT: XTchain
* LICENSE: See COPYING.md in the top level directory
* FILE: tools/xtchain.h
* DESCRIPTION: Common header for XTchain tools
* DEVELOPERS: Martin Storsjo <martin@martin.st>
* Rafal Kupiec <belliash@codingworkshop.eu.org>
* Aiken Harris <harraiken91@gmail.com>
*/
#ifndef _WIN32
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#endif
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <dirent.h>
#include <stdarg.h>
#include <setjmp.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <windows.h>
#include <process.h>
#include <io.h>
#define XTCHAIN_PROCESS_ID _getpid
#define PATH_SEP '\\'
#else
#define XTCHAIN_PROCESS_ID getpid
#define PATH_SEP '/'
#endif
#define SECTOR_SIZE 512
#define _T(x) x
#define YAML_MAX_BINARY (64u * 1024u * 1024u)
#define YAML_MAX_TEXT (512u * 1024u * 1024u)
typedef struct MBR_PARTITION {
uint8_t BootFlag; // 0x80 = bootable, 0x00 = non-boot
uint8_t StartCHS [3]; // CHS address
uint8_t Type; // Partition type
uint8_t EndCHS[3]; // CHS address
uint32_t StartLBA; // Start sector
uint32_t Size; // Sectors count
} MBR_PARTITION, *PMBR_PARTITION;
typedef struct _RESERVED_SECTOR_INFO
{
int SectorNumber;
const char* Description;
} RESERVED_SECTOR_INFO, *PRESERVED_SECTOR_INFO;
typedef struct _YAML_BUFFER
{
unsigned char *data;
size_t len, cap;
} YAML_BUFFER;
typedef enum _YAML_NODE_TYPE
{
YAML_SCALAR,
YAML_MAP,
YAML_SEQUENCE,
YAML_BINARY
} YAML_NODE_TYPE;
typedef struct _YAML_NODE YAML_NODE;
struct _YAML_NODE
{
YAML_NODE_TYPE type;
char *text;
int IsInteger;
unsigned char *bytes;
size_t length;
char **keys;
YAML_NODE **values;
size_t count, cap, *slots, slots_cap;
};
typedef struct YAML_ALLOCATION
{
void *p;
struct YAML_ALLOCATION *next;
} YAML_ALLOCATION;
typedef struct YAML_LINE
{
char *s;
size_t n, indent, number;
} YAML_LINE;
typedef struct YAML_PARSER
{
YAML_LINE *lines;
size_t count, at, col;
} YAML_PARSER;
typedef struct YAML_INLINE
{
const char *s;
size_t n, at, line;
} YAML_INLINE;
static const char *YamlProgramName = "xtchain";
static const char *(*YamlCharacterName)(uint32_t Codepoint) = NULL;
static YAML_ALLOCATION *YamlAllocations;
static void (*YamlValidationError)(void);
static inline void YamlWriterEmit(FILE *f, YAML_NODE *n, unsigned depth);
static inline YAML_NODE * YamlParserBlock(YAML_PARSER *p, size_t indent, unsigned depth);
static
inline
char *
_tcsrchrs(const char *str,
char char1,
char char2)
{
char *ptr1 = strrchr(str, char1);
char *ptr2 = strrchr(str, char2);
if(!ptr1)
{
return ptr2;
}
if(!ptr2)
{
return ptr1;
}
if(ptr1 < ptr2)
{
return ptr2;
}
return ptr1;
}
static
inline
void
split_argv(const char *argv0,
char **dir_ptr,
char **basename_ptr,
char **target_ptr,
char **exe_ptr)
{
const char *sep = _tcsrchrs(argv0, '/', '\\');
const char *basename_ptr_const = argv0;
char *dir = strdup(_T(""));
if(sep)
{
dir = strdup(argv0);
dir[sep + 1 - argv0] = '\0';
basename_ptr_const = sep + 1;
}
char *basename = strdup(basename_ptr_const);
char *period = strchr(basename, '.');
if(period)
{
*period = '\0';
}
char *target = strdup(basename);
char *dash = strrchr(target, '-');
char *exe = basename;
if(dash)
{
*dash = '\0';
exe = dash + 1;
}
else
{
target = NULL;
}
if(dir_ptr)
{
*dir_ptr = dir;
}
if(basename_ptr)
{
*basename_ptr = basename;
}
if(target_ptr)
{
*target_ptr = target;
}
if(exe_ptr)
{
*exe_ptr = exe;
}
}
/* Release only the allocations made after a validation checkpoint. */
static
inline
void
YamlReleaseAllocations(YAML_ALLOCATION *checkpoint)
{
while(YamlAllocations != checkpoint)
{
YAML_ALLOCATION *a = YamlAllocations;
YamlAllocations = a->next;
free(a->p);
free(a);
}
}
/* A per-command arena ensures cleanup on every validation failure as well. */
static
inline
void
YamlCleanup(void)
{
YamlReleaseAllocations(NULL);
}
static
inline
void
YamlError(const char *format,
...)
{
if(YamlValidationError)
{
YamlValidationError();
}
va_list ap;
va_start(ap, format);
fprintf(stderr, "%s: error: ", YamlProgramName);
vfprintf(stderr, format, ap);
fputc('\n', stderr);
va_end(ap);
exit(1);
}
static
inline
void *
YamlAllocateMemory(size_t n)
{
YAML_ALLOCATION *a = malloc(sizeof(*a));
void *p = calloc(n ? n : 1, 1);
if(!a || !p)
{
free(a);
free(p);
/* Resource exhaustion is fatal, even during format detection. */
YamlValidationError = NULL;
YamlError("out of memory");
}
a->p = p;
a->next = YamlAllocations;
YamlAllocations = a;
return p;
}
static
inline
char *
YamlCopyString(const char *s)
{
size_t n = strlen(s) + 1;
char *p = YamlAllocateMemory(n);
memcpy(p, s, n);
return p;
}
static
inline
char *
YamlFormatString(const char *format,
...)
{
if(!format)
{
YamlError("null format string");
}
va_list ap, aq;
va_start(ap, format);
va_copy(aq, ap);
int n = vsnprintf(NULL, 0, format, aq);
va_end(aq);
if(n < 0)
{
YamlError("formatting failed");
}
char *p = YamlAllocateMemory((size_t)n + 1);
vsnprintf(p, (size_t)n + 1, format, ap);
va_end(ap);
return p;
}
static
inline
void
YamlAppendBuffer(YAML_BUFFER *b,
const void *data,
size_t n)
{
if(n > YAML_MAX_BINARY || b->len > YAML_MAX_BINARY - n)
{
YamlError("binary exceeds 64 MiB");
}
if(b->len + n > b->cap)
{
size_t cap = b->cap ? b->cap : 1024;
while(cap < b->len + n)
{
cap *= 2;
}
unsigned char *p = YamlAllocateMemory(cap);
if(b->len)
{
memcpy(p, b->data, b->len);
}
b->data = p;
b->cap = cap;
}
if(n)
{
memcpy(b->data + b->len, data, n);
}
b->len += n;
}
static
inline
void
WriteUInt16(YAML_BUFFER *b,
uint32_t v)
{
if(v > 65535)
{
YamlError("value does not fit 16 bits");
}
unsigned char p[2] = {(unsigned char)v, (unsigned char)(v >> 8)};
YamlAppendBuffer(b, p, 2);
}
static
inline
void
WriteUInt32(YAML_BUFFER *b,
uint32_t v)
{
unsigned char p[4] = {
(unsigned char)v, (unsigned char)(v >> 8), (unsigned char)(v >> 16), (unsigned char)(v >> 24)};
YamlAppendBuffer(b, p, 4);
}
static
inline
uint32_t
ReadUInt16(const unsigned char *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8);
}
static
inline
uint32_t
ReadUInt32(const unsigned char *p)
{
return ReadUInt16(p) | (ReadUInt16(p + 2) << 16);
}
static
inline
YAML_NODE *
YamlCreateNode(YAML_NODE_TYPE type)
{
YAML_NODE *n = YamlAllocateMemory(sizeof(*n));
n->type = type;
return n;
}
static
inline
YAML_NODE *
YamlCreateString(const char *s)
{
YAML_NODE *n = YamlCreateNode(YAML_SCALAR);
n->text = YamlCopyString(s);
return n;
}
static
inline
YAML_NODE *
YamlCreateInteger(uint32_t v)
{
YAML_NODE *n = YamlCreateString(YamlFormatString("%u", v));
n->IsInteger = 1;
return n;
}
static
inline
YAML_NODE *
YamlCreateHexadecimal(uint32_t v,
unsigned width)
{
return YamlCreateString(YamlFormatString("0x%0*X", (int)width, v));
}
static
inline
YAML_NODE *
YamlCreateCodepoint(uint32_t v)
{
return YamlCreateString(YamlFormatString("U+%04X", v));
}
static
inline
YAML_NODE *
YamlCreateBinary(const void *data,
size_t n)
{
YAML_NODE *v = YamlCreateNode(YAML_BINARY);
v->bytes = YamlAllocateMemory(n);
if(n)
{
memcpy(v->bytes, data, n);
}
v->length = n;
return v;
}
static
inline
size_t
YamlWriterHash(const char *s)
{
size_t h = 2166136261u;
for(; *s; s++)
{
h = (h ^ (unsigned char)*s) * 16777619u;
}
return h;
}
static
inline
void
YamlWriterReserve(YAML_NODE *n)
{
if(n->count == n->cap)
{
size_t cap = n->cap ? n->cap * 2 : 8;
if(cap > 2097152)
{
YamlError("too many YAML entries");
}
YAML_NODE **v = YamlAllocateMemory(cap * sizeof(*v));
char **k = YamlAllocateMemory(cap * sizeof(*k));
if(n->count)
{
memcpy(v, n->values, n->count * sizeof(*v));
memcpy(k, n->keys, n->count * sizeof(*k));
}
n->values = v;
n->keys = k;
n->cap = cap;
}
}
static
inline
void
YamlAddField(YAML_NODE *n,
const char *key,
YAML_NODE *v)
{
if(!n || n->type != YAML_MAP)
{
YamlError("expected mapping");
}
if(!n->slots_cap || (n->count + 1) * 2 >= n->slots_cap)
{
size_t cap = n->slots_cap ? n->slots_cap * 2 : 16;
size_t *slots = YamlAllocateMemory(cap * sizeof(*slots));
for(size_t i = 0; i < n->count; i++)
{
size_t s = YamlWriterHash(n->keys[i]) & (cap - 1);
while(slots[s])
{
s = (s + 1) & (cap - 1);
}
slots[s] = i + 1;
}
n->slots = slots;
n->slots_cap = cap;
}
size_t s = YamlWriterHash(key) & (n->slots_cap - 1);
while(n->slots[s])
{
if(!strcmp(n->keys[n->slots[s] - 1], key))
{
YamlError("duplicate YAML key: %s", key);
}
s = (s + 1) & (n->slots_cap - 1);
}
YamlWriterReserve(n);
n->keys[n->count] = YamlCopyString(key);
n->values[n->count] = v;
n->slots[s] = ++n->count;
}
static
inline
void
YamlAppendItem(YAML_NODE *n,
YAML_NODE *v)
{
if(!n || n->type != YAML_SEQUENCE)
{
YamlError("expected sequence");
}
YamlWriterReserve(n);
n->values[n->count++] = v;
}
static
inline
YAML_NODE *
YamlFindField(YAML_NODE *n,
const char *key)
{
if(!n || n->type != YAML_MAP)
{
YamlError("expected mapping for %s", key);
}
if(!n->slots_cap)
{
return NULL;
}
size_t s = YamlWriterHash(key) & (n->slots_cap - 1);
while(n->slots[s])
{
if(!strcmp(n->keys[n->slots[s] - 1], key))
{
return n->values[n->slots[s] - 1];
}
s = (s + 1) & (n->slots_cap - 1);
}
return NULL;
}
static
inline
YAML_NODE *
YamlGetField(YAML_NODE *n,
const char *key)
{
YAML_NODE *v = YamlFindField(n, key);
if(!v)
{
YamlError("missing field: %s", key);
}
return v;
}
static
inline
int
YamlWriterInList(const char *list,
const char *key)
{
size_t n = strlen(key);
while(*list)
{
size_t len = strcspn(list, " ");
if(n == len && !memcmp(list, key, n))
{
return 1;
}
list += len;
while(*list == ' ')
{
list++;
}
}
return 0;
}
static
inline
void
YamlValidateFields(YAML_NODE *n,
const char *required,
const char *allowed)
{
if(!n || n->type != YAML_MAP)
{
YamlError("expected mapping");
}
for(size_t i = 0; i < n->count; i++)
{
if(!YamlWriterInList(required, n->keys[i]) && !YamlWriterInList(allowed, n->keys[i]))
{
YamlError("unexpected field: %s", n->keys[i]);
}
}
char *r = YamlCopyString(required), *p = r;
while(*p)
{
char *end = strchr(p, ' ');
if(end)
{
*end = 0;
}
(void)YamlGetField(n, p);
if(!end)
{
break;
}
p = end + 1;
}
}
const
char *
YamlGetScalar(YAML_NODE *n)
{
if(!n || n->type != YAML_SCALAR)
{
YamlError("expected scalar");
}
return n->text;
}
static
inline
uint32_t
YamlParseNumber(const char *s,
unsigned base,
uint32_t max)
{
uint64_t YamlWriterValue = 0;
if(!*s)
{
YamlError("empty number");
}
for(const unsigned char *p = (const unsigned char *)s; *p; p++)
{
unsigned digit = *p >= '0' && *p <= '9' ? *p - '0'
: *p >= 'A' && *p <= 'F' ? *p - 'A' + 10
: *p >= 'a' && *p <= 'f' ? *p - 'a' + 10
: 99;
if(digit >= base || YamlWriterValue > (UINT32_MAX - digit) / base)
{
YamlError("invalid number: %s", s);
}
YamlWriterValue = YamlWriterValue * base + digit;
}
if(YamlWriterValue > max)
{
YamlError("number out of range: %s", s);
}
return (uint32_t)YamlWriterValue;
}
static
inline
uint32_t
YamlGetUnsigned(YAML_NODE *n,
uint32_t max)
{
return YamlParseNumber(YamlGetScalar(n), 10, max);
}
static
inline
uint32_t
YamlGetHexadecimal(YAML_NODE *n,
uint32_t max)
{
const char *s = YamlGetScalar(n);
if(strncmp(s, "0x", 2) || strlen(s) > 10)
{
YamlError("expected 0x... encoded value");
}
return YamlParseNumber(s + 2, 16, max);
}
static
inline
uint32_t
YamlGetCodepoint(YAML_NODE *n)
{
const char *s = YamlGetScalar(n);
if(strlen(s) != 6 || strncmp(s, "U+", 2))
{
YamlError("expected U+XXXX");
}
return YamlParseNumber(s + 2, 16, 65535);
}
static
inline
int
YamlWriterBase64Digit(unsigned char c)
{
if(c >= 'A' && c <= 'Z')
{
return c - 'A';
}
if(c >= 'a' && c <= 'z')
{
return c - 'a' + 26;
}
if(c >= '0' && c <= '9')
{
return c - '0' + 52;
}
return c == '+' ? 62 : c == '/' ? 63 : c == '=' ? 64 : -1;
}
static
inline
YAML_NODE *
YamlReadBinary(const unsigned char *s,
size_t n)
{
YAML_BUFFER b = {0};
unsigned q[4], used = 0;
int ended = 0;
for(size_t i = 0; i < n; i++)
{
if(isspace(s[i]))
{
continue;
}
int d = YamlWriterBase64Digit(s[i]);
if(d < 0 || ended)
{
YamlError("invalid Base64");
}
q[used++] = (unsigned)d;
if(used == 4)
{
if(q[0] > 63 || q[1] > 63 || (q[2] == 64 && q[3] != 64))
{
YamlError("invalid Base64 padding");
}
unsigned char bytes[3] = {(unsigned char)((q[0] << 2) | (q[1] >> 4)),
(unsigned char)((q[1] << 4) | (q[2] >> 2)),
(unsigned char)((q[2] << 6) | q[3])};
size_t count = q[2] == 64 ? 1 : q[3] == 64 ? 2 : 3;
if((count == 1 && (q[1] & 15)) || (count == 2 && (q[2] & 3)))
{
YamlError("noncanonical Base64 padding bits");
}
YamlAppendBuffer(&b, bytes, count);
ended = count != 3;
used = 0;
}
}
if(used)
{
YamlError("incomplete Base64 group");
}
return YamlCreateBinary(b.data, b.len);
}
static
inline
void
YamlWriterIndent(FILE *f,
unsigned n)
{
while(n--)
{
fputc(' ', f);
}
}
static
inline
void
YamlWriterQuoted(FILE *f,
const char *s)
{
int escaped = 0;
for(const unsigned char *p = (const unsigned char *)s; *p; p++)
{
if(*p < 0x20 || *p == 0x7f || *p >= 0x80)
{
escaped = 1;
}
}
if(escaped)
{
fputc('"', f);
for(const unsigned char *p = (const unsigned char *)s; *p; p++)
{
if(*p == '"' || *p == '\\')
{
fputc('\\', f);
fputc(*p, f);
}
else if(*p < 0x20 || *p == 0x7f)
{
fprintf(f, "\\u%04X", *p);
}
else if(*p >= 0x80)
{
uint32_t c = *p;
unsigned extra;
if(c < 0xe0)
{
c &= 31;
extra = 1;
}
else if(c < 0xf0)
{
c &= 15;
extra = 2;
}
else
{
c &= 7;
extra = 3;
}
while(extra--)
{
p++;
c = (c << 6) | (*p & 63);
}
fprintf(f, c <= 65535 ? "\\u%04X" : "\\U%08X", c);
}
else
{
fputc(*p, f);
}
}
fputc('"', f);
}
else
{
fputc('\'', f);
for(; *s; s++)
{
fputc(*s, f);
if(*s == '\'')
{
fputc('\'', f);
}
}
fputc('\'', f);
}
}
static
inline
int
YamlWriterPlain(const char *s)
{
if(!*s)
{
return 0;
}
const char *reserved[] = {"null", "true", "false", "yes", "no", "on", "off", "y", "n"};
for(size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++)
{
size_t j = 0;
while(s[j] && reserved[i][j] && tolower((unsigned char)s[j]) == reserved[i][j])
{
j++;
}
if(!s[j] && !reserved[i][j])
{
return 0;
}
}
for(const unsigned char *p = (const unsigned char *)s; *p; p++)
{
if(!isalnum(*p) && *p != '_' && *p != '-' && *p != '+')
{
return 0;
}
}
return strncmp(s, "0x", 2) != 0;
}
static
inline
void
YamlWriterOutputScalar(FILE *f,
YAML_NODE *n)
{
if(n->IsInteger ||
(YamlWriterPlain(n->text) && !isdigit((unsigned char)n->text[0]) && n->text[0] != '-' && n->text[0] != '+'))
{
fputs(n->text, f);
}
else
{
YamlWriterQuoted(f, n->text);
}
}
static
inline
int
YamlWriterCodepoint(const char *s)
{
if(!s || strlen(s) != 6 || strncmp(s, "U+", 2))
{
return -1;
}
for(unsigned i = 2; i < 6; i++)
{
if(!isxdigit((unsigned char)s[i]))
{
return -1;
}
}
return (int)strtoul(s + 2, NULL, 16);
}
static
inline
void
YamlWriterComment(FILE *f,
const char *key,
YAML_NODE *v)
{
int c = v->type == YAML_SCALAR ? YamlWriterCodepoint(v->text) : -1;
if(c < 0)
{
c = YamlWriterCodepoint(key);
}
if(c >= 0 && YamlCharacterName)
{
fprintf(f, " # %s", YamlCharacterName((uint32_t)c));
}
}
static
inline
void
YamlWriterValue(FILE *f,
YAML_NODE *v,
unsigned depth,
const char *key)
{
if(v->type == YAML_SCALAR)
{
fputc(' ', f);
YamlWriterOutputScalar(f, v);
YamlWriterComment(f, key, v);
fputc('\n', f);
}
else if(v->type == YAML_BINARY)
{
static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
if(!v->length)
{
fputs(" !!binary ''\n", f);
return;
}
fputs(" !!binary |\n", f);
unsigned column = 0;
for(size_t i = 0; i < v->length; i += 3)
{
uint32_t bits = (uint32_t)v->bytes[i] << 16;
if(i + 1 < v->length)
{
bits |= (uint32_t)v->bytes[i + 1] << 8;
}
if(i + 2 < v->length)
{
bits |= v->bytes[i + 2];
}
char chars[4] = {alphabet[bits >> 18],
alphabet[(bits >> 12) & 63],
i + 1 < v->length ? alphabet[(bits >> 6) & 63] : '=',
i + 2 < v->length ? alphabet[bits & 63] : '='};
if(!column)
{
YamlWriterIndent(f, depth);
}
fwrite(chars, 1, 4, f);
column += 4;
if(column == 76)
{
fputc('\n', f);
column = 0;
}
}
if(column)
{
fputc('\n', f);
}
}
else if(!v->count)
{
fputs(v->type == YAML_MAP ? " {}\n" : " []\n", f);
}
else
{
fputc('\n', f);
YamlWriterEmit(f, v, depth);
}
}
static
inline
void
YamlWriterEmit(FILE *f,
YAML_NODE *n,
unsigned depth)
{
for(size_t i = 0; i < n->count; i++)
{
YamlWriterIndent(f, depth);
if(n->type == YAML_MAP)
{
if(YamlWriterPlain(n->keys[i]))
{
fputs(n->keys[i], f);
}
else
{
YamlWriterQuoted(f, n->keys[i]);
}
fputc(':', f);
}
else
{
fputc('-', f);
}
YamlWriterValue(f, n->values[i], depth + 2, n->type == YAML_MAP ? n->keys[i] : NULL);
}
}
static
inline
void
YamlWriteDocument(FILE *f,
YAML_NODE *root,
const char *Generator)
{
if(Generator)
{
fprintf(f, "# Generated by %s. Do not edit manually.\n", Generator);
}
YamlWriterEmit(f, root, 0);
if(ferror(f))
{
YamlError("cannot write YAML output");
}
}
/* Bounded YAML parser: block and flow collections, quoted scalars and binary values */
/* Bounded YAML subset with explicit scalar handling and no implicit type resolver */
static
inline
void
YamlParserError(size_t line,
const char *why)
{
YamlError("YAML line %zu: %s", line, why);
}
static
inline
int
YamlParserSpace(char c)
{
return c == ' ' || c == '\t';
}
static
inline
void
YamlParserWs(YAML_INLINE *p)
{
while(p->at < p->n && YamlParserSpace(p->s[p->at]))
{
p->at++;
}
}
static
inline
void
YamlParserDepthCheck(unsigned depth)
{
if(depth > 32)
{
YamlError("YAML nesting exceeds 32 levels");
}
}
static
inline
char *
YamlParserSlice(const char *s,
size_t n)
{
char *v = YamlAllocateMemory(n + 1);
if(n)
{
memcpy(v, s, n);
}
return v;
}
static
inline
void
YamlParserUtf8(YAML_BUFFER *b,
uint32_t c,
size_t line)
{
unsigned char v[4];
size_t n;
if(!c || c > 0x10ffff || (c >= 0xd800 && c <= 0xdfff))
{
YamlParserError(line, "invalid Unicode escape");
}
if(c < 0x80)
{
v[0] = (unsigned char)c;
n = 1;
}
else if(c < 0x800)
{
v[0] = 0xc0 | (c >> 6);
v[1] = 0x80 | (c & 63);
n = 2;
}
else if(c < 0x10000)
{
v[0] = 0xe0 | (c >> 12);
v[1] = 0x80 | ((c >> 6) & 63);
v[2] = 0x80 | (c & 63);
n = 3;
}
else
{
v[0] = 0xf0 | (c >> 18);
v[1] = 0x80 | ((c >> 12) & 63);
v[2] = 0x80 | ((c >> 6) & 63);
v[3] = 0x80 | (c & 63);
n = 4;
}
YamlAppendBuffer(b, v, n);
}
static
inline
char *
YamlParserQuoted(YAML_INLINE *p)
{
char quote = p->s[p->at++];
YAML_BUFFER b = {0};
while(p->at < p->n)
{
unsigned char c = (unsigned char)p->s[p->at++];
if(c == (unsigned char)quote)
{
if(quote == '\'' && p->at < p->n && p->s[p->at] == '\'')
{
p->at++;
YamlAppendBuffer(&b, &c, 1);
continue;
}
return YamlParserSlice((const char *)b.data, b.len);
}
if(quote == '"' && c == '\\')
{
if(p->at == p->n)
{
YamlParserError(p->line, "unfinished quoted escape");
}
c = (unsigned char)p->s[p->at++];
uint32_t cp = c;
switch(c)
{
case 'a':
cp = 7;
break;
case 'b':
cp = 8;
break;
case 't':
cp = 9;
break;
case 'n':
cp = 10;
break;
case 'v':
cp = 11;
break;
case 'f':
cp = 12;
break;
case 'r':
cp = 13;
break;
case 'e':
cp = 27;
break;
case 'N':
cp = 0x85;
break;
case '_':
cp = 0xa0;
break;
case 'L':
cp = 0x2028;
break;
case 'P':
cp = 0x2029;
break;
case ' ':
case '"':
case '/':
case '\\':
break;
case 'x':
case 'u':
case 'U':
{
unsigned digits = c == 'x' ? 2 : c == 'u' ? 4 : 8;
cp = 0;
if(p->n - p->at < digits)
{
YamlParserError(p->line, "short Unicode escape");
}
while(digits--)
{
unsigned char h = (unsigned char)p->s[p->at++];
unsigned d = h >= '0' && h <= '9' ? h - '0'
: h >= 'a' && h <= 'f' ? h - 'a' + 10
: h >= 'A' && h <= 'F' ? h - 'A' + 10
: 99;
if(d > 15)
{
YamlParserError(p->line, "invalid Unicode escape");
}
cp = cp * 16 + d;
}
break;
}
default:
YamlParserError(p->line, "unsupported quoted escape");
}
YamlParserUtf8(&b, cp, p->line);
}
else
{
YamlAppendBuffer(&b, &c, 1);
}
}
YamlParserError(p->line, "unterminated quote; multiline strings are not supported");
return NULL;
}
static
inline
char *
YamlParserTextValue(YAML_INLINE *p,
int key,
int flow)
{
YamlParserWs(p);
if(p->at == p->n)
{
YamlParserError(p->line, "missing scalar");
}
char c = p->s[p->at];
if(c == '\'' || c == '"')
{
return YamlParserQuoted(p);
}
if(strchr("!&*|>%@`{}[],?#", c) ||
((c == '-' || c == ':') && (p->at + 1 == p->n || YamlParserSpace(p->s[p->at + 1]))))
{
YamlParserError(p->line, "unsupported scalar, tag, anchor or alias");
}
size_t start = p->at;
while(p->at < p->n)
{
c = p->s[p->at];
if(c == '#' && (p->at == start || YamlParserSpace(p->s[p->at - 1])))
{
break;
}
if(flow && strchr(",[]{}", c))
{
break;
}
if(c == ':' &&
(key || p->at + 1 == p->n || YamlParserSpace(p->s[p->at + 1]) || (flow && strchr(",[]{}", p->s[p->at + 1]))))
{
break;
}
p->at++;
}
size_t end = p->at;
while(end > start && YamlParserSpace(p->s[end - 1]))
{
end--;
}
if(end == start)
{
YamlParserError(p->line, "missing scalar");
}
return YamlParserSlice(p->s + start, end - start);
}
static
inline
YAML_NODE *
YamlParserInlineValue(YAML_INLINE *p,
unsigned depth,
int flow)
{
YamlParserDepthCheck(depth);
YamlParserWs(p);
if(p->at == p->n)
{
YamlParserError(p->line, "missing value");
}
char open = p->s[p->at];
if(open == '[' || open == '{')
{
YAML_NODE *n = YamlCreateNode(open == '[' ? YAML_SEQUENCE : YAML_MAP);
char close = open == '[' ? ']' : '}';
p->at++;
YamlParserWs(p);
if(p->at < p->n && p->s[p->at] == close)
{
p->at++;
return n;
}
for(;;)
{
if(n->type == YAML_MAP)
{
YamlParserWs(p);
int quoted_key = p->at < p->n && (p->s[p->at] == '\'' || p->s[p->at] == '"');
char *key = YamlParserTextValue(p, 1, 1);
YamlParserWs(p);
if(p->at == p->n || p->s[p->at++] != ':')
{
YamlParserError(p->line, "expected ':' in flow mapping");
}
if(!quoted_key && p->at < p->n && !YamlParserSpace(p->s[p->at]) && !strchr("[{", p->s[p->at]))
{
YamlParserError(p->line, "plain flow mapping keys require separation after ':'");
}
if(!strcmp(key, "<<"))
{
YamlParserError(p->line, "YAML merge keys are not supported");
}
YamlAddField(n, key, YamlParserInlineValue(p, depth + 1, 1));
}
else
{
YamlAppendItem(n, YamlParserInlineValue(p, depth + 1, 1));
}
YamlParserWs(p);
if(p->at == p->n)
{
YamlParserError(p->line, "unterminated flow collection; multiline flow is not supported");
}
char c = p->s[p->at++];
if(c == close)
{
return n;
}
if(c != ',')
{
YamlParserError(p->line, "expected comma or closing delimiter");
}
YamlParserWs(p);
if(p->at < p->n && p->s[p->at] == close)
{
p->at++;
return n;
}
}
}
if(p->n - p->at >= 8 && !memcmp(p->s + p->at, "!!binary", 8) &&
(p->at + 8 == p->n || YamlParserSpace(p->s[p->at + 8])))
{
p->at += 8;
char *s = YamlParserTextValue(p, 0, flow);
return YamlReadBinary((const unsigned char *)s, strlen(s));
}
return YamlCreateString(YamlParserTextValue(p, 0, flow));
}
static
inline
void
YamlParserFinish(YAML_INLINE *p)
{
size_t before = p->at;
YamlParserWs(p);
if(p->at == p->n)
{
return;
}
if(p->s[p->at] == '#' && p->at > before)
{
return;
}
/* Plain scalars consume whitespace before stopping at a comment. */
if(p->s[p->at] == '#' && p->at && YamlParserSpace(p->s[p->at - 1]))
{
return;
}
YamlParserError(p->line, "unexpected trailing content");
}
static
inline
void
YamlParserSkip(YAML_PARSER *p)
{
while(p->at < p->count)
{
YAML_LINE *l = &p->lines[p->at];
if(l->indent != l->n && l->s[l->indent] != '#')
{
break;
}
p->at++;
}
if(p->at < p->count)
{
p->col = p->lines[p->at].indent;
}
}
static
inline
void
YamlParserNext(YAML_PARSER *p)
{
p->at++;
YamlParserSkip(p);
}
static
inline
int
YamlParserDash(const YAML_LINE *l,
size_t col)
{
return col < l->n && l->s[col] == '-' && (col + 1 == l->n || YamlParserSpace(l->s[col + 1]));
}
static
inline
int
YamlParserEmpty(const YAML_INLINE *p)
{
return p->at == p->n || p->s[p->at] == '#';
}
static
inline
YAML_NODE *
YamlParserBlockValue(YAML_PARSER *p,
YAML_INLINE *v,
size_t parent,
unsigned depth)
{
YamlParserDepthCheck(depth);
YamlParserWs(v);
if(YamlParserEmpty(v))
{
YamlParserNext(p);
if(p->at == p->count || p->col < parent || (p->col == parent && !YamlParserDash(&p->lines[p->at], p->col)))
{
YamlParserError(v->line, "missing value; implicit null is not supported");
}
return YamlParserBlock(p, p->col, depth);
}
if(v->n - v->at >= 8 && !memcmp(v->s + v->at, "!!binary", 8) &&
(v->at + 8 == v->n || YamlParserSpace(v->s[v->at + 8])))
{
YAML_INLINE tail = *v;
tail.at += 8;
YamlParserWs(&tail);
if(tail.at < tail.n && tail.s[tail.at] == '|')
{
tail.at++;
if(tail.at < tail.n && (tail.s[tail.at] == '-' || tail.s[tail.at] == '+'))
{
tail.at++;
}
YamlParserFinish(&tail);
YAML_BUFFER b = {0};
size_t content_indent = 0;
p->at++;
while(p->at < p->count)
{
YAML_LINE *l = &p->lines[p->at];
if(l->indent == l->n)
{
p->at++;
continue;
}
if(l->indent <= parent)
{
break;
}
if(!content_indent)
{
content_indent = l->indent;
}
if(l->indent != content_indent)
{
YamlParserError(l->number, "inconsistent binary block indentation");
}
YamlAppendBuffer(&b, l->s + l->indent, l->n - l->indent);
p->at++;
}
YamlParserSkip(p);
return YamlReadBinary(b.data, b.len);
}
}
YAML_NODE *n = YamlParserInlineValue(v, depth, 0);
YamlParserFinish(v);
YamlParserNext(p);
return n;
}
static
inline
YAML_NODE *
YamlParserBlock(YAML_PARSER *p,
size_t indent,
unsigned depth)
{
YamlParserDepthCheck(depth);
YAML_LINE *l = &p->lines[p->at];
int seq = YamlParserDash(l, p->col);
YAML_NODE *n = YamlCreateNode(seq ? YAML_SEQUENCE : YAML_MAP);
while(p->at < p->count && p->col == indent)
{
l = &p->lines[p->at];
if(!indent && ((!strcmp(l->s, "---")) || (!strcmp(l->s, "..."))))
{
break;
}
if(YamlParserDash(l, p->col) != seq)
{
break;
}
YAML_INLINE v = {l->s, l->n, p->col, l->number};
if(seq)
{
v.at++;
YamlParserWs(&v);
/* Compact sequence/map entries, as emitted by PyYAML. */
int compact = YamlParserDash(l, v.at);
if(!compact && !YamlParserEmpty(&v) && !strchr("[{!&*", v.s[v.at]))
{
YAML_INLINE probe = v;
(void)YamlParserTextValue(&probe, 1, 0);
YamlParserWs(&probe);
if(probe.at < probe.n && probe.s[probe.at] == ':' &&
(probe.at + 1 == probe.n || YamlParserSpace(probe.s[probe.at + 1])))
{
compact = 1;
}
}
if(compact)
{
p->col = v.at;
YamlAppendItem(n, YamlParserBlock(p, v.at, depth + 1));
}
else
{
if(YamlParserEmpty(&v))
{
YamlParserNext(p);
if(p->at == p->count || p->col <= indent)
{
YamlParserError(v.line, "missing sequence item");
}
YamlAppendItem(n, YamlParserBlock(p, p->col, depth + 1));
}
else
{
YamlAppendItem(n, YamlParserBlockValue(p, &v, indent, depth + 1));
}
}
}
else
{
char *key = YamlParserTextValue(&v, 1, 0);
YamlParserWs(&v);
if(v.at == v.n || v.s[v.at++] != ':' || (v.at < v.n && !YamlParserSpace(v.s[v.at])))
{
YamlParserError(v.line, "expected block mapping 'key: value'");
}
if(!strcmp(key, "<<"))
{
YamlParserError(v.line, "YAML merge keys are not supported");
}
YamlAddField(n, key, YamlParserBlockValue(p, &v, indent, depth + 1));
}
}
if(p->at < p->count && p->col > indent)
{
YamlParserError(p->lines[p->at].number, "unexpected indentation or multiline scalar");
}
return n;
}
static
inline
void
YamlParserValidateUtf8(const unsigned char *s,
size_t len)
{
for(size_t i = 0; i < len;)
{
uint32_t c = s[i++], minimum = 0;
unsigned extra = 0;
if(c >= 0xc2 && c <= 0xdf)
{
c &= 31;
extra = 1;
minimum = 0x80;
}
else if(c >= 0xe0 && c <= 0xef)
{
c &= 15;
extra = 2;
minimum = 0x800;
}
else if(c >= 0xf0 && c <= 0xf4)
{
c &= 7;
extra = 3;
minimum = 0x10000;
}
else if(c >= 0x80)
{
YamlError("invalid UTF-8 in YAML input");
}
if(extra > len - i)
{
YamlError("truncated UTF-8 in YAML input");
}
while(extra--)
{
unsigned b = s[i++];
if((b & 0xc0) != 0x80)
{
YamlError("invalid UTF-8 in YAML input");
}
c = (c << 6) | (b & 63);
}
if(c < minimum || c > 0x10ffff || (c >= 0xd800 && c <= 0xdfff))
{
YamlError("invalid UTF-8 codepoint");
}
if((c < 32 && c != 9 && c != 10 && c != 13) || (c >= 0x7f && c <= 0x9f && c != 0x85) || c == 0xfffe ||
c == 0xffff)
{
YamlError("non-printable character in YAML input");
}
if(c == 0x85 || c == 0x2028 || c == 0x2029 || c == 0xfeff)
{
YamlError("unsupported YAML line break or embedded BOM");
}
}
}
static
inline
YAML_NODE *
YamlReadDocument(const unsigned char *data,
size_t len)
{
if(len > YAML_MAX_TEXT)
{
YamlError("YAML input exceeds 512 MiB");
}
YamlParserValidateUtf8(data, len);
size_t count = 1;
for(size_t i = 0; i < len; i++)
{
if(data[i] == '\n')
{
count++;
}
}
if(count > 2097152)
{
YamlError("too many YAML lines");
}
YAML_PARSER p = {YamlAllocateMemory(count * sizeof(YAML_LINE)), 0, 0, 0};
char *storage = YamlParserSlice((const char *)data, len);
size_t start = 0;
for(size_t i = 0; i <= len; i++)
{
if(i < len && storage[i] != '\n')
{
continue;
}
size_t end = i;
if(end > start && storage[end - 1] == '\r')
{
end--;
}
while(end > start && YamlParserSpace(storage[end - 1]))
{
end--;
}
storage[end] = 0;
YAML_LINE *l = &p.lines[p.count++];
l->s = storage + start;
l->n = end - start;
l->number = p.count;
while(l->indent < l->n && l->s[l->indent] == ' ')
{
l->indent++;
}
if(l->indent < l->n && l->s[l->indent] == '\t')
{
YamlParserError(l->number, "tabs in indentation are not supported");
}
if(memchr(l->s, '\r', l->n))
{
YamlParserError(l->number, "use LF or CRLF line endings");
}
start = i + 1;
}
YamlParserSkip(&p);
if(p.at < p.count && !p.col && !strcmp(p.lines[p.at].s, "---"))
{
YamlParserNext(&p);
}
if(p.at == p.count)
{
YamlError("empty YAML document");
}
if(p.col)
{
YamlParserError(p.lines[p.at].number, "root must start at column one");
}
YAML_NODE *root;
if(p.lines[p.at].s[0] == '{' || p.lines[p.at].s[0] == '[')
{
YAML_INLINE v = {p.lines[p.at].s, p.lines[p.at].n, 0, p.lines[p.at].number};
root = YamlParserInlineValue(&v, 0, 0);
YamlParserFinish(&v);
YamlParserNext(&p);
}
else
{
root = YamlParserBlock(&p, 0, 0);
}
if(p.at < p.count && !p.col && !strcmp(p.lines[p.at].s, "..."))
{
YamlParserNext(&p);
}
if(p.at != p.count)
{
YamlParserError(p.lines[p.at].number, "unexpected content or multiple YAML documents");
}
return root;
}