From d7350891528c91f45a1fa404a28de37d97c68b68 Mon Sep 17 00:00:00 2001 From: Alcaro Date: Tue, 2 Aug 2016 00:52:25 +0200 Subject: [PATCH] Update Arlib --- arlib/Makefile | 50 ++-- arlib/arlib.h | 5 +- arlib/array.h | 103 +++++-- arlib/bml.h | 71 +++++ arlib/bmlparse.cpp | 552 ++++++++++++++++++++++++++++++++++++ arlib/bmlwrite.cpp | 38 +++ arlib/global.h | 144 ++++------ arlib/gui/window.h | 3 +- arlib/malloc.cpp | 8 +- arlib/serialize.cpp | 50 ++++ arlib/serialize.h | 87 ++++++ arlib/string.cpp | 62 ++++ arlib/string.h | 671 ++++++++++++++++++++++++++++++++------------ arlib/stringconv.h | 14 + arlib/test.cpp | 46 ++- arlib/test.h | 34 +++ 16 files changed, 1629 insertions(+), 309 deletions(-) create mode 100644 arlib/bml.h create mode 100644 arlib/bmlparse.cpp create mode 100644 arlib/bmlwrite.cpp create mode 100644 arlib/serialize.cpp create mode 100644 arlib/serialize.h create mode 100644 arlib/string.cpp create mode 100644 arlib/stringconv.h create mode 100644 arlib/test.h diff --git a/arlib/Makefile b/arlib/Makefile index c88b985..a7425f6 100644 --- a/arlib/Makefile +++ b/arlib/Makefile @@ -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 diff --git a/arlib/arlib.h b/arlib/arlib.h index 9813c94..12d7d6e 100644 --- a/arlib/arlib.h +++ b/arlib/arlib.h @@ -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) diff --git a/arlib/array.h b/arlib/array.h index d579856..c3d55d8 100644 --- a/arlib/array.h +++ b/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 { 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 slice(size_t first, size_t count) + { + if ((first&7) == 0) + { + array ret; + ret.resize(count); + memcpy(ret.bits(), this->bits() + first/8, (count+7)/8); + return ret; + } + else + { + array ret; + ret.resize(count); + for (size_t i=0;iget(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); } }; diff --git a/arlib/bml.h b/arlib/bml.h new file mode 100644 index 0000000..8652e9a --- /dev/null +++ b/arlib/bml.h @@ -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 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; } +}; diff --git a/arlib/bmlparse.cpp b/arlib/bmlparse.cpp new file mode 100644 index 0000000..9b5bfdf --- /dev/null +++ b/arlib/bmlparse.cpp @@ -0,0 +1,552 @@ +#include "bml.h" +#include "test.h" +#include +//#include + +/* + +[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 diff --git a/arlib/bmlwrite.cpp b/arlib/bmlwrite.cpp new file mode 100644 index 0000000..3234602 --- /dev/null +++ b/arlib/bmlwrite.cpp @@ -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 diff --git a/arlib/global.h b/arlib/global.h index 0717773..85d44a6 100644 --- a/arlib/global.h +++ b/arlib/global.h @@ -29,6 +29,7 @@ #include #include #include "function.h" +#include 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 char(&ARRAY_SIZE_CORE(T(&x)[N]))[N]; @@ -72,7 +76,7 @@ template 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 char(&ARRAY_SIZE_CORE(T(&x)[N]))[N]; #define MAYBE_UNUSED #define TYPENAME_IF_GCC #endif + +#if __cplusplus < 201103 template struct static_assert_t; template<> struct static_assert_t { struct STATIC_ASSERTION_FAILED {}; }; template<> struct static_assert_t {}; @@ -92,6 +98,9 @@ template<> struct static_assert_t {}; 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 static inline void ignore(T t) {} +template static T min(const T& a) { return a; } +template static T min(const T& a, Args... args) +{ + const T& b = min(args...); + if (a < b) return a; + else return b; +} + +template static T max(const T& a) { return a; } +template 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 static inline void ignore(T t) {} //}; //template T* generic_create() { return generic_create_core::create((T*)NULL, NULL); } //template void generic_delete(T* obj) { generic_create_core::destroy(obj, (T*)NULL, NULL); } - -template T* generic_create() { return T::create(); } -template T* generic_new() { return new T; } -template void generic_delete(T* obj) { delete obj; } -template void generic_release(T* obj) { obj->release(); } - -template void* generic_create_void() { return (void*)generic_create(); } -template void* generic_new_void() { return (void*)generic_new(); } -template void generic_delete_void(void* obj) { generic_delete((T*)obj); } -template void generic_release_void(void* obj) { generic_release((T*)obj); } +// +//template T* generic_create() { return T::create(); } +//template T* generic_new() { return new T; } +//template void generic_delete(T* obj) { delete obj; } +//template void generic_release(T* obj) { obj->release(); } +// +//template void* generic_create_void() { return (void*)generic_create(); } +//template void* generic_new_void() { return (void*)generic_new(); } +//template void generic_delete_void(void* obj) { generic_delete((T*)obj); } +//template 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 class autoptr : nocopy { - T* obj; -#ifdef HAVE_MOVE +template +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 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 class autoptr : autoref { - 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&& other) { ptr=other.ptr; other.ptr=NULL; } + autoptr& operator=(T* ptr) { delete this->ptr; this->ptr=ptr; } + autoptr& operator=(autoptr&& 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 diff --git a/arlib/gui/window.h b/arlib/gui/window.h index e7a4eec..52844d1 100644 --- a/arlib/gui/window.h +++ b/arlib/gui/window.h @@ -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 widget_listbox_virtual(Args... cols) { diff --git a/arlib/malloc.cpp b/arlib/malloc.cpp index 0611974..0f72f84 100644 --- a/arlib/malloc.cpp +++ b/arlib/malloc.cpp @@ -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); } diff --git a/arlib/serialize.cpp b/arlib/serialize.cpp new file mode 100644 index 0000000..f9c83d4 --- /dev/null +++ b/arlib/serialize.cpp @@ -0,0 +1,50 @@ +#include "serialize.h" +#include "test.h" +#include + +#ifdef ARLIB_TEST +struct serializable_test { +int a; +int b; + +onserialize() { + SER(a); + SER(b) SER_HEX; +} +}; + +class serializer_test : public serializer_base { +public: + int phase; + + template + 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 diff --git a/arlib/serialize.h b/arlib/serialize.h new file mode 100644 index 0000000..50659fb --- /dev/null +++ b/arlib/serialize.h @@ -0,0 +1,87 @@ +#pragma once +#include "global.h" + +//public API: +//for serializable classes: + +#define onserialize() \ + template \ + 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 void serialize(const char * name, T& member, const serialize_opts& opts) +//and inherit from serializer_base, 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 +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 +void operator+=(const serialize_execute& exec, const serialize_opts& opts) +{ + exec.parent->serialize(exec.name, exec.member, opts); +} + +template +class serializer_base { +public: + template + serialize_execute execute_base(const char * name, T& val) + { + return serialize_execute((real*)this, name, val); + } +}; + +#define SER_OPT(name, ...) +serialize_opts::opt_##name(__VA_ARGS__) diff --git a/arlib/string.cpp b/arlib/string.cpp new file mode 100644 index 0000000..df892e5 --- /dev/null +++ b/arlib/string.cpp @@ -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; +} diff --git a/arlib/string.h b/arlib/string.h index 712bcc3..c4cd51f 100644 --- a/arlib/string.h +++ b/arlib/string.h @@ -1,213 +1,515 @@ #pragma once #include "global.h" +#include -//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 - // 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 - // 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 - // 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 { diff --git a/arlib/stringconv.h b/arlib/stringconv.h new file mode 100644 index 0000000..4b26647 --- /dev/null +++ b/arlib/stringconv.h @@ -0,0 +1,14 @@ +#include "string.h" +#include + +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 inline T fromstring(string s); +template<> inline string fromstring(string s) { return s; } +template<> inline cstring fromstring(string s) { return s; } +//no const char *, their lifetime is unknowable +template<> inline int fromstring(string s) { return atoi(s); } + diff --git a/arlib/test.cpp b/arlib/test.cpp index 82d8a06..b033a09 100644 --- a/arlib/test.cpp +++ b/arlib/test.cpp @@ -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 diff --git a/arlib/test.h b/arlib/test.h new file mode 100644 index 0000000..19a55e7 --- /dev/null +++ b/arlib/test.h @@ -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