mirror of
https://github.com/Alcaro/Flips.git
synced 2026-09-07 01:56:07 -05:00
Update Arlib
This commit is contained in:
@@ -7,12 +7,16 @@ else
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
OS = linux
|
||||
endif
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
else ifeq ($(UNAME_S),Darwin)
|
||||
OS = osx
|
||||
else
|
||||
$(error what weird OS is this?)
|
||||
endif
|
||||
endif
|
||||
|
||||
SPACE :=
|
||||
SPACE +=
|
||||
|
||||
ARGUI ?= 0
|
||||
ARTHREAD ?= 0
|
||||
ARSANDBOX ?= 0
|
||||
@@ -21,6 +25,7 @@ ARSOCKET ?= 0
|
||||
ARSOCKET_SSL ?= openssl
|
||||
|
||||
DEBUG ?= 1
|
||||
OPT ?= 0
|
||||
|
||||
CC = gcc
|
||||
CFLAGS =
|
||||
@@ -29,6 +34,7 @@ CXXFLAGS = $(CFLAGS)
|
||||
LD = g++
|
||||
LFLAGS =
|
||||
OBJSUFFIX =
|
||||
CCXXFLAGS = -fvisibility=hidden -fno-exceptions -Wall -Wno-comment
|
||||
|
||||
EXESUFFIX =
|
||||
EXTRAOBJ ?=
|
||||
@@ -41,23 +47,9 @@ ifeq ($(OS),linux)
|
||||
CONF_LFLAGS += -pthread
|
||||
endif
|
||||
OBJSUFFIX = -linux
|
||||
TESTRUNNER = valgrind
|
||||
endif
|
||||
|
||||
## function rwildcard(directory, pattern)
|
||||
## mostly stolen from bsnes, but slightly improved (can use . as a directory)
|
||||
#rwildcard = \
|
||||
# $(strip \
|
||||
# $(warning 1 1 $1 : 2 $2 : c $(if $(strip $1),$1,.)) \
|
||||
# $(filter $(if $2,$2,%), \
|
||||
# $(foreach f, \
|
||||
# $(wildcard $(if $(strip $1),$1,.)/*), \
|
||||
# $(eval t = $(call rwildcard,$f)) \
|
||||
# $(warning 2 t $t : f $f : 1 $1 : 2 $2) \
|
||||
# $(if $t,$t,$f) \
|
||||
# ) \
|
||||
# ) \
|
||||
# )
|
||||
|
||||
ifeq ($(OS),windows)
|
||||
EXESUFFIX = .exe
|
||||
# EXTRAOBJ = obj/resource$(OBJSUFFIX).o
|
||||
@@ -68,6 +60,10 @@ ifeq ($(OS),windows)
|
||||
OBJSUFFIX = -windows
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring test,$(MAKECMDGOALS)))
|
||||
SELFTEST = 1
|
||||
endif
|
||||
|
||||
OPTFLAGS := -Os -fomit-frame-pointer -fmerge-all-constants -fvisibility=hidden
|
||||
OPTFLAGS += -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
|
||||
OPTFLAGS += -ffunction-sections -fdata-sections
|
||||
@@ -77,6 +73,7 @@ ifeq ($(OPT),1)
|
||||
CFLAGS += $(OPTFLAGS)
|
||||
LFLAGS += -Wl,--gc-sections -s
|
||||
DEBUG = 0
|
||||
OBJSUFFIX += -opt
|
||||
endif
|
||||
ifeq ($(DEBUG),1)
|
||||
CFLAGS += -g -DDEBUG
|
||||
@@ -92,6 +89,11 @@ endif
|
||||
|
||||
OUTNAME = $(PROGRAM)$(EXESUFFIX)
|
||||
|
||||
ifneq ($(SELFTEST),)
|
||||
CONF_CFLAGS += -DARLIB_TEST -Dmain=not_quite_main
|
||||
OBJSUFFIX += -test
|
||||
endif
|
||||
|
||||
#stolen from http://stackoverflow.com/questions/22586084/makefile-with-multiple-rules-sharing-same-recipe-with-patternrules
|
||||
define ADDDIR_CORE
|
||||
$(eval OBJPREFIX := obj/_arlib_$(subst /,_,$(1))_)
|
||||
@@ -103,6 +105,7 @@ define ADDDIR
|
||||
$(eval $(call ADDDIR_CORE,$(1)))
|
||||
endef
|
||||
|
||||
OBJSUFFIX := $(subst $(SPACE),,$(OBJSUFFIX))
|
||||
OBJS := $(patsubst %.cpp,obj/%$(OBJSUFFIX).o,$(wildcard *.cpp)) $(EXTRAOBJ)
|
||||
# obj/miniz$(OBJSUFFIX).o
|
||||
|
||||
@@ -162,8 +165,6 @@ ifeq ($(ARSOCKET),1)
|
||||
else ifeq ($(OS),windows)
|
||||
CONF_CFLAGS += -DARLIB_SSL_SCHANNEL
|
||||
CONF_LFLAGS += -lcrypt32 -lsecur32
|
||||
#not sure if these are needed, looks like mingw bug workarounds that were probably relevant four years ago
|
||||
#CONF_LFLAGS += lib/crypt32.exp -l:lib/crypt32.lib -Wl,--enable-stdcall-fixup
|
||||
else ifeq ($(ARSOCKET_SSL),wolfssl)
|
||||
WOLFSSL_DIR = arlib/socket/wolfssl-3.9.0
|
||||
CONF_CFLAGS += -DARLIB_SSL_WOLFSSL -I$(WOLFSSL_DIR)
|
||||
@@ -180,7 +181,6 @@ ifeq ($(ARSOCKET),1)
|
||||
endif
|
||||
endif
|
||||
|
||||
CCXXFLAGS = -fvisibility=hidden -fno-exceptions -Wall -Wno-comment
|
||||
TRUE_CFLAGS = -std=c99 $(CCXXFLAGS) $(CFLAGS) $(CONF_CFLAGS)
|
||||
TRUE_CXXFLAGS =-std=c++11 -fno-rtti $(CCXXFLAGS) $(CXXFLAGS) $(CONF_CXXFLAGS)
|
||||
TRUE_LFLAGS = $(LFLAGS) -fvisibility=hidden $(CONF_LFLAGS)
|
||||
@@ -224,3 +224,13 @@ $(OUTNAME): $(OBJS)
|
||||
$(LD) $+ $(TRUE_LFLAGS) -o $@ -lm
|
||||
|
||||
$(PROGRAM)_dummy: $(OUTNAME)
|
||||
|
||||
|
||||
|
||||
ifneq ($(SELFTEST),)
|
||||
obj/arlibtest$(EXESUFFIX): $(OBJS)
|
||||
$(LD) $+ $(TRUE_LFLAGS) -o $@ -lm
|
||||
|
||||
test: obj/arlibtest$(EXESUFFIX)
|
||||
$(TESTRUNNER) obj/arlibtest$(EXESUFFIX)
|
||||
endif
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#pragma once
|
||||
#include "bml.h"
|
||||
#include "containers.h"
|
||||
#include "endian.h"
|
||||
#include "file.h"
|
||||
#include "function.h"
|
||||
#include "intwrap.h"
|
||||
#include "os.h"
|
||||
#include "serialize.h"
|
||||
#include "string.h"
|
||||
#include "stringconv.h"
|
||||
|
||||
//not in #ifdef, there's a check inside that header
|
||||
//not in #ifdef, it contains some dummy implementations if threads are disabled
|
||||
#include "thread/thread.h"
|
||||
|
||||
#if !defined(ARGUI_NONE) && !defined(ARGUI_WIN32) && !defined(ARGUI_GTK3)
|
||||
|
||||
103
arlib/array.h
103
arlib/array.h
@@ -23,9 +23,9 @@ public:
|
||||
const T& operator[](size_t n) const { return items[n]; }
|
||||
|
||||
const T* ptr() const { return items; }
|
||||
size_t len() const { return count; }
|
||||
size_t size() const { return count; }
|
||||
|
||||
operator bool() { return items; }
|
||||
operator bool() { return count; }
|
||||
|
||||
arrayview()
|
||||
{
|
||||
@@ -204,9 +204,22 @@ public:
|
||||
|
||||
template<> class array<bool> {
|
||||
protected:
|
||||
class null_only;
|
||||
static const size_t n_inline = sizeof(uint8_t*)/sizeof(uint8_t)*8;
|
||||
|
||||
uint8_t* bits;
|
||||
union {
|
||||
uint8_t bits_inline[n_inline/8];
|
||||
uint8_t* bits_outline;
|
||||
};
|
||||
uint8_t* bits()
|
||||
{
|
||||
if (nbits <= n_inline) return bits_inline;
|
||||
else return bits_outline;
|
||||
}
|
||||
const uint8_t* bits() const
|
||||
{
|
||||
if (nbits <= n_inline) return bits_inline;
|
||||
else return bits_outline;
|
||||
}
|
||||
size_t nbits;
|
||||
|
||||
class entry {
|
||||
@@ -224,23 +237,16 @@ protected:
|
||||
bool get(size_t n) const
|
||||
{
|
||||
if (n >= nbits) return false;
|
||||
return bits[n/8]>>(n&7) & 1;
|
||||
return bits()[n/8]>>(n&7) & 1;
|
||||
}
|
||||
|
||||
void set(size_t n, bool val)
|
||||
{
|
||||
if (n >= nbits)
|
||||
{
|
||||
size_t prevbytes = bitround((nbits+7)/8);
|
||||
size_t newbytes = bitround((n+8)/8);
|
||||
if (newbytes > prevbytes)
|
||||
{
|
||||
bits = realloc(bits, newbytes);
|
||||
memset(bits+prevbytes, 0, newbytes-prevbytes);
|
||||
}
|
||||
nbits = n+1;
|
||||
resize(n+1);
|
||||
}
|
||||
uint8_t& byte = bits[n/8];
|
||||
uint8_t& byte = bits()[n/8];
|
||||
byte &=~ (1<<(n&7));
|
||||
byte |= (val<<(n&7));
|
||||
}
|
||||
@@ -249,22 +255,81 @@ public:
|
||||
bool operator[](size_t n) const { return get(n); }
|
||||
entry operator[](size_t n) { return entry(*this, n); }
|
||||
|
||||
size_t len() const { return nbits; }
|
||||
size_t size() const { return nbits; }
|
||||
void reset()
|
||||
{
|
||||
free(this->bits);
|
||||
this->bits = NULL;
|
||||
if (nbits >= n_inline) free(this->bits_outline);
|
||||
this->nbits = 0;
|
||||
}
|
||||
|
||||
void resize(size_t len)
|
||||
{
|
||||
switch ((this->nbits > n_inline)<<1 | (len > n_inline))
|
||||
{
|
||||
case 0: // small->small
|
||||
break;
|
||||
case 1: // small->big
|
||||
{
|
||||
size_t newbytes = bitround((len+7)/8);
|
||||
uint8_t* newbits = malloc(newbytes);
|
||||
memcpy(newbits, this->bits_inline, sizeof(this->bits_inline));
|
||||
memset(newbits+sizeof(this->bits_inline), 0, newbytes-sizeof(this->bits_inline));
|
||||
bits_outline = newbits;
|
||||
}
|
||||
break;
|
||||
case 2: // big->small
|
||||
{
|
||||
uint8_t* freethis = this->bits_outline;
|
||||
memcpy(this->bits_inline, this->bits_outline, sizeof(this->bits_inline));
|
||||
free(freethis);
|
||||
}
|
||||
case 3: // big->big
|
||||
{
|
||||
size_t prevbytes = bitround((this->nbits+7)/8);
|
||||
size_t newbytes = bitround((len+7)/8);
|
||||
if (newbytes > prevbytes)
|
||||
{
|
||||
bits_outline = realloc(this->bits_outline, newbytes);
|
||||
if (newbytes > prevbytes)
|
||||
{
|
||||
memset(this->bits_outline+prevbytes, 0, newbytes-prevbytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this->nbits = len;
|
||||
}
|
||||
|
||||
void append(bool item) { set(this->nbits, item); }
|
||||
|
||||
array<bool> slice(size_t first, size_t count)
|
||||
{
|
||||
if ((first&7) == 0)
|
||||
{
|
||||
array<bool> ret;
|
||||
ret.resize(count);
|
||||
memcpy(ret.bits(), this->bits() + first/8, (count+7)/8);
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
array<bool> ret;
|
||||
ret.resize(count);
|
||||
for (size_t i=0;i<count;i++) ret.set(i, this->get(first+i));
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
array()
|
||||
{
|
||||
this->bits = NULL;
|
||||
this->nbits = 0;
|
||||
memset(this->bits_inline, 0, sizeof(this->bits_inline));
|
||||
}
|
||||
|
||||
~array()
|
||||
{
|
||||
free(this->bits);
|
||||
if (nbits >= n_inline) free(this->bits_outline);
|
||||
}
|
||||
};
|
||||
|
||||
71
arlib/bml.h
Normal file
71
arlib/bml.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include "global.h"
|
||||
#include "array.h"
|
||||
#include "string.h"
|
||||
#include "serialize.h"
|
||||
|
||||
|
||||
//This is a streaming parser. For each node, { enter } then { exit } is returned; more enter/exit pairs may be present between them.
|
||||
//For example, the document
|
||||
/*
|
||||
parent child=1
|
||||
parent2
|
||||
*/
|
||||
//would yield { enter, parent, "" }, { enter, child, 1 }, { exit } { exit } { enter, parent2, "" } { exit }.
|
||||
//The parser keeps trying after an { error }, giving you a partial view of the damaged document; however,
|
||||
// there are no guarantees on how much you can see, and it is likely for one error to cause many more, or misplaced nodes.
|
||||
//enter/exit is always paired, even in the presense of errors.
|
||||
//After the document ends, { finish } will be returned forever until the object is deleted.
|
||||
class bmlparser : nocopy {
|
||||
public:
|
||||
enum { enter, exit, error, finish };
|
||||
struct event {
|
||||
int action;
|
||||
cstring name;
|
||||
cstring value; // or error message
|
||||
};
|
||||
|
||||
//Since this takes a cstring, the string must be kept alive until the object is disposed.
|
||||
bmlparser(cstring bml) : m_orig_data(bml), m_data(bml), m_exit(false) {}
|
||||
event next();
|
||||
|
||||
private:
|
||||
cstring m_orig_data; // keep a reference if we're passed in the only copy of a string object
|
||||
cstring m_data;
|
||||
cstring m_thisline;
|
||||
array<bool> m_indent_step;
|
||||
cstring m_indent;
|
||||
cstring m_inlines;
|
||||
bool m_exit;
|
||||
|
||||
inline void getlineraw();
|
||||
inline bool getline();
|
||||
};
|
||||
|
||||
//This is also streaming. It may disobey the mode if the value is not supported; for example, val!="" on bml_anon won't help you.
|
||||
//It also disobeys mode <= bml_inl_col on enter(), you need node().
|
||||
//Calling exit() without a matching enter(), or finish() without closing every enter(), is undefined behavior.
|
||||
class bmlwriterx {
|
||||
string m_data;
|
||||
int m_indent;
|
||||
|
||||
public:
|
||||
enum mode {
|
||||
anon, // parent node
|
||||
inl_eq, // parent node=value
|
||||
inl_col, // parent node: value
|
||||
eq, // node=value
|
||||
col, // node: value
|
||||
multiline // node\n :value
|
||||
};
|
||||
|
||||
bmlwriterx() { m_indent = 0; }
|
||||
|
||||
void enter(cstring name, cstring val, mode m);
|
||||
void exit() { m_indent--; }
|
||||
void linebreak();
|
||||
void comment(cstring text);
|
||||
void node(cstring name, cstring val, mode m);
|
||||
|
||||
string finish() { return m_data; }
|
||||
};
|
||||
552
arlib/bmlparse.cpp
Normal file
552
arlib/bmlparse.cpp
Normal file
@@ -0,0 +1,552 @@
|
||||
#include "bml.h"
|
||||
#include "test.h"
|
||||
#include <ctype.h>
|
||||
//#include <valgrind/memcheck.h>
|
||||
|
||||
/*
|
||||
|
||||
[bml]
|
||||
a
|
||||
b
|
||||
c
|
||||
d e f
|
||||
g
|
||||
:h
|
||||
:i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
|
||||
read "a"
|
||||
{a}
|
||||
set m_indent_step[0]
|
||||
return enter a
|
||||
|
||||
read "b"
|
||||
m_indent_step.size(){1} > m_indent.length(){0}, so:
|
||||
{b}
|
||||
clear last true element of m_indent_step
|
||||
clear trailing false elements of m_indent_step
|
||||
restore "b" to read buffer
|
||||
return exit
|
||||
|
||||
read "b", goto {a}
|
||||
|
||||
read " c"
|
||||
{c}
|
||||
set m_indent to " "
|
||||
m_indent_step.size(){1} <= m_indent.length(){1}
|
||||
set m_indent_step[1]
|
||||
return enter c
|
||||
|
||||
read "d e"
|
||||
set m_indent to ""
|
||||
m_indent_step.size(){2} > m_indent.length(){0}, so goto {b}
|
||||
|
||||
read "d e"
|
||||
m_indent_step.size(){1} > m_indent.length(){0}, so goto {b}
|
||||
|
||||
read "d e"
|
||||
m_indent_step.size(){0} <= m_indent.length(){0}
|
||||
set m_inlines = " e f" (or "e f", not sure and doesn't matter)
|
||||
goto {a}
|
||||
|
||||
m_inlines is not empty, so:
|
||||
{e}
|
||||
read "e f"
|
||||
set m_exit
|
||||
set m_inlines to "f"
|
||||
return enter e
|
||||
|
||||
m_exit is set, so:
|
||||
{ex}
|
||||
clear m_exit
|
||||
return exit
|
||||
|
||||
m_inlines is not empty, so goto {e} [m_inlines = ""]
|
||||
m_exit is set, so goto {ex}
|
||||
|
||||
|
||||
[bml]
|
||||
g
|
||||
:h
|
||||
:i
|
||||
j
|
||||
k
|
||||
l
|
||||
m
|
||||
|
||||
read "g"
|
||||
read " :h"
|
||||
if it doesn't start with colon, restore to read buffer
|
||||
but it does, so:
|
||||
set m_indent = " "
|
||||
read " :i"
|
||||
it too starts with colon, so ensure that indentation is identical
|
||||
read "j"
|
||||
it does not start with colon, so restore to read buffer
|
||||
set m_indent_step[0]
|
||||
return enter g="h i"
|
||||
|
||||
read "j", goto {a}
|
||||
read " k", goto {c}
|
||||
|
||||
read " l"
|
||||
set m_indent to " "
|
||||
m_indent_step.size(){2} <= m_indent.length(){2}
|
||||
set m_indent_step[2]
|
||||
return enter l
|
||||
|
||||
read " m"
|
||||
m_indent_step.size(){3} > m_indent.length(){2}, so goto {b}
|
||||
|
||||
read " m"
|
||||
m_indent_step.size(){2} <= m_indent.length(){2}
|
||||
set m_indent_step[2]
|
||||
return enter m
|
||||
|
||||
read ""
|
||||
set m_indent to ""
|
||||
m_indent_step.size(){2} > m_indent.length(){0}, so goto {b}
|
||||
|
||||
read ""
|
||||
m_indent_step.size(){1} > m_indent.length(){0}, so goto {b}
|
||||
|
||||
read ""
|
||||
nothing else to do, so return finish
|
||||
|
||||
|
||||
|
||||
process in this order:
|
||||
|
||||
process m_exit
|
||||
process m_inlines
|
||||
|
||||
peek line (put in m_nextline, or use m_nextline if already present)
|
||||
check for consistent whitespace (set m_indent even on failure)
|
||||
set m_indent
|
||||
check if m_indent_step.size requires exit
|
||||
|
||||
discard peeked line
|
||||
read line
|
||||
extract main node on line; put remainder in m_inlines
|
||||
if no value, peek next line
|
||||
as long as it starts with colon, discard line and peek next
|
||||
set value to that
|
||||
set m_indent_step
|
||||
return enter
|
||||
|
||||
'read line' must discard blank/comment lines
|
||||
if there is no line, return finish
|
||||
|
||||
*/
|
||||
|
||||
static cstring cut(cstring& input, int skipstart, int cut, int skipafter)
|
||||
{
|
||||
cstring ret = input.csubstr(skipstart, cut);
|
||||
input = input.csubstr(cut+skipafter, ~0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
//takes a single line, returns the first node in it
|
||||
//hasvalue is to differentiate 'foo' from 'foo='; only the former allows a multi-line value
|
||||
static bool bml_parse_inline_node(cstring& data, cstring& node, bool& hasvalue, cstring& value)
|
||||
{
|
||||
int nodestart = 0;
|
||||
while (data[nodestart]==' ' || data[nodestart]=='\t') nodestart++;
|
||||
|
||||
int nodelen = nodestart;
|
||||
while (isalnum(data[nodelen]) || data[nodelen]=='-' || data[nodelen]=='.') nodelen++;
|
||||
if (nodestart == nodelen)
|
||||
{
|
||||
value = "Invalid node name";
|
||||
return false;
|
||||
}
|
||||
node = cut(data, nodestart, nodelen, 0);
|
||||
switch (data[0])
|
||||
{
|
||||
case '\0':
|
||||
case '\t':
|
||||
case ' ':
|
||||
{
|
||||
hasvalue = false;
|
||||
return true;
|
||||
}
|
||||
case ':':
|
||||
{
|
||||
hasvalue = true;
|
||||
int valstart = 1;
|
||||
while (data[valstart]==' ' || data[valstart]=='\t') valstart++;
|
||||
value = data.csubstr(valstart, ~0);
|
||||
data = "";
|
||||
return true;
|
||||
}
|
||||
case '=':
|
||||
{
|
||||
if (data[1]=='"')
|
||||
{
|
||||
hasvalue = true;
|
||||
int valend = 2;
|
||||
while (data[valend]!='"' && data[valend]!='\0') valend++;
|
||||
if (data[valend]!='"' || !strchr(" \t", data[valend+1]))
|
||||
{
|
||||
while (data[valend]!='\0') valend++;
|
||||
data = data.csubstr(valend, ~0);
|
||||
value = "Broken quoted value";
|
||||
return false;
|
||||
}
|
||||
value = cut(data, 2, valend, 1);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
hasvalue = true;
|
||||
int valend = 0;
|
||||
while (data[valend]!=' ' && data[valend]!='\0') valend++;
|
||||
value = cut(data, 1, valend, 0);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
default:
|
||||
value = "Invalid node suffix";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isendl(char ch)
|
||||
{
|
||||
if (ch>=32) return false;
|
||||
return (ch=='\r' || ch=='\n' || ch=='\0');
|
||||
}
|
||||
|
||||
static cstring cutline(cstring& input)
|
||||
{
|
||||
//pointers are generally bad ideas, but this is such a hotspot it's worth it
|
||||
const char * inputraw = input.nt();
|
||||
size_t nlpos = 0;
|
||||
//that 32 is also a perf hack
|
||||
if (input.ntterm())
|
||||
{
|
||||
while (!isendl(inputraw[nlpos])) nlpos++;
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t inputlen = input.length();
|
||||
while (nlpos < inputlen && !isendl(inputraw[nlpos])) nlpos++;
|
||||
}
|
||||
|
||||
return cut(input, 0, nlpos, (input[nlpos]=='\r') ? 2 : (input[nlpos]=='\n') ? 1 : 0);
|
||||
}
|
||||
|
||||
inline void bmlparser::getlineraw()
|
||||
{
|
||||
nextline:
|
||||
if (!m_data)
|
||||
{
|
||||
m_thisline = "";
|
||||
return;
|
||||
}
|
||||
m_thisline = cutline(m_data);
|
||||
int indentlen = 0;
|
||||
while (m_thisline[indentlen] == ' ' || m_thisline[indentlen] == '\t') indentlen++;
|
||||
if (m_thisline[indentlen] == '#' || m_thisline[indentlen]=='\0') goto nextline;
|
||||
}
|
||||
|
||||
inline bool bmlparser::getline()
|
||||
{
|
||||
getlineraw();
|
||||
|
||||
int indentlen = 0;
|
||||
while (m_thisline[indentlen] == ' ' || m_thisline[indentlen] == '\t') indentlen++;
|
||||
|
||||
int sharedindent = min(indentlen, m_indent.length());
|
||||
bool badwhite = (memcmp(m_thisline.nt(), m_indent.nt(), sharedindent)!=0);
|
||||
|
||||
m_indent = cut(m_thisline, 0, indentlen, 0);
|
||||
|
||||
return !badwhite;
|
||||
}
|
||||
|
||||
bmlparser::event bmlparser::next()
|
||||
{
|
||||
if (m_exit)
|
||||
{
|
||||
m_exit = false;
|
||||
return (event){ exit };
|
||||
}
|
||||
|
||||
if (m_inlines)
|
||||
{
|
||||
event ev = { enter };
|
||||
bool dummy;
|
||||
if (!bml_parse_inline_node(m_inlines, ev.name, dummy, ev.value))
|
||||
{
|
||||
ev.action = error;
|
||||
ev.name = "";
|
||||
return ev;
|
||||
}
|
||||
|
||||
m_exit = true;
|
||||
return ev;
|
||||
}
|
||||
|
||||
if (!m_thisline && m_data)
|
||||
{
|
||||
if (!getline()) return (event){ error, "", "Mixed tabs and spaces" };
|
||||
}
|
||||
|
||||
if (m_indent_step.size() > m_indent.length())
|
||||
{
|
||||
handle_indent:
|
||||
if (!m_indent_step[m_indent.length()]) return (event){ error, "", "Invalid indentation depth" };
|
||||
|
||||
int lasttrue = m_indent_step.size()-2;
|
||||
while (lasttrue>=0 && m_indent_step[lasttrue]==false) lasttrue--;
|
||||
|
||||
m_indent_step.resize(lasttrue+1);
|
||||
return (event){ exit };
|
||||
}
|
||||
|
||||
if (!m_thisline)
|
||||
{
|
||||
if (m_indent_step.size()) goto handle_indent;
|
||||
return (event){ finish };
|
||||
}
|
||||
|
||||
m_inlines = m_thisline;
|
||||
m_thisline = "";
|
||||
|
||||
cstring node;
|
||||
bool hasvalue;
|
||||
cstring value;
|
||||
if (!bml_parse_inline_node(m_inlines, node, hasvalue, value))
|
||||
{
|
||||
return (event){ error, "", value };
|
||||
}
|
||||
|
||||
int indentlen = m_indent.length(); // changed by getline
|
||||
//multilines
|
||||
if (!hasvalue)
|
||||
{
|
||||
if (!getline()) return (event){ error, "", "Mixed tabs and spaces" };
|
||||
if (m_thisline[0] == ':')
|
||||
{
|
||||
size_t inner_indent = m_indent.length();
|
||||
value = m_thisline.csubstr(1, ~0);
|
||||
if (!getline()) return (event){ error, "", "Mixed tabs and spaces" };
|
||||
while (m_thisline[0] == ':')
|
||||
{
|
||||
if (inner_indent != m_indent.length()) return (event){ error, "", "Multi-line values must have constant indentation" };
|
||||
value += "\n" + m_thisline.csubstr(1, ~0);
|
||||
if (!getline()) return (event){ error, "", "Mixed tabs and spaces" };
|
||||
}
|
||||
|
||||
if (m_indent.length() != inner_indent)
|
||||
{
|
||||
if (m_indent.length() > inner_indent) return (event){ error, "", "Can't change indentation after a multi-line value" };
|
||||
if (!m_indent_step[m_indent.length()]) return (event){ error, "", "Invalid indentation depth" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_indent_step[indentlen] = true;
|
||||
return (event){ enter, node, value };
|
||||
}
|
||||
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
#define e_enter bmlparser::enter
|
||||
#define e_exit bmlparser::exit
|
||||
#define e_error bmlparser::error
|
||||
#define e_finish bmlparser::finish
|
||||
|
||||
const char * test1 =
|
||||
"node\n"
|
||||
"node=foo\n"
|
||||
"node=\"foo bar\"\n"
|
||||
"node: foo bar\n"
|
||||
"node\n"
|
||||
" child\n"
|
||||
"node child=foo\n"
|
||||
"node=\n"
|
||||
"node=\"\"\n"
|
||||
"node:\n"
|
||||
"node\tchild\n"
|
||||
"#bar\n"
|
||||
"node";
|
||||
bmlparser::event test1e[]={
|
||||
{ e_enter, "node" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node", "foo" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node", "foo bar" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node", "foo bar" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_enter, "child" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_enter, "child", "foo" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_enter, "child" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "node" },
|
||||
{ e_exit },
|
||||
{ e_finish }
|
||||
};
|
||||
|
||||
const char * test2 =
|
||||
"parent\n"
|
||||
" node=123 child1=456 child2: 789 123\n"
|
||||
" child3\n";
|
||||
bmlparser::event test2e[]={
|
||||
{ e_enter, "parent" },
|
||||
{ e_enter, "node", "123" },
|
||||
{ e_enter, "child1", "456" },
|
||||
{ e_exit },
|
||||
{ e_enter, "child2", "789 123" },
|
||||
{ e_exit },
|
||||
{ e_enter, "child3" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_finish }
|
||||
};
|
||||
|
||||
const char * test3 =
|
||||
"a b=1 c=2 d: 3\n"
|
||||
" e=4 f=5\n"
|
||||
" g h=6\n"
|
||||
" :7\n"
|
||||
" :8\n"
|
||||
"i";
|
||||
bmlparser::event test3e[]={
|
||||
{ e_enter, "a" },
|
||||
{ e_enter, "b", "1" },
|
||||
{ e_exit },
|
||||
{ e_enter, "c", "2" },
|
||||
{ e_exit },
|
||||
{ e_enter, "d", "3" },
|
||||
{ e_exit },
|
||||
{ e_enter, "e", "4" },
|
||||
{ e_enter, "f", "5" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "g", "7\n8" },
|
||||
{ e_enter, "h", "6" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "i" },
|
||||
{ e_exit },
|
||||
{ e_finish }
|
||||
};
|
||||
|
||||
const char * test4 =
|
||||
"Parent-1.0=A-value child child=\"B value\" child:C:\"value\"\n"
|
||||
" child:D:\"value\"\n"
|
||||
" grandchild\n"
|
||||
" child grandchild=A\n"
|
||||
" :multi-line\n"
|
||||
" :text-field\n"
|
||||
" grandchild=B foo=bar\n"
|
||||
" foo=bar\n"
|
||||
"\n"
|
||||
"Parent-1.0";
|
||||
bmlparser::event test4e[]={
|
||||
{ e_enter, "Parent-1.0", "A-value" },
|
||||
{ e_enter, "child" },
|
||||
{ e_exit },
|
||||
{ e_enter, "child", "B value" },
|
||||
{ e_exit },
|
||||
{ e_enter, "child", "C:\"value\"" },
|
||||
{ e_exit },
|
||||
{ e_enter, "child", "D:\"value\"" },
|
||||
{ e_enter, "grandchild" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "child", "multi-line\ntext-field" },
|
||||
{ e_enter, "grandchild", "A" },
|
||||
{ e_exit },
|
||||
{ e_enter, "grandchild", "B" },
|
||||
{ e_enter, "foo", "bar" },
|
||||
{ e_exit },
|
||||
{ e_enter, "foo", "bar" },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_exit },
|
||||
{ e_enter, "Parent-1.0" },
|
||||
{ e_exit },
|
||||
{ e_finish }
|
||||
};
|
||||
|
||||
static bool testbml(const char * bml, bmlparser::event* expected)
|
||||
{
|
||||
bmlparser parser(bml);
|
||||
while (true)
|
||||
{
|
||||
bmlparser::event actual = parser.next();
|
||||
|
||||
//printf("e=%i [%s] [%s]\n", expected->action, expected->name.data(), expected->value.data());
|
||||
//printf("a=%i [%s] [%s]\n\n", actual.action, actual.name.data(), actual.value.data());
|
||||
assert_eq(actual.action, expected->action);
|
||||
assert_eq(actual.name, expected->name);
|
||||
assert_eq(actual.value, expected->value);
|
||||
|
||||
if (expected->action == e_finish || actual.action == e_finish) return true;
|
||||
|
||||
expected++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool testbml_error(const char * bml)
|
||||
{
|
||||
bmlparser parser(bml);
|
||||
for (int i=0;i<100;i++)
|
||||
{
|
||||
bmlparser::event ev = parser.next();
|
||||
//printf("a=%i [%s] [%s]\n\n", ev.action, ev.name.data(), ev.value.data());
|
||||
if (ev.action == e_error) return true;
|
||||
}
|
||||
assert(!"expected error");
|
||||
}
|
||||
|
||||
test()
|
||||
{
|
||||
assert(testbml(test1, test1e));
|
||||
assert(testbml(test2, test2e));
|
||||
assert(testbml(test3, test3e));
|
||||
assert(testbml(test4, test4e));
|
||||
|
||||
assert(testbml_error("*")); // invalid node name
|
||||
assert(testbml_error("a=\"")); // unclosed quote
|
||||
assert(testbml_error("a=\"b\"c")); // no space after closing quote
|
||||
assert(testbml_error("a=\"b\"c\"")); // no space after closing quote
|
||||
assert(testbml_error("a\n b\n c")); // derpy indentation
|
||||
assert(testbml_error("a\n b\n\tc")); // mixed tabs and spaces
|
||||
assert(testbml_error("a=b\n :c")); // two values
|
||||
|
||||
//derpy indentation with multilines
|
||||
assert(testbml_error("a\n :b\n :c"));
|
||||
assert(testbml_error("a\n :b\n :c"));
|
||||
assert(testbml_error("a\n :b\n c"));
|
||||
assert(testbml_error("a\n :b\n c"));
|
||||
assert(testbml_error("a\n :b\n\t:c"));
|
||||
assert(testbml_error("a\n :b\n\tc"));
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
38
arlib/bmlwrite.cpp
Normal file
38
arlib/bmlwrite.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "bml.h"
|
||||
#include "test.h"
|
||||
|
||||
//This is also streaming. It may disobey the mode if the value is not supported; for example, val!="" on mode=anon won't work.
|
||||
//It also disobeys mode <= inl_col on enter(), you need node() for that.
|
||||
//Calling exit() without a matching enter(), or finish() without closing every enter(), is undefined behavior.
|
||||
class bmlwriter {
|
||||
string m_data;
|
||||
int m_indent;
|
||||
|
||||
public:
|
||||
enum mode {
|
||||
anon, // parent node
|
||||
inl_eq, // parent node=value
|
||||
inl_col, // parent node: value
|
||||
eq, // node=value
|
||||
col, // node: value
|
||||
multiline // node\n :value
|
||||
};
|
||||
|
||||
bmlwriter() { m_indent = 0; }
|
||||
|
||||
void enter(cstring name, cstring val, mode m); // Always uses mode=eq or higher.
|
||||
void exit() { m_indent--; }
|
||||
void linebreak();
|
||||
void comment(cstring text);
|
||||
void node(cstring name, cstring val, mode m);
|
||||
|
||||
//Tells what mode will actually be used if node() is called with these parameters.
|
||||
mode typeof(cstring val, mode m) const;
|
||||
|
||||
string finish() { return m_data; }
|
||||
};
|
||||
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
|
||||
#endif
|
||||
144
arlib/global.h
144
arlib/global.h
@@ -29,6 +29,7 @@
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "function.h"
|
||||
#include <utility>
|
||||
|
||||
typedef void(*funcptr)();
|
||||
|
||||
@@ -41,6 +42,9 @@ typedef void(*funcptr)();
|
||||
#define JOIN_(x, y) x ## y
|
||||
#define JOIN(x, y) JOIN_(x, y)
|
||||
|
||||
#define STR_(x) #x
|
||||
#define STR(x) STR_(x)
|
||||
|
||||
//some magic stolen from http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx
|
||||
//C++ can be so messy sometimes...
|
||||
template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
||||
@@ -72,7 +76,7 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
||||
//- works on all compilers
|
||||
//optional:
|
||||
//- (PASS) works in a template, even if the template isn't instantiated, if the condition isn't dependent on the types
|
||||
//- (FAIL) works if compiled as C (can fix with an ifdef, but I'm lazy)
|
||||
//- (FAIL) works if compiled as C (tried to design an alternate implementation and ifdef it, but nothing works inside structs)
|
||||
//- (FAIL) can name assertions, if desired
|
||||
#ifdef __GNUC__
|
||||
#define MAYBE_UNUSED __attribute__((__unused__)) // shut up, stupid warnings
|
||||
@@ -81,6 +85,8 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
||||
#define MAYBE_UNUSED
|
||||
#define TYPENAME_IF_GCC
|
||||
#endif
|
||||
|
||||
#if __cplusplus < 201103
|
||||
template<bool x> struct static_assert_t;
|
||||
template<> struct static_assert_t<true> { struct STATIC_ASSERTION_FAILED {}; };
|
||||
template<> struct static_assert_t<false> {};
|
||||
@@ -92,6 +98,9 @@ template<> struct static_assert_t<false> {};
|
||||
JOIN(static_assertion_, __COUNTER__) = \
|
||||
sizeof(TYPENAME_IF_GCC static_assert_t<(bool)(expr)>::STATIC_ASSERTION_FAILED) \
|
||||
} MAYBE_UNUSED
|
||||
#else
|
||||
#define static_assert(expr) static_assert(expr, #expr)
|
||||
#endif
|
||||
|
||||
//almost C version (fails inside structs):
|
||||
//#define static_assert(expr) \
|
||||
@@ -132,11 +141,28 @@ anyptr try_realloc(anyptr ptr, size_t size);
|
||||
anyptr calloc_check(size_t size, size_t count);
|
||||
anyptr try_calloc(size_t size, size_t count);
|
||||
#define calloc calloc_check
|
||||
void malloc_assert(bool cond); // if the condition is false, the malloc failure handler is called
|
||||
|
||||
|
||||
//if I cast it to void, that means I do not care, so shut the hell up about warn_unused_result.
|
||||
template<typename T> static inline void ignore(T t) {}
|
||||
|
||||
template<typename T> static T min(const T& a) { return a; }
|
||||
template<typename T, typename... Args> static T min(const T& a, Args... args)
|
||||
{
|
||||
const T& b = min(args...);
|
||||
if (a < b) return a;
|
||||
else return b;
|
||||
}
|
||||
|
||||
template<typename T> static T max(const T& a) { return a; }
|
||||
template<typename T, typename... Args> static T max(const T& a, Args... args)
|
||||
{
|
||||
const T& b = min(args...);
|
||||
if (a < b) return b;
|
||||
else return a;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//too reliant on non-ancient compilers
|
||||
@@ -153,16 +179,16 @@ template<typename T> static inline void ignore(T t) {}
|
||||
//};
|
||||
//template<typename T> T* generic_create() { return generic_create_core<T>::create((T*)NULL, NULL); }
|
||||
//template<typename T> void generic_delete(T* obj) { generic_create_core<T>::destroy(obj, (T*)NULL, NULL); }
|
||||
|
||||
template<typename T> T* generic_create() { return T::create(); }
|
||||
template<typename T> T* generic_new() { return new T; }
|
||||
template<typename T> void generic_delete(T* obj) { delete obj; }
|
||||
template<typename T> void generic_release(T* obj) { obj->release(); }
|
||||
|
||||
template<typename T> void* generic_create_void() { return (void*)generic_create<T>(); }
|
||||
template<typename T> void* generic_new_void() { return (void*)generic_new<T>(); }
|
||||
template<typename T> void generic_delete_void(void* obj) { generic_delete((T*)obj); }
|
||||
template<typename T> void generic_release_void(void* obj) { generic_release((T*)obj); }
|
||||
//
|
||||
//template<typename T> T* generic_create() { return T::create(); }
|
||||
//template<typename T> T* generic_new() { return new T; }
|
||||
//template<typename T> void generic_delete(T* obj) { delete obj; }
|
||||
//template<typename T> void generic_release(T* obj) { obj->release(); }
|
||||
//
|
||||
//template<typename T> void* generic_create_void() { return (void*)generic_create<T>(); }
|
||||
//template<typename T> void* generic_new_void() { return (void*)generic_new<T>(); }
|
||||
//template<typename T> void generic_delete_void(void* obj) { generic_delete((T*)obj); }
|
||||
//template<typename T> void generic_release_void(void* obj) { generic_release((T*)obj); }
|
||||
|
||||
|
||||
|
||||
@@ -170,97 +196,35 @@ class empty {
|
||||
int x[];
|
||||
};
|
||||
|
||||
class nocopy : private empty {
|
||||
class nocopy : empty {
|
||||
protected:
|
||||
nocopy() {}
|
||||
~nocopy() {}
|
||||
//#ifdef HAVE_MOVE
|
||||
// nocopy(nocopy&&) = default;
|
||||
// const nocopy& operator=(nocopy&&) = default;
|
||||
//#endif
|
||||
private:
|
||||
nocopy(const nocopy&);
|
||||
const nocopy& operator=(const nocopy&);
|
||||
nocopy(const nocopy&) = delete;
|
||||
const nocopy& operator=(const nocopy&) = delete;
|
||||
nocopy(nocopy&&) = default;
|
||||
nocopy& operator=(nocopy&&) = default;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
template<typename T> class autoptr : nocopy {
|
||||
T* obj;
|
||||
#ifdef HAVE_MOVE
|
||||
template<typename T>
|
||||
class autoptr : nocopy {
|
||||
T* ptr;
|
||||
public:
|
||||
autoptr(T* obj) : obj(obj) {}
|
||||
autoptr(map&& other) : obj(other.obj) { other.obj=NULL; }
|
||||
~map() { delete obj; }
|
||||
#else
|
||||
unsigned int* refcount;
|
||||
public:
|
||||
autoptr(T* obj) : obj(obj)
|
||||
{
|
||||
this->refcount=new unsigned int;
|
||||
this->refcount[0]=1;
|
||||
}
|
||||
autoptr(const autoptr& other) : obj(other.obj)
|
||||
{
|
||||
this->refcount=other.refcount;
|
||||
this->refcount[0]++;
|
||||
}
|
||||
~autoptr()
|
||||
{
|
||||
this->refcount[0]--;
|
||||
if (this->refcount[0]==0)
|
||||
{
|
||||
delete this->refcount;
|
||||
delete this->obj;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
T& operator*() { return *obj; }
|
||||
T* operator->() { return obj; }
|
||||
};
|
||||
*/
|
||||
#ifdef HAVE_MOVE
|
||||
#define autoref nocopy
|
||||
#else
|
||||
template<typename T> class autoref {
|
||||
unsigned int* refcount;
|
||||
public:
|
||||
autoref()
|
||||
{
|
||||
this->refcount=new unsigned int;
|
||||
this->refcount[0]=1;
|
||||
}
|
||||
autoref(const autoref& other)
|
||||
{
|
||||
this->refcount=other.refcount;
|
||||
this->refcount[0]++;
|
||||
}
|
||||
~autoref()
|
||||
{
|
||||
this->refcount[0]--;
|
||||
if (this->refcount[0]==0)
|
||||
{
|
||||
((T*)this) -> release();
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
template<typename T> class autoptr : autoref<T> {
|
||||
T* obj;
|
||||
public:
|
||||
autoptr(T* obj) : obj(obj) {}
|
||||
void release() { delete obj; }
|
||||
|
||||
T& operator*() { return *obj; }
|
||||
T* operator->() { return obj; }
|
||||
autoptr() : ptr(NULL) {}
|
||||
autoptr(T* ptr) : ptr(ptr) {}
|
||||
autoptr(autoptr<T>&& other) { ptr=other.ptr; other.ptr=NULL; }
|
||||
autoptr<T>& operator=(T* ptr) { delete this->ptr; this->ptr=ptr; }
|
||||
autoptr<T>& operator=(autoptr<T>&& other) { delete this->ptr; ptr=other.ptr; other.ptr=NULL; }
|
||||
T* operator->() { return ptr; }
|
||||
T& operator*() { return *ptr; }
|
||||
~autoptr() { delete ptr; }
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
#if defined(__linux__) || GCC_VERSION >= 40900
|
||||
#define asprintf(...) ignore(asprintf(__VA_ARGS__))
|
||||
#define asprintf(...) malloc_assert(asprintf(__VA_ARGS__) >= 0)
|
||||
#else
|
||||
void asprintf(char * * ptr, const char * fmt, ...);
|
||||
#endif
|
||||
|
||||
@@ -133,7 +133,7 @@ public:
|
||||
virtual void place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height) = 0;
|
||||
|
||||
//this one acts roughly like Q_OBJECT
|
||||
#define WIDGET_BASE \
|
||||
#define WIDGET_BASE \
|
||||
unsigned int init(struct window * parent, uintptr_t parenthandle); \
|
||||
void measure(); \
|
||||
void place(void* resizeinf, unsigned int x, unsigned int y, unsigned int width, unsigned int height);
|
||||
@@ -400,7 +400,6 @@ private:
|
||||
|
||||
public:
|
||||
widget_listbox_virtual(unsigned int numcolumns, const char * * columns) { construct(numcolumns, columns); }
|
||||
widget_listbox_virtual(const char * firstcol, ...);
|
||||
template<typename... Args>
|
||||
widget_listbox_virtual(Args... cols)
|
||||
{
|
||||
|
||||
@@ -13,7 +13,8 @@ static void debug(void* ptr)
|
||||
|
||||
static void malloc_fail(size_t size)
|
||||
{
|
||||
printf("malloc failed, size %" PRIuPTR, size);
|
||||
if (size > 0) printf("malloc failed, size %" PRIuPTR "\n", size);
|
||||
else puts("malloc failed, size unknown");
|
||||
abort();
|
||||
}
|
||||
|
||||
@@ -56,6 +57,11 @@ anyptr try_calloc(size_t size, size_t count)
|
||||
return calloc(size, count);
|
||||
}
|
||||
|
||||
void malloc_assert(bool cond)
|
||||
{
|
||||
if (!cond) malloc_fail(0);
|
||||
}
|
||||
|
||||
void* operator new(size_t n) { return malloc_check(n); }
|
||||
void* operator new[](size_t n) { return malloc_check(n); }
|
||||
void operator delete(void * p) { free(p); }
|
||||
|
||||
50
arlib/serialize.cpp
Normal file
50
arlib/serialize.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "serialize.h"
|
||||
#include "test.h"
|
||||
#include <string.h>
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
struct serializable_test {
|
||||
int a;
|
||||
int b;
|
||||
|
||||
onserialize() {
|
||||
SER(a);
|
||||
SER(b) SER_HEX;
|
||||
}
|
||||
};
|
||||
|
||||
class serializer_test : public serializer_base<serializer_test> {
|
||||
public:
|
||||
int phase;
|
||||
|
||||
template<typename T>
|
||||
void serialize(const char * name, T& member, const serialize_opts& opts)
|
||||
{
|
||||
if(0);
|
||||
else if (phase==0 && !strcmp(name, "a") && member==16 && opts.hex==false) phase++;
|
||||
else if (phase==1 && !strcmp(name, "b") && member==32 && opts.hex==true) phase++;
|
||||
else phase=-1;
|
||||
member++;
|
||||
}
|
||||
};
|
||||
|
||||
test()
|
||||
{
|
||||
serializer_test s;
|
||||
s.phase = 0;
|
||||
serializable_test item;
|
||||
item.a = 16;
|
||||
item.b = 32;
|
||||
item.serialize(s);
|
||||
|
||||
return s.phase==2 && item.a==17 && item.b==33;
|
||||
}
|
||||
|
||||
//test BML serialization - not sure which file that belongs in, so just pick one
|
||||
#include "bml.h"
|
||||
|
||||
test()
|
||||
{
|
||||
|
||||
}
|
||||
#endif
|
||||
87
arlib/serialize.h
Normal file
87
arlib/serialize.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
#include "global.h"
|
||||
|
||||
//public API:
|
||||
//for serializable classes:
|
||||
|
||||
#define onserialize() \
|
||||
template<typename _T> \
|
||||
void serialize(_T& _s)
|
||||
|
||||
#define SER(member) _s.execute_base(#member, member)+=serialize_opts()
|
||||
|
||||
//these are optional and go in front of SER, in any order
|
||||
//it would be better to do this via reflection and attributes, but it doesn't seem like C++17 will have that, and Arlib is C++11 anyways.
|
||||
#define SER_HEX SER_OPT(hex)
|
||||
#define SER_BML(x) SER_OPT(bml, x)
|
||||
|
||||
//serializer options:
|
||||
#define SER_OPTS(na, a) /* na = no argument to SER_name, a = has argument*/ \
|
||||
na(bool, hex) \
|
||||
a(int8_t, bml) \
|
||||
|
||||
//example:
|
||||
//onserialize() { SER(width) SER_HEX; }
|
||||
|
||||
//for serializers:
|
||||
//implement template<typename T> void serialize(const char * name, T& member, const serialize_opts& opts)
|
||||
//and inherit from serializer_base<your class>, both public
|
||||
//then poke opts.hex
|
||||
|
||||
//for an example of everything, see serialize.cpp
|
||||
|
||||
|
||||
|
||||
//implementation follows (serialize.cpp is just a test). warning: ugly as fuck
|
||||
|
||||
struct serialize_opts {
|
||||
#define X(t, n) t n;
|
||||
SER_OPTS(X, X)
|
||||
#undef X
|
||||
serialize_opts()
|
||||
{
|
||||
#define X(t, n) n=t();
|
||||
SER_OPTS(X, X)
|
||||
#undef X
|
||||
}
|
||||
|
||||
#define Xn(t, n) static serialize_opts opt_##n() { serialize_opts ret; ret.n=1; return ret; }
|
||||
#define Xa(t, n) static serialize_opts opt_##n(t v) { serialize_opts ret; ret.n=v; return ret; }
|
||||
SER_OPTS(Xn, Xa)
|
||||
#undef Xn
|
||||
#undef Xa
|
||||
|
||||
serialize_opts operator+(const serialize_opts& right)
|
||||
{
|
||||
#define X(t, n) n |= right.n;
|
||||
SER_OPTS(X, X)
|
||||
#undef X
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename Tser, typename Tmem>
|
||||
struct serialize_execute {
|
||||
Tser* parent;
|
||||
const char * name;
|
||||
Tmem& member;
|
||||
serialize_execute(Tser* parent, const char * name, Tmem& member) : parent(parent), name(name), member(member) {}
|
||||
};
|
||||
|
||||
template<typename Tser, typename Tmem>
|
||||
void operator+=(const serialize_execute<Tser,Tmem>& exec, const serialize_opts& opts)
|
||||
{
|
||||
exec.parent->serialize(exec.name, exec.member, opts);
|
||||
}
|
||||
|
||||
template<typename real>
|
||||
class serializer_base {
|
||||
public:
|
||||
template<typename T>
|
||||
serialize_execute<real,T> execute_base(const char * name, T& val)
|
||||
{
|
||||
return serialize_execute<real,T>((real*)this, name, val);
|
||||
}
|
||||
};
|
||||
|
||||
#define SER_OPT(name, ...) +serialize_opts::opt_##name(__VA_ARGS__)
|
||||
62
arlib/string.cpp
Normal file
62
arlib/string.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "string.h"
|
||||
#include "test.h"
|
||||
|
||||
test()
|
||||
{
|
||||
{
|
||||
const char * g = "hi";
|
||||
|
||||
string a = g;
|
||||
a[2]='!';
|
||||
string b = a;
|
||||
assert_eq(b, "hi!");
|
||||
a[3]='!';
|
||||
assert_eq(a, "hi!!");
|
||||
assert_eq(b, "hi!");
|
||||
a = b;
|
||||
assert_eq(a, "hi!");
|
||||
assert_eq(b, "hi!");
|
||||
|
||||
|
||||
a.replace(1,1, "ello");
|
||||
assert_eq(a, "hello!");
|
||||
assert_eq(a.substr(1,3), "el");
|
||||
a.replace(1,4, "i");
|
||||
assert_eq(a, "hi!");
|
||||
a.replace(1,2, "ey");
|
||||
assert_eq(a, "hey");
|
||||
|
||||
assert_eq(a.substr(2,2), "");
|
||||
}
|
||||
|
||||
{
|
||||
//ensure it works properly when going across the inline-outline border
|
||||
string a = "123456789012345";
|
||||
a += "678";
|
||||
assert_eq(a, "123456789012345678");
|
||||
a += (const char*)a;
|
||||
string b = a;
|
||||
assert_eq(a, "123456789012345678123456789012345678");
|
||||
assert_eq(a.substr(1,3), "23");
|
||||
assert_eq(b, "123456789012345678123456789012345678");
|
||||
assert_eq(a.substr(1,21), "23456789012345678123");
|
||||
assert_eq(a.substr(1,~1), "2345678901234567812345678901234567");
|
||||
assert_eq(a.substr(2,2), "");
|
||||
assert_eq(a.substr(22,22), "");
|
||||
a.replace(1,5, "-");
|
||||
assert_eq(a, "1-789012345678123456789012345678");
|
||||
a.replace(4,20, "-");
|
||||
assert_eq(a, "1-78-12345678");
|
||||
}
|
||||
|
||||
{
|
||||
string a = "12345678";
|
||||
a += a;
|
||||
a += a;
|
||||
cstring b = a; // ensure this takes a proper reference, rather than piggybacking the original string
|
||||
a = "";
|
||||
assert_eq(b, "12345678123456781234567812345678");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
671
arlib/string.h
671
arlib/string.h
@@ -1,213 +1,515 @@
|
||||
#pragma once
|
||||
#include "global.h"
|
||||
#include <string.h>
|
||||
|
||||
//A cstring does not own its memory; it only borrows it from someone else. A string does own its memory.
|
||||
//Most strings own their storage; all string do.
|
||||
//If a cstring is constructed from a string, it too owns a reference.
|
||||
//However, if it's created from a char* or .csubstr, it lives and dies by the source array/string.
|
||||
//A cstring created from another cstring mirrors the source.
|
||||
//A string created from a 'soulbound' cstring copies the source. It doesn't care if the other cstring is destroyed.
|
||||
|
||||
//Public members shall be named after their counterpart in std::string, if one exists; if not, look in .NET System.String.
|
||||
//If neither have a good counterpart, any name is acceptable.
|
||||
//Rule of thumb: cstring for arguments, string for storage and return value.
|
||||
|
||||
//Due to COW optimizations, strings are not thread safe. If you need to share strings across threads,
|
||||
// call .unshare() after storing it.
|
||||
|
||||
//Strings are always NUL terminated. It is safe to overwrite the NUL on a string; that will extend the string.
|
||||
class cstring;
|
||||
|
||||
class string {
|
||||
//Reference string implementation - slow but simple
|
||||
//all of these functions, including private functions but not including the members, must be present in a complaint string class
|
||||
#if 0
|
||||
private:
|
||||
static const int obj_size = 24; // maximum 32, or len_inline overflows
|
||||
static const int max_inline = obj_size-1-1; // -1 for length, -1 for NUL
|
||||
char* ptr;
|
||||
size_t len;
|
||||
|
||||
//cstring uses the nocopy and null constructors
|
||||
friend class cstring;
|
||||
|
||||
void init_from(const char * str)
|
||||
{
|
||||
ptr = strdup(str);
|
||||
len = strlen(str);
|
||||
}
|
||||
void init_from(const char * str, uint32_t len)
|
||||
{
|
||||
this->len = len;
|
||||
ptr = malloc(len+1);
|
||||
memcpy(ptr, str, len);
|
||||
ptr[len]='\0';
|
||||
}
|
||||
void init_from(const string& other) { init_from(other.ptr); }
|
||||
void init_from(string&& other) { init_from(other.ptr); }
|
||||
void init_from_nocopy(const char * str) { init_from(str); }
|
||||
void init_from_nocopy(const char * str, uint32_t len) { init_from(str, len); }
|
||||
void init_from_nocopy(const string& other) { init_from(other); }
|
||||
void init_from_nocopy(string&& other) { init_from(other.ptr); }
|
||||
void release() { free(ptr); }
|
||||
|
||||
//constant for all string implementations, but used by the implementation, so let's keep it here
|
||||
int32_t realpos(int32_t pos) const
|
||||
{
|
||||
if (pos >= 0) return pos;
|
||||
else return length()-~pos;
|
||||
}
|
||||
|
||||
char getchar(int32_t index) const { return ptr[realpos(index)]; }
|
||||
void setchar(int32_t index_, char val)
|
||||
{
|
||||
uint32_t index = realpos(index_);
|
||||
if (index==len)
|
||||
{
|
||||
len++;
|
||||
ptr = realloc(ptr, len+1);
|
||||
ptr[len] = 0;
|
||||
}
|
||||
ptr[index] = val;
|
||||
if (val == '\0') len = index;
|
||||
}
|
||||
|
||||
//wstring uses these two plus the public API
|
||||
friend class wstring;
|
||||
bool wcache() const { return false; }
|
||||
void wcache(bool newval) const {}
|
||||
|
||||
public:
|
||||
//NUL terminated
|
||||
const char * data() const { return ptr; }
|
||||
uint32_t length() const { return strlen(ptr); }
|
||||
|
||||
//Non-terminated
|
||||
const char * nt() const { return ptr; }
|
||||
|
||||
void replace(int32_t pos, int32_t len, const string& newdat)
|
||||
{
|
||||
pos = realpos(pos);
|
||||
len = realpos(len);
|
||||
|
||||
const char * part1 = ptr;
|
||||
size_t len1 = pos;
|
||||
|
||||
const char * part2 = newdat.ptr;
|
||||
size_t len2 = newdat.len;
|
||||
|
||||
const char * part3 = ptr+pos+len;
|
||||
size_t len3 = strlen(part3);
|
||||
|
||||
char* newptr = malloc(len1+len2+len3+1);
|
||||
memcpy(newptr, part1, len1);
|
||||
memcpy(newptr+len1, part2, len2);
|
||||
memcpy(newptr+len1+len2, part3, len3);
|
||||
newptr[len1+len2+len3]='\0';
|
||||
|
||||
free(ptr);
|
||||
ptr = newptr;
|
||||
len = len1+len2+len3;
|
||||
}
|
||||
|
||||
string& operator+=(const char * right)
|
||||
{
|
||||
char* ret = malloc(len+strlen(right)+1);
|
||||
memcpy(ret, ptr, len);
|
||||
strcpy(ret+len, right);
|
||||
len += strlen(right);
|
||||
free(ptr);
|
||||
ptr = ret;
|
||||
return *this;
|
||||
}
|
||||
|
||||
string& operator+=(const string& right)
|
||||
{
|
||||
this->operator+=(right.data());
|
||||
return *this;
|
||||
}
|
||||
|
||||
#else
|
||||
//Optimized implementation - fast but unreadable
|
||||
static const int obj_size = 16; // maximum 120, or the inline length overflows
|
||||
// (127 would fit, but that requires an extra alignment byte, which throws the sizeof assert)
|
||||
// minimum 16 (pointer + various members + alignment)
|
||||
static const int max_inline = obj_size-1;
|
||||
|
||||
union {
|
||||
struct { // .inlined = 1 (checking .inlined is always allowed)
|
||||
//ensure .inlined is in the first byte; don't care if it's top or bottom bit
|
||||
//GCC orders bitfields according to <http://stackoverflow.com/a/1490135>
|
||||
// With GCC, big endian machines lay out the bits big end first and little endian machines lay out the bits little end first.
|
||||
//while MSVC follows <https://msdn.microsoft.com/en-us/library/ewwyfdbe.aspx>
|
||||
// Microsoft Specific: The ordering of data declared as bit fields is from low to high bit
|
||||
//so I need the low bit first on little endian, MSVC or both; and high bit first on bigend GCC.
|
||||
//Luckily, Windows doesn't operate on bigend, per <https://support.microsoft.com/en-us/kb/102025>
|
||||
// Windows NT was designed around Little Endian architecture and was not designed to be compatible with Big Endian
|
||||
//so I can ignore that combination, and swapping based on endian gives what I want.
|
||||
BIGEND_SWAP2(
|
||||
uint8_t inlined : 1;
|
||||
uint8_t owning : 1;
|
||||
mutable uint8_t wcache : 1;
|
||||
,
|
||||
uint8_t len_inline : 5;
|
||||
)
|
||||
//it would be possible to use the last byte of the inlined data as both length indicator and NUL terminator
|
||||
//(0x00 = length 23, other = outlined or shorter)
|
||||
//but the extra effort required makes it not worth it. 22 is a perfectly fine SSO length, I don't need 23.
|
||||
char data_inline[max_inline+1];
|
||||
struct {
|
||||
char m_inline[max_inline];
|
||||
|
||||
//this is how many bytes are unused by the raw string data
|
||||
//if all bytes are used, there are zero unused bytes - which also serves as the NUL
|
||||
//if not inlined, it's -1
|
||||
char m_inline_len;
|
||||
};
|
||||
struct { // .inlined = 0
|
||||
BIGEND_SWAP2(
|
||||
uint32_t inlined32 : 1;
|
||||
uint32_t owning32 : 1;
|
||||
mutable uint32_t wcache32 : 1;
|
||||
,
|
||||
uint32_t len_outline : 29;
|
||||
)
|
||||
//char pad[4];
|
||||
char* data_outline; // if owning, there's also a int32 refcount before this pointer; if not owning, no such thing
|
||||
//char pad2[8];
|
||||
struct {
|
||||
mutable char* m_data; // if owning, there's also a int32 refcount before this pointer; if not owning, no such thing
|
||||
uint32_t m_len;
|
||||
mutable bool m_owning;
|
||||
mutable bool m_nul; // whether the string is properly terminated (always true if owning)
|
||||
mutable bool m_wcache; // could use bitfields here, but no point, there's nothing else I need those extra bytes for
|
||||
char reserved; // matches the last byte of the inline data; never ever access this
|
||||
};
|
||||
};
|
||||
|
||||
bool inlined() const
|
||||
{
|
||||
static_assert(sizeof(string)==obj_size);
|
||||
|
||||
return m_inline_len != (char)-1;
|
||||
}
|
||||
|
||||
const char * ptr() const
|
||||
{
|
||||
if (inlined()) return m_inline;
|
||||
else return m_data;
|
||||
}
|
||||
|
||||
char* ptr()
|
||||
{
|
||||
if (inlined()) return m_inline;
|
||||
else return m_data;
|
||||
}
|
||||
|
||||
|
||||
static size_t bytes_for(uint32_t len)
|
||||
{
|
||||
return bitround(sizeof(int)+len+1);
|
||||
}
|
||||
|
||||
static char * clone_sized(const char * in, uint32_t len, uint32_t alloclen)
|
||||
//the sizes can be 0 if you want to
|
||||
//sizes are how many characters fit in the string, including the NUL
|
||||
static char* alloc(char* prev, uint32_t prevsize, uint32_t newsize)
|
||||
{
|
||||
int* refcount = malloc(bytes_for(alloclen));
|
||||
*refcount = 1;
|
||||
char* ret = (char*)(refcount+1);
|
||||
memcpy(ret, in, len);
|
||||
ret[len] = '\0';
|
||||
return ret;
|
||||
}
|
||||
static char * clone(const char * in, uint32_t len)
|
||||
{
|
||||
return clone_sized(in, len, len);
|
||||
}
|
||||
static char * clone(const char * in)
|
||||
{
|
||||
return clone(in, strlen(in));
|
||||
if (prevsize==0)
|
||||
{
|
||||
char* ptr = malloc(bytes_for(newsize));
|
||||
*(int*)ptr = 1;
|
||||
return ptr+sizeof(int);
|
||||
}
|
||||
|
||||
if (newsize==0)
|
||||
{
|
||||
int* refcount = (int*)(prev-sizeof(int));
|
||||
if (--*refcount == 0) free(refcount);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
prevsize = bytes_for(prevsize);
|
||||
newsize = bytes_for(newsize);
|
||||
if (prevsize==newsize) return prev;
|
||||
|
||||
int* refcount = (int*)(prev-sizeof(int));
|
||||
if (*refcount == 1)
|
||||
{
|
||||
return (char*)realloc(refcount, newsize)+sizeof(int);
|
||||
}
|
||||
--*refcount;
|
||||
|
||||
char* ptr = malloc(bytes_for(newsize));
|
||||
memcpy(ptr, prev, min(prevsize, newsize));
|
||||
*(int*)ptr = 1;
|
||||
return ptr+sizeof(int);
|
||||
}
|
||||
|
||||
int* refcount() // yields garbage if not inlined or not owning
|
||||
|
||||
void unshare() const
|
||||
{
|
||||
return (int*)(data_outline-sizeof(int));
|
||||
wcache(false);
|
||||
if (inlined()) return;
|
||||
if (m_owning && *(int*)(m_data-sizeof(int))==1) return;
|
||||
|
||||
m_owning = true;
|
||||
m_data = alloc(m_data,m_len, m_len);
|
||||
m_data[m_len] = '\0';
|
||||
m_nul = true;
|
||||
}
|
||||
void addref()
|
||||
|
||||
//does not initialize the new data
|
||||
void resize(uint32_t newlen)
|
||||
{
|
||||
if (inlined) return;
|
||||
if (!owning) return;
|
||||
++*refcount();
|
||||
}
|
||||
void release()
|
||||
{
|
||||
if (inlined) return;
|
||||
if (!owning) return;
|
||||
if (--*refcount() == 0) free(data_outline - sizeof(int));
|
||||
unshare();
|
||||
|
||||
switch (!inlined()<<1 | (newlen>max_inline))
|
||||
{
|
||||
case 0: // small->small
|
||||
{
|
||||
m_inline[newlen] = '\0';
|
||||
m_inline_len = max_inline-newlen;
|
||||
}
|
||||
break;
|
||||
case 1: // small->big
|
||||
{
|
||||
char* newptr = alloc(NULL,0, newlen+1);
|
||||
memcpy(newptr, m_inline, max_inline);
|
||||
newptr[newlen] = '\0';
|
||||
m_data = newptr;
|
||||
m_len = newlen;
|
||||
m_owning = true;
|
||||
m_nul = true;
|
||||
m_wcache = false;
|
||||
|
||||
m_inline_len = -1;
|
||||
}
|
||||
break;
|
||||
case 2: // big->small
|
||||
{
|
||||
char* oldptr = m_data;
|
||||
uint32_t oldlen = m_len;
|
||||
memcpy(m_inline, oldptr, newlen);
|
||||
alloc(oldptr,oldlen, 0);
|
||||
m_inline[newlen] = '\0';
|
||||
m_inline_len = max_inline-newlen;
|
||||
}
|
||||
break;
|
||||
case 3: // big->big
|
||||
{
|
||||
m_data = alloc(m_data,m_len, newlen+1);
|
||||
m_data[newlen] = '\0';
|
||||
m_len = newlen;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
//Detaches a string object from anything it's COWed with. Normally not needed, but if you need to
|
||||
// share a string across threads, it can be useful.
|
||||
void unshare()
|
||||
//NUL terminated
|
||||
const char * data() const
|
||||
{
|
||||
wcache = 0;
|
||||
if (inlined) return;
|
||||
if (owning && *refcount() == 1) return;
|
||||
//use the string after releasing our reference - ugly, but we lose the old refcount if we change data_outline, and we're not thread safe anyways
|
||||
release();
|
||||
owning = 1;
|
||||
data_outline = clone(data_outline, len_outline);
|
||||
if (!inlined() && !m_nul)
|
||||
{
|
||||
unshare();
|
||||
}
|
||||
return ptr();
|
||||
}
|
||||
uint32_t length() const
|
||||
{
|
||||
if (inlined()) return max_inline-m_inline_len;
|
||||
else return m_len;
|
||||
}
|
||||
|
||||
//Non-terminated (can be terminated in some cases)
|
||||
const char * nt() const
|
||||
{
|
||||
return ptr();
|
||||
}
|
||||
bool ntterm() const
|
||||
{
|
||||
return (inlined() || m_nul);
|
||||
}
|
||||
|
||||
private:
|
||||
//cstring uses the nocopy constructors
|
||||
friend class cstring;
|
||||
|
||||
void init_from(const char * str)
|
||||
{
|
||||
uint32_t len = strlen(str);
|
||||
init_from(str, strlen(str));
|
||||
}
|
||||
void init_from(const char * str, uint32_t len)
|
||||
{
|
||||
if (len <= max_inline)
|
||||
{
|
||||
inlined = 1;
|
||||
owning = 1;
|
||||
wcache = 0;
|
||||
len_inline = len;
|
||||
memcpy(data_inline, str, len+1);
|
||||
memcpy(m_inline, str, len);
|
||||
m_inline[len] = '\0';
|
||||
m_inline_len = max_inline-len;
|
||||
}
|
||||
else
|
||||
{
|
||||
inlined32 = 0;
|
||||
owning32 = 1;
|
||||
wcache32 = 0;
|
||||
len_outline = len;
|
||||
data_outline = clone(str, len_outline);
|
||||
m_inline_len = -1;
|
||||
|
||||
m_data = alloc(NULL,0, len+1);
|
||||
memcpy(m_data, str, len);
|
||||
m_data[len]='\0';
|
||||
|
||||
m_len = len;
|
||||
m_owning = true;
|
||||
m_nul = true;
|
||||
m_wcache = false;
|
||||
}
|
||||
}
|
||||
|
||||
void init_from(const string& other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(string));
|
||||
if (!inlined)
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
if (!inlined())
|
||||
{
|
||||
if (owning) addref();
|
||||
else data_outline = clone(data_outline, len_outline);
|
||||
if (m_owning) ++*(int*)(m_data-sizeof(int));
|
||||
else unshare();
|
||||
}
|
||||
}
|
||||
|
||||
void resize(uint32_t newsize)
|
||||
void init_from(string&& other)
|
||||
{
|
||||
uint32_t oldsize = size();
|
||||
if (oldsize == newsize) return;
|
||||
unshare();
|
||||
|
||||
if (newsize > max_inline)
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
other.m_inline_len = 0;
|
||||
if (!inlined() && !m_owning) unshare();
|
||||
}
|
||||
void init_from_nocopy(const char * str)
|
||||
{
|
||||
init_from_nocopy(str, strlen(str));
|
||||
if (!inlined()) m_nul = true;
|
||||
}
|
||||
void init_from_nocopy(const char * str, uint32_t len)
|
||||
{
|
||||
if (len <= max_inline)
|
||||
{
|
||||
if (inlined)
|
||||
{
|
||||
data_outline = clone_sized(data_inline, oldsize, newsize);
|
||||
}
|
||||
else if (bytes_for(oldsize) != bytes_for(newsize))
|
||||
{
|
||||
data_outline = realloc(data_outline-sizeof(int), bytes_for(newsize));
|
||||
}
|
||||
inlined32 = 0;
|
||||
owning32 = 1; // set this unconditionally, it allows the compiler to merge the writed
|
||||
wcache32 = 0;
|
||||
len_outline = newsize;
|
||||
data_outline[newsize] = '\0';
|
||||
memcpy(m_inline, str, len);
|
||||
m_inline[len] = '\0';
|
||||
m_inline_len = max_inline-len;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!inlined) memcpy(data_inline, data(), oldsize);
|
||||
data_inline[newsize] = '\0';
|
||||
inlined = 1;
|
||||
owning = 1;
|
||||
wcache = 0;
|
||||
len_inline = newsize;
|
||||
m_inline_len = -1;
|
||||
|
||||
m_data = (char*)str;
|
||||
m_len = len;
|
||||
m_owning = false;
|
||||
m_nul = false;
|
||||
m_wcache = false;
|
||||
}
|
||||
}
|
||||
void init_from_nocopy(const string& other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
if (!inlined() && m_owning)
|
||||
{
|
||||
++*(int*)(m_data-sizeof(int));
|
||||
}
|
||||
}
|
||||
void init_from_nocopy(string&& other)
|
||||
{
|
||||
memcpy(this, &other, sizeof(*this));
|
||||
other.m_inline_len = 0;
|
||||
}
|
||||
void release()
|
||||
{
|
||||
if (!inlined() && m_owning)
|
||||
{
|
||||
alloc(m_data,m_len, 0);
|
||||
}
|
||||
}
|
||||
|
||||
//Ignored if the new size is smaller.
|
||||
void resize_grow(uint32_t newsize)
|
||||
//constant for all string implementations, but used by the implementation, so let's keep it here
|
||||
int32_t realpos(int32_t pos) const
|
||||
{
|
||||
uint32_t oldsize = size();
|
||||
if (oldsize >= newsize) return;
|
||||
resize(newsize);
|
||||
if (pos >= 0) return pos;
|
||||
else return length()-~pos;
|
||||
}
|
||||
|
||||
//Ignored if the new size is larger.
|
||||
void resize_shrink(uint32_t newsize)
|
||||
char getchar(int32_t index) const
|
||||
{
|
||||
uint32_t oldsize = size();
|
||||
if (oldsize <= newsize) return;
|
||||
resize(newsize);
|
||||
//this function is REALLY hot, use the strongest possible optimizations
|
||||
if (index >= 0)
|
||||
{
|
||||
if (inlined()) return m_inline[index];
|
||||
else if ((uint32_t)index < m_len) return m_data[index];
|
||||
else return '\0';
|
||||
}
|
||||
|
||||
return getchar(realpos(index));
|
||||
}
|
||||
void setchar(int32_t index_, char val)
|
||||
{
|
||||
unshare();
|
||||
uint32_t index = realpos(index_);
|
||||
if (index == length())
|
||||
{
|
||||
resize(index+1);
|
||||
}
|
||||
ptr()[index] = val;
|
||||
}
|
||||
|
||||
char getchar(uint32_t index) const { return data()[index]; }
|
||||
void setchar(uint32_t index, char val) { unshare(); resize_grow(index+1); data()[index] = val; }
|
||||
//wstring uses these two plus the public API
|
||||
friend class wstring;
|
||||
bool wcache() const
|
||||
{
|
||||
if (inlined()) return false;
|
||||
else return m_wcache;
|
||||
}
|
||||
void wcache(bool newval) const
|
||||
{
|
||||
if (!inlined()) m_wcache = newval;
|
||||
}
|
||||
|
||||
char * data() { return inlined ? data_inline : data_outline; }
|
||||
void append(const char * newdat, uint32_t newlength)
|
||||
{
|
||||
if (newdat >= ptr() && newdat < ptr()+length())
|
||||
{
|
||||
uint32_t offset = newdat-ptr();
|
||||
uint32_t oldlength = length();
|
||||
resize(oldlength+newlength);
|
||||
memcpy(ptr()+oldlength, ptr()+offset, newlength);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t oldlength = length();
|
||||
resize(oldlength+newlength);
|
||||
memcpy(ptr()+oldlength, newdat, newlength);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
//Resizes the string to a suitable size, then allows the caller to fill it in with whatever. Contents are undefined.
|
||||
char* construct(uint32_t len)
|
||||
{
|
||||
resize(len);
|
||||
return ptr();
|
||||
}
|
||||
|
||||
void replace(int32_t pos, int32_t len, const string& newdat)
|
||||
{
|
||||
//if newdat is a cstring backed by this, then modifying this invalidates that string, so it's illegal
|
||||
//if newdat equals this, then the memmoves will mess things up
|
||||
if (this == &newdat)
|
||||
{
|
||||
string copy = newdat;
|
||||
replace(pos, len, copy);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t prevlength = length();
|
||||
uint32_t newlength = newdat.length();
|
||||
|
||||
if (newlength < prevlength)
|
||||
{
|
||||
unshare();
|
||||
memmove(ptr()+pos+newlength, ptr()+pos+len, prevlength-len-pos);
|
||||
resize(prevlength - len + newlength);
|
||||
}
|
||||
if (newlength == prevlength)
|
||||
{
|
||||
unshare();
|
||||
}
|
||||
if (newlength > prevlength)
|
||||
{
|
||||
resize(prevlength - len + newlength);
|
||||
memmove(ptr()+pos+newlength, ptr()+pos+len, prevlength-len-pos);
|
||||
}
|
||||
|
||||
memcpy(ptr()+pos, newdat.ptr(), newlength);
|
||||
}
|
||||
|
||||
string& operator+=(const char * right)
|
||||
{
|
||||
append(right, strlen(right));
|
||||
return *this;
|
||||
}
|
||||
|
||||
string& operator+=(const string& right)
|
||||
{
|
||||
append(right.ptr(), right.length());
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
//Shared between all string implementations.
|
||||
private:
|
||||
class noinit {};
|
||||
string(noinit) {}
|
||||
|
||||
public:
|
||||
string() { inlined=1; owning=1; wcache=0; len_inline=0; data_inline[0] = '\0'; }
|
||||
string() { init_from(""); }
|
||||
string(const string& other) { init_from(other); }
|
||||
string(string&& other) { init_from(std::move(other)); }
|
||||
string(const char * str) { init_from(str); }
|
||||
string(const char * str, uint32_t len) { init_from(str, len); }
|
||||
string& operator=(const string& other) { release(); init_from(other); return *this; }
|
||||
string& operator=(const char * str) { release(); init_from(str); return *this; }
|
||||
~string() { release(); }
|
||||
|
||||
const char * data() const { return inlined ? data_inline : data_outline; }
|
||||
uint32_t size() const { return inlined ? len_inline : len_outline; }
|
||||
operator bool() const { return length(); }
|
||||
operator const char * () const { return data(); }
|
||||
|
||||
private:
|
||||
@@ -224,43 +526,66 @@ private:
|
||||
friend class charref;
|
||||
|
||||
public:
|
||||
charref operator[](uint32_t index) { return charref(this, index); }
|
||||
//Reading the NUL terminator is fine. Writing extends the string. Poking outside the string is undefined.
|
||||
//charref operator[](uint32_t index) { return charref(this, index); }
|
||||
charref operator[](int index) { return charref(this, index); }
|
||||
char operator[](uint32_t index) const { return getchar(index); }
|
||||
//char operator[](uint32_t index) const { return getchar(index); }
|
||||
char operator[](int index) const { return getchar(index); }
|
||||
|
||||
void replace(uint32_t pos, uint32_t len, string newdat)
|
||||
static string create(const char * data, uint32_t len) { string ret=noinit(); ret.init_from(data, len); return ret; }
|
||||
|
||||
string substr(int32_t start, int32_t end) const
|
||||
{
|
||||
unshare();
|
||||
uint32_t newlen = newdat.size();
|
||||
if (newlen > len) resize(size()-len+newlen);
|
||||
uint32_t mylen = size();
|
||||
char* dat = data();
|
||||
if (newlen != len) memmove(dat+pos+newlen, dat+pos+len, mylen-len-pos);
|
||||
memcpy(dat+pos, newdat.data(), newlen);
|
||||
if (newlen < len) resize(mylen-len+newlen);
|
||||
start = realpos(start);
|
||||
end = realpos(end);
|
||||
return string(data()+start, end-start);
|
||||
}
|
||||
inline cstring csubstr(int32_t start, int32_t end) const;
|
||||
};
|
||||
|
||||
static inline bool string_eq(const char * left, uint32_t leftlen, const char * right, uint32_t rightlen)
|
||||
{
|
||||
return (leftlen==rightlen && !memcmp(left, right, leftlen));
|
||||
}
|
||||
|
||||
inline bool operator==(const string& left, const char * right ) { return string_eq(left.nt(),left.length(), right,strlen(right)); }
|
||||
inline bool operator==(const string& left, const string& right) { return string_eq(left.nt(),left.length(), right.nt(),right.length()); }
|
||||
inline bool operator==(const char * left, const string& right) { return operator==(right, left); }
|
||||
inline bool operator!=(const string& left, const char * right ) { return !operator==(left, right); }
|
||||
inline bool operator!=(const string& left, const string& right) { return !operator==(left, right); }
|
||||
inline bool operator!=(const char * left, const string& right) { return !operator==(left, right); }
|
||||
|
||||
inline string operator+(string&& left, const char * right) { left+=right; return left; }
|
||||
inline string operator+(const string& left, const char * right) { string ret=left; ret+=right; return ret; }
|
||||
inline string operator+(string&& left, const string& right) { left+=right; return left; }
|
||||
inline string operator+(const string& left, const string& right) { string ret=left; ret+=right; return ret; }
|
||||
inline string operator+(const char * left, const string& right) { string ret=left; ret+=right; return ret; }
|
||||
|
||||
class cstring : public string {
|
||||
friend class string;
|
||||
public:
|
||||
cstring() : string() {}
|
||||
cstring(const string& other) : string(other) {}
|
||||
cstring(const cstring& other) : string(noinit())
|
||||
{
|
||||
memcpy(this, &other, sizeof(cstring));
|
||||
owning = 0;
|
||||
}
|
||||
cstring(const char * str)
|
||||
{
|
||||
inlined32 = 0;
|
||||
owning32 = 0;
|
||||
wcache32 = 0;
|
||||
len_outline = strlen(str);
|
||||
data_outline = (char*)str;
|
||||
}
|
||||
cstring(const string& other) : string(noinit()) { init_from_nocopy(other); }
|
||||
cstring(const cstring& other) : string(noinit()) { init_from_nocopy(other); }
|
||||
cstring(string&& other) : string(noinit()) { init_from_nocopy(std::move(other)); }
|
||||
cstring(cstring&& other) : string(noinit()) { init_from_nocopy(std::move(other)); }
|
||||
cstring(const char * str) : string(noinit()) { init_from_nocopy(str); }
|
||||
cstring(const char * str, uint32_t len) : string(noinit()) { init_from_nocopy(str, len); }
|
||||
private:
|
||||
cstring(const char * str, uint32_t len, bool nul) : string(noinit()) { init_from_nocopy(str, len); if (!inlined()) m_nul=nul; }
|
||||
public:
|
||||
|
||||
cstring& operator=(const cstring& other) { release(); init_from_nocopy(other); return *this; }
|
||||
};
|
||||
|
||||
inline cstring string::csubstr(int32_t start, int32_t end) const
|
||||
{
|
||||
start = realpos(start);
|
||||
end = realpos(end);
|
||||
if (inlined()) return cstring(nt()+start, end-start);
|
||||
else return cstring(nt()+start, end-start, (m_nul && (uint32_t)end == m_len));
|
||||
}
|
||||
|
||||
//TODO
|
||||
class wstring : public string {
|
||||
mutable uint32_t pos_bytes;
|
||||
@@ -274,19 +599,19 @@ class wstring : public string {
|
||||
pos_bytes = 0;
|
||||
pos_chars = 0;
|
||||
wsize = WSIZE_UNKNOWN;
|
||||
wcache = 1;
|
||||
wcache(true);
|
||||
}
|
||||
|
||||
void checkcache() const
|
||||
{
|
||||
if (!wcache) clearcache();
|
||||
if (!wcache()) clearcache();
|
||||
}
|
||||
|
||||
uint32_t findcp(uint32_t index) const
|
||||
uint32_t findcp(int32_t index) const
|
||||
{
|
||||
checkcache();
|
||||
|
||||
if (pos_chars > index)
|
||||
if (pos_chars > (uint32_t)index)
|
||||
{
|
||||
pos_bytes=0;
|
||||
pos_chars=0;
|
||||
@@ -294,7 +619,7 @@ class wstring : public string {
|
||||
|
||||
uint8_t* scan = (uint8_t*)data() + pos_bytes;
|
||||
uint32_t chars = pos_chars;
|
||||
while (chars != index)
|
||||
while (chars != (uint32_t)index)
|
||||
{
|
||||
if ((*scan&0xC0) != 0x80) chars++;
|
||||
scan++;
|
||||
@@ -305,18 +630,18 @@ class wstring : public string {
|
||||
return pos_bytes;
|
||||
}
|
||||
|
||||
uint32_t getcp(uint32_t index) const { return 42; }
|
||||
void setcp(uint32_t index, uint32_t val) { }
|
||||
uint32_t getcp(int32_t index) const { return 42; }
|
||||
void setcp(int32_t index, uint32_t val) { }
|
||||
|
||||
class charref {
|
||||
wstring* parent;
|
||||
uint32_t index;
|
||||
int32_t index;
|
||||
|
||||
public:
|
||||
charref& operator=(char ch) { parent->setcp(index, ch); return *this; }
|
||||
operator uint32_t() { return parent->getcp(index); }
|
||||
|
||||
charref(wstring* parent, uint32_t index) : parent(parent), index(index) {}
|
||||
charref(wstring* parent, int32_t index) : parent(parent), index(index) {}
|
||||
};
|
||||
friend class charref;
|
||||
|
||||
@@ -325,10 +650,8 @@ public:
|
||||
wstring(const string& other) : string(other) { clearcache(); }
|
||||
wstring(const char * str) : string(str) { clearcache(); }
|
||||
|
||||
charref operator[](uint32_t index) { return charref(this, index); }
|
||||
charref operator[](int index) { return charref(this, index); }
|
||||
uint32_t operator[](uint32_t index) const { return getcp(index); }
|
||||
uint32_t operator[](int index) const { return getcp(index); }
|
||||
charref operator[](int32_t index) { return charref(this, index); }
|
||||
uint32_t operator[](int32_t index) const { return getcp(index); }
|
||||
|
||||
uint32_t size() const
|
||||
{
|
||||
|
||||
14
arlib/stringconv.h
Normal file
14
arlib/stringconv.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "string.h"
|
||||
#include <stdio.h>
|
||||
|
||||
inline string tostring(string s) { return s; }
|
||||
inline string tostring(cstring s) { return s; }
|
||||
inline string tostring(const char * s) { return s; }
|
||||
inline string tostring(int val) { char ret[16]; sprintf(ret, "%i", val); return ret; }
|
||||
|
||||
template<typename T> inline T fromstring(string s);
|
||||
template<> inline string fromstring<string>(string s) { return s; }
|
||||
template<> inline cstring fromstring<cstring>(string s) { return s; }
|
||||
//no const char *, their lifetime is unknowable
|
||||
template<> inline int fromstring<int>(string s) { return atoi(s); }
|
||||
|
||||
@@ -1,2 +1,44 @@
|
||||
//TODO
|
||||
//should use
|
||||
#ifdef ARLIB_TEST
|
||||
#include "test.h"
|
||||
|
||||
struct testlist {
|
||||
bool(*func)();
|
||||
const char * name;
|
||||
testlist* next;
|
||||
};
|
||||
|
||||
static testlist* g_testlist;
|
||||
|
||||
_testdecl::_testdecl(bool(*func)(), const char * name)
|
||||
{
|
||||
testlist* next = malloc(sizeof(testlist));
|
||||
next->func = func;
|
||||
next->name = name;
|
||||
next->next = g_testlist;
|
||||
g_testlist = next;
|
||||
}
|
||||
|
||||
#undef main // the real main is #define'd to something stupid on test runs
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int count[2]={0,0};
|
||||
testlist* test = g_testlist;
|
||||
while (test)
|
||||
{
|
||||
testlist* next = test->next;
|
||||
printf("Testing %s...", test->name);
|
||||
bool pass = test->func();
|
||||
count[pass]++;
|
||||
if (pass) puts(" pass");
|
||||
free(test);
|
||||
test = next;
|
||||
}
|
||||
printf("Passed %i, failed %i\n", count[1], count[0]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
test()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
34
arlib/test.h
Normal file
34
arlib/test.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "global.h"
|
||||
#include "stringconv.h"
|
||||
|
||||
#undef assert
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
|
||||
class _testdecl {
|
||||
public:
|
||||
_testdecl(bool(*func)(), const char * name);
|
||||
};
|
||||
|
||||
#define test() \
|
||||
static bool _testfunc##__LINE__(); \
|
||||
static _testdecl _testdeclv(_testfunc##__LINE__, __FILE__ ":" STR(__LINE__)); \
|
||||
static bool _testfunc##__LINE__()
|
||||
#define assert(x) do { if (!(x)) { puts("\nFailed assertion " #x); return false; } } while(0)
|
||||
#define assert_eq(x,y) do { \
|
||||
if ((x) != (y)) \
|
||||
{ \
|
||||
printf("\nFailed assertion " #x " == " #y " (line " STR(__LINE__) "): " \
|
||||
"expected %s, got %s\n", (const char*)tostring(y), (const char*)tostring(x)); \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define test() static bool MAYBE_UNUSED _testfunc_##__LINE__()
|
||||
#define assert(x)
|
||||
#define assert_eq(x,y)
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user