Update Arlib

This commit is contained in:
Alcaro
2016-08-02 00:52:25 +02:00
parent 9335f0b024
commit d735089152
16 changed files with 1629 additions and 309 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
View 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
View 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
View 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

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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
View 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
View 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
View 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;
}

View File

@@ -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
View 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); }

View File

@@ -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
View 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