mirror of
https://github.com/Alcaro/Flips.git
synced 2026-04-25 07:21:57 -05:00
Update Arlib
This commit is contained in:
parent
cb2ab4e9e6
commit
9908250790
|
|
@ -63,6 +63,9 @@ endif
|
|||
ifneq (,$(findstring test,$(MAKECMDGOALS)))
|
||||
SELFTEST = 1
|
||||
endif
|
||||
ifneq (,$(findstring check,$(MAKECMDGOALS)))
|
||||
SELFTEST = 1
|
||||
endif
|
||||
|
||||
OPTFLAGS := -Os -fomit-frame-pointer -fmerge-all-constants -fvisibility=hidden
|
||||
OPTFLAGS += -fno-exceptions -fno-unwind-tables -fno-asynchronous-unwind-tables
|
||||
|
|
@ -191,7 +194,9 @@ TRUE_LFLAGS = $(LFLAGS) -fvisibility=hidden $(CONF_LFLAGS)
|
|||
#// bar(a) \
|
||||
#// bar(b) \
|
||||
#// bar(c)
|
||||
#(2) https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431 says '#pragma GCC diagnostic ignored "-Wcomment"' does nothing
|
||||
# to my knowledge unreported
|
||||
#(2) '#pragma GCC diagnostic ignored "-Wcomment"' does nothing
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53431
|
||||
TRUE_CFLAGS += -Wno-comment
|
||||
TRUE_CXXFLAGS += -Wno-comment
|
||||
|
||||
|
|
@ -233,4 +238,7 @@ obj/arlibtest$(EXESUFFIX): $(OBJS)
|
|||
|
||||
test: obj/arlibtest$(EXESUFFIX)
|
||||
$(TESTRUNNER) obj/arlibtest$(EXESUFFIX)
|
||||
check: test
|
||||
endif
|
||||
|
||||
#todo: replace with ./test shell script (make test/check should still exist)
|
||||
|
|
|
|||
56
arlib/bml.h
56
arlib/bml.h
|
|
@ -4,14 +4,13 @@
|
|||
#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 }.
|
||||
//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.
|
||||
|
|
@ -23,9 +22,12 @@ public:
|
|||
int action;
|
||||
cstring name;
|
||||
cstring value; // or error message
|
||||
// putting error first would be cleaner in the parser, but reader clarity is more important, and this name is better
|
||||
};
|
||||
|
||||
//Since this takes a cstring, the string must be kept alive until the object is disposed.
|
||||
//Remember the cstring rules: If this cstring doesn't hold a reference, don't touch its buffer until the object is disposed.
|
||||
//If it's originally a string, don't worry about it.
|
||||
//It is not allowed to try to stream data into this object.
|
||||
bmlparser(cstring bml) : m_orig_data(bml), m_data(bml), m_exit(false) {}
|
||||
event next();
|
||||
|
||||
|
|
@ -42,30 +44,50 @@ private:
|
|||
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().
|
||||
//This is also streaming.
|
||||
//Calling exit() without a matching enter(), or finish() without closing every enter(), is undefined behavior.
|
||||
class bmlwriterx {
|
||||
class bmlwriter {
|
||||
string m_data;
|
||||
int m_indent;
|
||||
int m_indent = 0;
|
||||
bool m_caninline = false;
|
||||
|
||||
inline string indent();
|
||||
|
||||
public:
|
||||
enum mode {
|
||||
anon, // parent node
|
||||
inl_eq, // parent node=value
|
||||
inl_col, // parent node: value
|
||||
eq, // node=value
|
||||
col, // node: value
|
||||
ianon, // parent node
|
||||
ieq, // parent node=value
|
||||
iquote, // parent node="value"
|
||||
icol, // parent node: value
|
||||
|
||||
anon, // node
|
||||
eq, // node=value
|
||||
quote, // 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--; }
|
||||
//If you pass in data that's not valid for that mode (for example, val="foo bar" and mode=eq won't work),
|
||||
// then it silently switches to the lowest working mode (in the above example, quote).
|
||||
//Since enter() implies the tag has children, it will also disobey the inline modes; use node() for that.
|
||||
void enter(cstring name, cstring val, mode m = anon);
|
||||
void exit();
|
||||
void linebreak();
|
||||
void comment(cstring text);
|
||||
void node(cstring name, cstring val, mode m);
|
||||
void node(cstring name, cstring val, mode m = ianon); // Equivalent to enter()+exit(), except supports inline nodes.
|
||||
|
||||
//TODO: inline nodes on multilines? They're ridiculously ugly, but there are a few cases where they're useful.
|
||||
//I'll decide what to do once I actually need a multiline with children. Maybe add mode multiline_i or something.
|
||||
|
||||
//Tells what mode will actually be used if node() is called with these parameters and in this context.
|
||||
//To ask what enter() would do, call this with a non-inline mode.
|
||||
mode typeof(cstring val, mode m) const;
|
||||
|
||||
private:
|
||||
void node(cstring name, cstring val, mode m, bool enter);
|
||||
static mode typeof_core(cstring val);
|
||||
public:
|
||||
|
||||
string finish() { return m_data; }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ static bool bml_parse_inline_node(cstring& data, cstring& node, bool& hasvalue,
|
|||
if (nodestart == nodelen)
|
||||
{
|
||||
value = "Invalid node name";
|
||||
while (data[nodelen]!='\n' && data[nodelen]!='\0') nodelen++;
|
||||
data = data.csubstr(nodelen, ~0);
|
||||
return false;
|
||||
}
|
||||
node = cut(data, nodestart, nodelen, 0);
|
||||
|
|
@ -216,6 +218,7 @@ static bool bml_parse_inline_node(cstring& data, cstring& node, bool& hasvalue,
|
|||
|
||||
static bool isendl(char ch)
|
||||
{
|
||||
//this 32 is also a perf hack
|
||||
if (ch>=32) return false;
|
||||
return (ch=='\r' || ch=='\n' || ch=='\0');
|
||||
}
|
||||
|
|
@ -225,8 +228,7 @@ 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())
|
||||
if (input.hasnt())
|
||||
{
|
||||
while (!isendl(inputraw[nlpos])) nlpos++;
|
||||
}
|
||||
|
|
@ -299,9 +301,16 @@ bmlparser::event bmlparser::next()
|
|||
if (m_indent_step.size() > m_indent.length())
|
||||
{
|
||||
handle_indent:
|
||||
if (!m_indent_step[m_indent.length()]) return (event){ error, "", "Invalid indentation depth" };
|
||||
if (!m_indent_step[m_indent.length()])
|
||||
{
|
||||
//this may throw random mix-tab-space errors that weren't present in the original,
|
||||
// but only if the document contains mix-tab-space already.
|
||||
if (m_indent_step.size() > m_indent.length()) m_indent += m_indent[0];
|
||||
else m_indent = m_indent.csubstr(0, ~1);
|
||||
return (event){ error, "", "Invalid indentation depth" };
|
||||
}
|
||||
|
||||
int lasttrue = m_indent_step.size()-2;
|
||||
int lasttrue = m_indent_step.size()-2; // -1 for [size()] being OOB, -1 to skip the true at [size()-1] and discard it
|
||||
while (lasttrue>=0 && m_indent_step[lasttrue]==false) lasttrue--;
|
||||
|
||||
m_indent_step.resize(lasttrue+1);
|
||||
|
|
@ -493,7 +502,7 @@ bmlparser::event test4e[]={
|
|||
{ e_finish }
|
||||
};
|
||||
|
||||
static bool testbml(const char * bml, bmlparser::event* expected)
|
||||
static void testbml(const char * bml, bmlparser::event* expected)
|
||||
{
|
||||
bmlparser parser(bml);
|
||||
while (true)
|
||||
|
|
@ -506,47 +515,57 @@ static bool testbml(const char * bml, bmlparser::event* expected)
|
|||
assert_eq(actual.name, expected->name);
|
||||
assert_eq(actual.value, expected->value);
|
||||
|
||||
if (expected->action == e_finish || actual.action == e_finish) return true;
|
||||
if (expected->action == e_finish || actual.action == e_finish) return;
|
||||
|
||||
expected++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool testbml_error(const char * bml)
|
||||
static void testbml_error(const char * bml)
|
||||
{
|
||||
bmlparser parser(bml);
|
||||
for (int i=0;i<100;i++)
|
||||
int depth = 0;
|
||||
bool error = false;
|
||||
int events = 0;
|
||||
while (true)
|
||||
{
|
||||
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;
|
||||
if (events==999)
|
||||
printf("a=%i [%s] [%s]\n\n", ev.action, ev.name.data(), ev.value.data());
|
||||
if (ev.action == e_error) error = true; // any error is fine, really
|
||||
if (ev.action == e_enter) depth++;
|
||||
if (ev.action == e_exit) depth--;
|
||||
if (ev.action == e_finish) break;
|
||||
assert(depth >= 0);
|
||||
|
||||
events++;
|
||||
assert(events < 1000); // fail on infinite error loops
|
||||
}
|
||||
assert(!"expected error");
|
||||
assert_eq(error, true);
|
||||
assert_eq(depth, 0);
|
||||
}
|
||||
|
||||
test()
|
||||
{
|
||||
assert(testbml(test1, test1e));
|
||||
assert(testbml(test2, test2e));
|
||||
assert(testbml(test3, test3e));
|
||||
assert(testbml(test4, test4e));
|
||||
testcall(testbml(test1, test1e));
|
||||
testcall(testbml(test2, test2e));
|
||||
testcall(testbml(test3, test3e));
|
||||
testcall(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
|
||||
testcall(testbml_error("*")); // invalid node name
|
||||
testcall(testbml_error("a=\"")); // unclosed quote
|
||||
testcall(testbml_error("a=\"b\"c")); // no space after closing quote
|
||||
testcall(testbml_error("a=\"b\"c\"")); // quote in quoted element
|
||||
testcall(testbml_error("a\n b\n c")); // derpy indentation
|
||||
testcall(testbml_error("a\n b\n\tc")); // mixed tabs and spaces
|
||||
testcall(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;
|
||||
testcall(testbml_error("a\n :b\n :c"));
|
||||
testcall(testbml_error("a\n :b\n :c"));
|
||||
testcall(testbml_error("a\n :b\n c"));
|
||||
testcall(testbml_error("a\n :b\n c"));
|
||||
testcall(testbml_error("a\n :b\n\t:c"));
|
||||
testcall(testbml_error("a\n :b\n\tc"));
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,38 +1,168 @@
|
|||
#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;
|
||||
inline string bmlwriter::indent()
|
||||
{
|
||||
string ret;
|
||||
char* ptr = ret.construct(m_indent*2);
|
||||
memset(ptr, ' ', m_indent*2);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void bmlwriter::node(cstring name, cstring val, mode m, bool enter)
|
||||
{
|
||||
m = typeof(val, m);
|
||||
bool inlined = (m <= icol && m_caninline && !enter);
|
||||
if (m_data)
|
||||
{
|
||||
if (inlined) m_data += " ";
|
||||
else m_data += "\n"+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
|
||||
};
|
||||
m_indent++;
|
||||
|
||||
bmlwriter() { m_indent = 0; }
|
||||
if ((m==anon || m==eq || m==quote) && enter) m_caninline = true;
|
||||
if (m==icol || m==col || m==multiline) m_caninline = false;
|
||||
//for other modes, inlinability isn't affected
|
||||
|
||||
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);
|
||||
switch (m)
|
||||
{
|
||||
case ianon:
|
||||
case anon:
|
||||
m_data += name;
|
||||
break;
|
||||
case ieq:
|
||||
case eq:
|
||||
m_data += name+"="+val;
|
||||
break;
|
||||
case iquote:
|
||||
case quote:
|
||||
m_data += name+"=\""+val+"\"";
|
||||
break;
|
||||
case icol:
|
||||
case col:
|
||||
m_data += name+": "+val;
|
||||
break;
|
||||
case multiline:
|
||||
string prefix = "\n"+indent()+":";
|
||||
m_data += name + prefix + val.replace("\n", prefix);
|
||||
break;
|
||||
}
|
||||
if (!enter) m_indent--;
|
||||
}
|
||||
|
||||
void bmlwriter::enter(cstring name, cstring val, mode m) { node(name, val, m, true); }
|
||||
void bmlwriter::node(cstring name, cstring val, mode m) { node(name, val, m, false); }
|
||||
void bmlwriter::exit() { m_indent--; m_caninline = false; }
|
||||
|
||||
bmlwriter::mode bmlwriter::typeof_core(cstring val)
|
||||
{
|
||||
if (val.contains("\n") || val[0]==' ' || val[0]=='\t' || val[~0]==' ' || val[~0]=='\t') return multiline;
|
||||
if (val.contains("\"")) return col;
|
||||
if (val.contains(" ") || val.contains("\t")) return quote;
|
||||
if (val!="") return eq;
|
||||
return anon;
|
||||
}
|
||||
|
||||
bmlwriter::mode bmlwriter::typeof(cstring val, mode m) const
|
||||
{
|
||||
bool inlined = (m <= icol);
|
||||
if (m <= icol) m = (mode)(m+4);
|
||||
|
||||
//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; }
|
||||
};
|
||||
m = max(typeof_core(val), m);
|
||||
if (inlined && m != multiline && m_caninline) return (mode)(m-4);
|
||||
else return m;
|
||||
}
|
||||
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
test()
|
||||
{
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "");
|
||||
w.node("b", "1");
|
||||
w.node("c", "");
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a b=1 c");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "");
|
||||
w.enter("b", "");
|
||||
w.enter("c", "");
|
||||
w.enter("d", "");
|
||||
w.exit();
|
||||
w.exit();
|
||||
w.exit();
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a\n b\n c\n d");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "foo bar");
|
||||
w.node("b", "1");
|
||||
w.node("c", "foo \"bar\"");
|
||||
w.node("d", "");
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a=\"foo bar\" b=1 c: foo \"bar\"\n d");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "foo bar");
|
||||
w.node("b", "1");
|
||||
w.enter("c", "foo \"bar\"");
|
||||
w.node("d", "");
|
||||
w.exit();
|
||||
w.node("e", "");
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a=\"foo bar\" b=1\n c: foo \"bar\"\n d\n e");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "foo\nbar");
|
||||
w.node("b", "");
|
||||
w.node("c", "foo\nbar");
|
||||
w.enter("d", "foo\nbar");
|
||||
w.exit();
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a\n :foo\n :bar\n b\n c\n :foo\n :bar\n d\n :foo\n :bar");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.node("a", "");
|
||||
w.enter("b", "");
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a\nb");
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.node("a", "1");
|
||||
w.node("b", "2");
|
||||
|
||||
assert_eq(w.finish(), "a=1\nb=2"); // these must not be children of each other
|
||||
}
|
||||
|
||||
{
|
||||
bmlwriter w;
|
||||
w.enter("a", "");
|
||||
w.node("b", "c\nd");
|
||||
w.exit();
|
||||
|
||||
assert_eq(w.finish(), "a\n b\n :c\n :d"); // ensure this is properly non-inlined
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ typedef void(*funcptr)();
|
|||
//No attempt is made to keep any kind of backwards or forwards compatibility.
|
||||
|
||||
#define using(obj) for(bool FIRST=true;FIRST;FIRST=false)for(obj;FIRST;FIRST=false)
|
||||
//in C++17, this becomes if(obj;true)
|
||||
|
||||
#define JOIN_(x, y) x ## y
|
||||
#define JOIN(x, y) JOIN_(x, y)
|
||||
|
|
@ -45,11 +46,6 @@ typedef void(*funcptr)();
|
|||
#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];
|
||||
#define ARRAY_SIZE(x) (sizeof(ARRAY_SIZE_CORE(x)))
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define GCC_VERSION (__GNUC__ * 10000 \
|
||||
+ __GNUC_MINOR__ * 100 \
|
||||
|
|
@ -62,6 +58,42 @@ template<typename T, size_t N> char(&ARRAY_SIZE_CORE(T(&x)[N]))[N];
|
|||
#define UNLIKELY(expr) (expr)
|
||||
#endif
|
||||
|
||||
//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];
|
||||
#define ARRAY_SIZE(x) (sizeof(ARRAY_SIZE_CORE(x)))
|
||||
|
||||
//yep, C++ is definitely a mess. based on https://github.com/swansontec/map-macro with some changes:
|
||||
//- namespaced all child macros, renamed main one
|
||||
//- merged https://github.com/swansontec/map-macro/pull/3
|
||||
//- merged http://stackoverflow.com/questions/6707148/foreach-macro-on-macros-arguments#comment62878935_13459454, plus ifdef
|
||||
#define PPFE_EVAL0(...) __VA_ARGS__
|
||||
#define PPFE_EVAL1(...) PPFE_EVAL0 (PPFE_EVAL0 (PPFE_EVAL0 (__VA_ARGS__)))
|
||||
#define PPFE_EVAL2(...) PPFE_EVAL1 (PPFE_EVAL1 (PPFE_EVAL1 (__VA_ARGS__)))
|
||||
#define PPFE_EVAL3(...) PPFE_EVAL2 (PPFE_EVAL2 (PPFE_EVAL2 (__VA_ARGS__)))
|
||||
#define PPFE_EVAL4(...) PPFE_EVAL3 (PPFE_EVAL3 (PPFE_EVAL3 (__VA_ARGS__)))
|
||||
#define PPFE_EVAL(...) PPFE_EVAL4 (PPFE_EVAL4 (PPFE_EVAL4 (__VA_ARGS__)))
|
||||
#define PPFE_MAP_END(...)
|
||||
#define PPFE_MAP_OUT
|
||||
#define PPFE_MAP_GET_END2() 0, PPFE_MAP_END
|
||||
#define PPFE_MAP_GET_END1(...) PPFE_MAP_GET_END2
|
||||
#define PPFE_MAP_GET_END(...) PPFE_MAP_GET_END1
|
||||
#define PPFE_MAP_NEXT0(test, next, ...) next PPFE_MAP_OUT
|
||||
#ifdef _MSC_VER
|
||||
//this version doesn't work on GCC, it makes PPFE_MAP0 not get expanded the second time and quite effectively stops everything.
|
||||
//but completely unknown guy says it's required on MSVC, so I'll trust that and ifdef it.
|
||||
#define PPFE_MAP_NEXT1(test, next) PPFE_EVAL0(PPFE_MAP_NEXT0 (test, next, 0))
|
||||
#else
|
||||
#define PPFE_MAP_NEXT1(test, next) PPFE_MAP_NEXT0 (test, next, 0)
|
||||
#endif
|
||||
#define PPFE_MAP_NEXT(test, next) PPFE_MAP_NEXT1 (PPFE_MAP_GET_END test, next)
|
||||
#define PPFE_MAP0(f, x, peek, ...) f(x) PPFE_MAP_NEXT (peek, PPFE_MAP1) (f, peek, __VA_ARGS__)
|
||||
#define PPFE_MAP1(f, x, peek, ...) f(x) PPFE_MAP_NEXT (peek, PPFE_MAP0) (f, peek, __VA_ARGS__)
|
||||
#define PPFOREACH(f, ...) PPFE_EVAL (PPFE_MAP1 (f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
|
||||
//usage:
|
||||
//#define STRING(x) char const *x##_string = #x;
|
||||
//PPFOREACH(STRING, foo, bar, baz)
|
||||
//limited to 365 entries, but that's enough.
|
||||
|
||||
//requirements:
|
||||
//- static_assert(false) throws something at compile time
|
||||
|
|
@ -102,7 +134,7 @@ template<> struct static_assert_t<false> {};
|
|||
#define static_assert(expr) static_assert(expr, #expr)
|
||||
#endif
|
||||
|
||||
//almost C version (fails inside structs):
|
||||
//almost C version (fails inside structs)
|
||||
//#define static_assert(expr) \
|
||||
// typedef char JOIN(static_assertion_, __COUNTER__)[(expr)?1:-1]
|
||||
|
||||
|
|
@ -206,6 +238,16 @@ protected:
|
|||
nocopy& operator=(nocopy&&) = default;
|
||||
};
|
||||
|
||||
class nomove : empty {
|
||||
protected:
|
||||
nomove() {}
|
||||
~nomove() {}
|
||||
nomove(const nomove&) = delete;
|
||||
const nomove& operator=(const nomove&) = delete;
|
||||
nomove(nomove&&) = delete;
|
||||
nomove& operator=(nomove&&) = delete;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class autoptr : nocopy {
|
||||
T* ptr;
|
||||
|
|
@ -228,6 +270,9 @@ public:
|
|||
#else
|
||||
void asprintf(char * * ptr, const char * fmt, ...);
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
void* memmem(const void * haystack, size_t haystacklen, const void * needle, size_t needlelen);
|
||||
#endif
|
||||
|
||||
|
||||
//msvc:
|
||||
|
|
|
|||
182
arlib/memmem.cpp
Normal file
182
arlib/memmem.cpp
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#include "global.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
//from musl, http://git.musl-libc.org/cgit/musl/tree/src/string/memmem.c commit 4be6a310a7618e791615fcb8543eb2f23d47370b
|
||||
//with a few C++ and warning fixes
|
||||
|
||||
/*
|
||||
musl as a whole is licensed under the following standard MIT license:
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Copyright © 2005-2014 Rich Felker, et al.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
static char *twobyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint16_t nw = n[0]<<8 | n[1], hw = h[0]<<8 | h[1];
|
||||
for (h++, k--; k; k--, hw = hw<<8 | *++h)
|
||||
if (hw == nw) return (char *)h-1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *threebyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8;
|
||||
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8;
|
||||
for (h+=2, k-=2; k; k--, hw = (hw|*++h)<<8)
|
||||
if (hw == nw) return (char *)h-2;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *fourbyte_memmem(const unsigned char *h, size_t k, const unsigned char *n)
|
||||
{
|
||||
uint32_t nw = n[0]<<24 | n[1]<<16 | n[2]<<8 | n[3];
|
||||
uint32_t hw = h[0]<<24 | h[1]<<16 | h[2]<<8 | h[3];
|
||||
for (h+=3, k-=3; k; k--, hw = hw<<8 | *++h)
|
||||
if (hw == nw) return (char *)h-3;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||
|
||||
#define BITOP(a,b,op) \
|
||||
((a)[(size_t)(b)/(8*sizeof *(a))] op (size_t)1<<((size_t)(b)%(8*sizeof *(a))))
|
||||
|
||||
static char *twoway_memmem(const unsigned char *h, const unsigned char *z, const unsigned char *n, size_t l)
|
||||
{
|
||||
size_t i, ip, jp, k, p, ms, p0, mem, mem0;
|
||||
size_t byteset[32 / sizeof(size_t)] = { 0 };
|
||||
size_t shift[256];
|
||||
|
||||
/* Computing length of needle and fill shift table */
|
||||
for (i=0; i<l; i++)
|
||||
BITOP(byteset, n[i], |=), shift[n[i]] = i+1;
|
||||
|
||||
/* Compute maximal suffix */
|
||||
ip = -1; jp = 0; k = p = 1;
|
||||
while (jp+k<l) {
|
||||
if (n[ip+k] == n[jp+k]) {
|
||||
if (k == p) {
|
||||
jp += p;
|
||||
k = 1;
|
||||
} else k++;
|
||||
} else if (n[ip+k] > n[jp+k]) {
|
||||
jp += k;
|
||||
k = 1;
|
||||
p = jp - ip;
|
||||
} else {
|
||||
ip = jp++;
|
||||
k = p = 1;
|
||||
}
|
||||
}
|
||||
ms = ip;
|
||||
p0 = p;
|
||||
|
||||
/* And with the opposite comparison */
|
||||
ip = -1; jp = 0; k = p = 1;
|
||||
while (jp+k<l) {
|
||||
if (n[ip+k] == n[jp+k]) {
|
||||
if (k == p) {
|
||||
jp += p;
|
||||
k = 1;
|
||||
} else k++;
|
||||
} else if (n[ip+k] < n[jp+k]) {
|
||||
jp += k;
|
||||
k = 1;
|
||||
p = jp - ip;
|
||||
} else {
|
||||
ip = jp++;
|
||||
k = p = 1;
|
||||
}
|
||||
}
|
||||
if (ip+1 > ms+1) ms = ip;
|
||||
else p = p0;
|
||||
|
||||
/* Periodic needle? */
|
||||
if (memcmp(n, n+p, ms+1)) {
|
||||
mem0 = 0;
|
||||
p = MAX(ms, l-ms-1) + 1;
|
||||
} else mem0 = l-p;
|
||||
mem = 0;
|
||||
|
||||
/* Search loop */
|
||||
for (;;) {
|
||||
/* If remainder of haystack is shorter than needle, done */
|
||||
if (z-h < (ptrdiff_t)l) return 0;
|
||||
|
||||
/* Check last byte first; advance by shift on mismatch */
|
||||
if (BITOP(byteset, h[l-1], &)) {
|
||||
k = l-shift[h[l-1]];
|
||||
if (k) {
|
||||
if (mem0 && mem && k < p) k = l-p;
|
||||
h += k;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
h += l;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Compare right half */
|
||||
for (k=MAX(ms+1,mem); k<l && n[k] == h[k]; k++);
|
||||
if (k < l) {
|
||||
h += k-ms;
|
||||
mem = 0;
|
||||
continue;
|
||||
}
|
||||
/* Compare left half */
|
||||
for (k=ms+1; k>mem && n[k-1] == h[k-1]; k--);
|
||||
if (k <= mem) return (char *)h;
|
||||
h += p;
|
||||
mem = mem0;
|
||||
}
|
||||
}
|
||||
|
||||
void *memmem(const void *h0, size_t k, const void *n0, size_t l)
|
||||
{
|
||||
const unsigned char *h = (const unsigned char*)h0, *n = (const unsigned char*)n0;
|
||||
|
||||
/* Return immediately on empty needle */
|
||||
if (!l) return (void *)h;
|
||||
|
||||
/* Return immediately when needle is longer than haystack */
|
||||
if (k<l) return 0;
|
||||
|
||||
/* Use faster algorithms for short needles */
|
||||
h = (const unsigned char*)memchr(h0, *n, k);
|
||||
if (!h || l==1) return (void *)h;
|
||||
k -= h - (const unsigned char *)h0;
|
||||
if (k<l) return 0;
|
||||
if (l==2) return twobyte_memmem(h, k, n);
|
||||
if (l==3) return threebyte_memmem(h, k, n);
|
||||
if (l==4) return fourbyte_memmem(h, k, n);
|
||||
|
||||
return twoway_memmem(h, h+k, n, l);
|
||||
}
|
||||
#endif
|
||||
|
|
@ -10,6 +10,10 @@
|
|||
#include <sys/wait.h>
|
||||
#include <linux/futex.h>
|
||||
|
||||
//TODO: merge sh_fd[], one is enough for all plausible purposes
|
||||
//TODO: figure out where O_BENEATH went, it's not in the manpages
|
||||
//TODO: split sandbox::impl to impl_parent, impl_child, impl_shared, to ensure the right one is always used
|
||||
|
||||
#include<errno.h>
|
||||
|
||||
struct sandbox::impl {
|
||||
|
|
|
|||
|
|
@ -32,9 +32,9 @@
|
|||
// are well documented, and if I miss something, strace quickly tells me what.
|
||||
//
|
||||
//On Linux, this requires exclusive control over SIGSYS in the child process.
|
||||
//<http://lxr.free-electrons.com/ident?i=SIGSYS> (as of kernel version 4.6) shows me about fifty references to SIGSYS,
|
||||
// but they're all seccomp-related (mostly in seccomp tests), #define SIGSYS 123, or otherwise uninteresting to handle,
|
||||
// so it's safe to claim this signal for myself.
|
||||
//All uses (according to <http://lxr.free-electrons.com/ident?i=SIGSYS>, kernel version 4.6) are either seccomp,
|
||||
// hardware events on rare platforms that won't be delivered to me, or catching / passing on the signal (as opposed to raising it).
|
||||
//Therefore, this requirement is safe.
|
||||
//
|
||||
//Chrome sandbox entry points: http://stackoverflow.com/questions/1590337/using-the-google-chrome-sandbox
|
||||
|
||||
|
|
@ -111,9 +111,9 @@ public:
|
|||
// old shared area is deleted.
|
||||
// The implementation must ensure the parent does not crash even if the child's internal
|
||||
// structures are corrupt, including but not limited to size mismatch.
|
||||
// This function is not thread safe.
|
||||
// This function is not thread safe. Only one thread per side may enter.
|
||||
// It is implementation defined which processes are charged for these bytes. It could be parent,
|
||||
// child, a little of each, both, or something weirder.
|
||||
// child, a little of each, both, neither, or something weirder.
|
||||
void* shalloc(int index, size_t bytes);
|
||||
|
||||
//Convenience function, just calls the above.
|
||||
|
|
@ -126,7 +126,7 @@ public:
|
|||
void set_fopen_fallback(function<intptr_t(const char * path, bool write)> callback); // Child only.
|
||||
|
||||
//Clones a file handle into the child. The handle remains open in the parent. The child may get another ID.
|
||||
//Like shalloc(), neither process returns until the other enters.
|
||||
//Like shalloc(), both processes are allowed to sleep until the other enters.
|
||||
void give_fd(intptr_t fd); // Parent only.
|
||||
intptr_t accept_fd(); // Child only.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,50 +1,251 @@
|
|||
#include "serialize.h"
|
||||
#include "test.h"
|
||||
#include <string.h>
|
||||
#include "bml.h"
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
struct serializable_test {
|
||||
int a;
|
||||
int b;
|
||||
/*
|
||||
|
||||
onserialize() {
|
||||
SER(a);
|
||||
SER(b) SER_HEX;
|
||||
}
|
||||
{ a; b; }
|
||||
a=1
|
||||
b=2
|
||||
|
||||
{ a={ b; c; } d; }
|
||||
a
|
||||
b=1
|
||||
c=2
|
||||
d=3
|
||||
|
||||
*/
|
||||
|
||||
#define SERIALIZE_CORE(member) s(STR(member), member);
|
||||
#define SERIALIZE(...) template<typename T> void serialize(T& s) { PPFOREACH(SERIALIZE_CORE, __VA_ARGS__); }
|
||||
|
||||
class bmlserialize_impl {
|
||||
bmlwriter w;
|
||||
template<typename T> friend string bmlserialize(T& item);
|
||||
|
||||
public:
|
||||
|
||||
static const bool serializing = true;
|
||||
|
||||
template<typename T> void operator()(cstring name, T& item)
|
||||
{
|
||||
w.enter(name, "");
|
||||
item.serialize(*this);
|
||||
w.exit();
|
||||
}
|
||||
|
||||
#define LEAF(T) void operator()(cstring name, T& item) { w.node(name, tostring(item)); }
|
||||
LEAF(char);
|
||||
LEAF(int);
|
||||
LEAF(unsigned int);
|
||||
LEAF(bool);
|
||||
LEAF(float);
|
||||
LEAF(time_t);
|
||||
#undef LEAF
|
||||
};
|
||||
|
||||
class serializer_test : public serializer_base<serializer_test> {
|
||||
public:
|
||||
int phase;
|
||||
template<typename T> string bmlserialize(T& item)
|
||||
{
|
||||
bmlserialize_impl s;
|
||||
item.serialize(s);
|
||||
return s.w.finish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
class bmlunserialize_impl {
|
||||
bmlparser p;
|
||||
int pdepth = 0;
|
||||
|
||||
template<typename T>
|
||||
void serialize(const char * name, T& member, const serialize_opts& opts)
|
||||
int thisdepth = 0;
|
||||
cstring thisnode;
|
||||
cstring thisval;
|
||||
bool matchagain;
|
||||
|
||||
bmlparser::event event()
|
||||
{
|
||||
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++;
|
||||
bmlparser::event ret = p.next();
|
||||
if (ret.action == bmlparser::enter) pdepth++;
|
||||
if (ret.action == bmlparser::exit) pdepth--;
|
||||
if (ret.action == bmlparser::finish) pdepth=-2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skipchildren()
|
||||
{
|
||||
while (pdepth > thisdepth) event();
|
||||
}
|
||||
|
||||
bmlunserialize_impl(cstring bml) : p(bml) {}
|
||||
template<typename T> friend T bmlunserialize(cstring bml);
|
||||
|
||||
template<typename T> void item(T& out)
|
||||
{
|
||||
while (pdepth >= thisdepth)
|
||||
{
|
||||
bmlparser::event ev = event();
|
||||
if (ev.action == bmlparser::enter)
|
||||
{
|
||||
thisdepth++;
|
||||
thisnode = ev.name;
|
||||
thisval = ev.value;
|
||||
do {
|
||||
matchagain = false;
|
||||
out.serialize(*this);
|
||||
} while (matchagain);
|
||||
thisdepth--;
|
||||
skipchildren();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void next()
|
||||
{
|
||||
matchagain = false;
|
||||
|
||||
if (pdepth >= thisdepth)
|
||||
{
|
||||
thisdepth--;
|
||||
skipchildren();
|
||||
|
||||
bmlparser::event ev = event();
|
||||
if (ev.action == bmlparser::enter)
|
||||
{
|
||||
matchagain = true;
|
||||
thisnode = ev.name;
|
||||
thisval = ev.value;
|
||||
}
|
||||
|
||||
thisdepth++;
|
||||
}
|
||||
}
|
||||
|
||||
#define LEAF(T) void item(T& out) { out = fromstring<T>(thisval); }
|
||||
LEAF(char);
|
||||
LEAF(int);
|
||||
LEAF(unsigned int);
|
||||
LEAF(bool);
|
||||
LEAF(float);
|
||||
LEAF(time_t);
|
||||
#undef LEAF
|
||||
|
||||
public:
|
||||
|
||||
static const bool serializing = false;
|
||||
|
||||
template<typename T> void operator()(cstring name, T& out)
|
||||
{
|
||||
while (thisnode == name) // this should be a loop, in case of documents like 'foo bar=1 bar=2 bar=3'
|
||||
{
|
||||
item(out);
|
||||
thisnode = "";
|
||||
next();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test()
|
||||
template<typename T> T bmlunserialize(cstring bml)
|
||||
{
|
||||
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;
|
||||
T out{};
|
||||
bmlunserialize_impl s(bml);
|
||||
s.item(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
//test BML serialization - not sure which file that belongs in, so just pick one
|
||||
#include "bml.h"
|
||||
|
||||
|
||||
#ifdef ARLIB_TEST
|
||||
struct ser1 {
|
||||
int a;
|
||||
int b;
|
||||
|
||||
SERIALIZE(a, b);
|
||||
};
|
||||
|
||||
struct ser2 {
|
||||
ser1 c;
|
||||
ser1 d;
|
||||
|
||||
SERIALIZE(c, d);
|
||||
};
|
||||
|
||||
struct ser3 {
|
||||
int a;
|
||||
int b;
|
||||
int c;
|
||||
int d;
|
||||
int e;
|
||||
int f;
|
||||
int g;
|
||||
int h;
|
||||
|
||||
SERIALIZE(a, b, c, d, e, f, g, h);
|
||||
};
|
||||
|
||||
struct ser4 {
|
||||
ser3 mem;
|
||||
int count = 0;
|
||||
template<typename T> void serialize(T& s) { mem.serialize(s); count++; }
|
||||
};
|
||||
|
||||
test()
|
||||
{
|
||||
{
|
||||
ser1 item;
|
||||
item.a = 1;
|
||||
item.b = 2;
|
||||
|
||||
assert_eq(bmlserialize(item), "a=1\nb=2");
|
||||
}
|
||||
|
||||
{
|
||||
ser2 item;
|
||||
item.c.a = 1;
|
||||
item.c.b = 2;
|
||||
item.d.a = 3;
|
||||
item.d.b = 4;
|
||||
assert_eq(bmlserialize(item), "c a=1 b=2\nd a=3 b=4");
|
||||
}
|
||||
}
|
||||
|
||||
test()
|
||||
{
|
||||
{
|
||||
ser1 item = bmlunserialize<ser1>("a=1\nb=2");
|
||||
assert_eq(item.a, 1);
|
||||
assert_eq(item.b, 2);
|
||||
}
|
||||
|
||||
{
|
||||
ser2 item = bmlunserialize<ser2>("c a=1 b=2\nd a=3 b=4");
|
||||
assert_eq(item.c.a, 1);
|
||||
assert_eq(item.c.b, 2);
|
||||
assert_eq(item.d.a, 3);
|
||||
assert_eq(item.d.b, 4);
|
||||
}
|
||||
|
||||
//the system should not be order-sensitive
|
||||
{
|
||||
ser2 item = bmlunserialize<ser2>("d b=4 a=3\nc a=1 b=2");
|
||||
assert_eq(item.c.a, 1);
|
||||
assert_eq(item.c.b, 2);
|
||||
assert_eq(item.d.a, 3);
|
||||
assert_eq(item.d.b, 4);
|
||||
}
|
||||
|
||||
//in case of dupes, last one should win; extraneous nodes should be cleanly ignored
|
||||
{
|
||||
ser1 item = bmlunserialize<ser1>("a=1\nb=2\nq=0\na=3\na=4");
|
||||
assert_eq(item.a, 4);
|
||||
assert_eq(item.b, 2);
|
||||
}
|
||||
|
||||
//the system is allowed to loop, but only if there's bogus or extraneous nodes
|
||||
//we want O(n) runtime for a clean document, so ensure no looping
|
||||
//this includes missing and duplicate elements, both of which are possible for serialized arrays
|
||||
{
|
||||
ser4 item = bmlunserialize<ser4>("a=1\nb=2\nd=4\ne=5\ne=5\nf=6");
|
||||
assert_eq(item.count, 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,87 +1,3 @@
|
|||
#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__)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,26 @@ test()
|
|||
assert_eq(a, "1-78-12345678");
|
||||
}
|
||||
|
||||
{
|
||||
//ensure outline->outline also works
|
||||
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;
|
||||
|
|
@ -58,5 +78,25 @@ test()
|
|||
assert_eq(b, "12345678123456781234567812345678");
|
||||
}
|
||||
|
||||
return true;
|
||||
{
|
||||
string a = "1abc1de1fgh1";
|
||||
assert_eq(a.replace("1", ""), "abcdefgh");
|
||||
assert_eq(a.replace("1", "@"), "@abc@de@fgh@");
|
||||
assert_eq(a.replace("1", "@@"), "@@abc@@de@@fgh@@");
|
||||
}
|
||||
|
||||
{
|
||||
//this has thrown valgrind errors due to derpy allocations
|
||||
string a = "abcdefghijklmnopqrstuvwxyz";
|
||||
string b = a; // needs an extra reference
|
||||
a += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
assert_eq(a, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
}
|
||||
|
||||
{
|
||||
//this has also crashed, due to unshare() not respecting m_owning=false
|
||||
cstring a = "aaaaaaaaaaaaaaaa";
|
||||
a[0] = 'b';
|
||||
assert_eq(a, "baaaaaaaaaaaaaaa");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,7 +171,8 @@ public:
|
|||
}
|
||||
|
||||
//the sizes can be 0 if you want to
|
||||
//sizes are how many characters fit in the string, including the NUL
|
||||
//sizes are how many characters fit in the string, excluding the NUL
|
||||
//always allocates, doesn't try to inline
|
||||
static char* alloc(char* prev, uint32_t prevsize, uint32_t newsize)
|
||||
{
|
||||
if (prevsize==0)
|
||||
|
|
@ -200,7 +201,7 @@ public:
|
|||
--*refcount;
|
||||
|
||||
char* ptr = malloc(bytes_for(newsize));
|
||||
memcpy(ptr, prev, min(prevsize, newsize));
|
||||
memcpy(ptr, prev-sizeof(int), min(prevsize, newsize));
|
||||
*(int*)ptr = 1;
|
||||
return ptr+sizeof(int);
|
||||
}
|
||||
|
|
@ -212,9 +213,14 @@ public:
|
|||
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);
|
||||
char* prevdat = m_data;
|
||||
m_data = alloc(NULL,0, m_len);
|
||||
memcpy(m_data, prevdat, m_len);
|
||||
m_data[m_len] = '\0';
|
||||
|
||||
if (m_owning) alloc(prevdat,m_len, 0);
|
||||
|
||||
m_owning = true;
|
||||
m_nul = true;
|
||||
}
|
||||
|
||||
|
|
@ -233,7 +239,7 @@ public:
|
|||
break;
|
||||
case 1: // small->big
|
||||
{
|
||||
char* newptr = alloc(NULL,0, newlen+1);
|
||||
char* newptr = alloc(NULL,0, newlen);
|
||||
memcpy(newptr, m_inline, max_inline);
|
||||
newptr[newlen] = '\0';
|
||||
m_data = newptr;
|
||||
|
|
@ -257,7 +263,7 @@ public:
|
|||
break;
|
||||
case 3: // big->big
|
||||
{
|
||||
m_data = alloc(m_data,m_len, newlen+1);
|
||||
m_data = alloc(m_data,m_len, newlen);
|
||||
m_data[newlen] = '\0';
|
||||
m_len = newlen;
|
||||
}
|
||||
|
|
@ -286,7 +292,7 @@ public:
|
|||
{
|
||||
return ptr();
|
||||
}
|
||||
bool ntterm() const
|
||||
bool hasnt() const // has nul terminator, no missing apostrophe here
|
||||
{
|
||||
return (inlined() || m_nul);
|
||||
}
|
||||
|
|
@ -448,7 +454,7 @@ public:
|
|||
return ptr();
|
||||
}
|
||||
|
||||
void replace(int32_t pos, int32_t len, const string& newdat)
|
||||
void replace(int32_t pos, int32_t len, const string& newdat) // const string& is ugly, but cstring isn't declared yet.
|
||||
{
|
||||
//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
|
||||
|
|
@ -481,6 +487,47 @@ public:
|
|||
memcpy(ptr()+pos, newdat.ptr(), newlength);
|
||||
}
|
||||
|
||||
string replace(const string& in, const string& out)
|
||||
{
|
||||
size_t outlen = length();
|
||||
|
||||
if (in.length() != out.length())
|
||||
{
|
||||
char* haystack = ptr();
|
||||
char* haystackend = ptr()+length();
|
||||
while (true)
|
||||
{
|
||||
haystack = (char*)memmem(haystack, haystackend-haystack, in.ptr(), in.length());
|
||||
if (!haystack) break;
|
||||
|
||||
haystack += in.length();
|
||||
outlen += out.length(); // outlen-inlen is type uint - bad idea
|
||||
outlen -= in.length();
|
||||
}
|
||||
}
|
||||
|
||||
string ret;
|
||||
char* retptr = ret.construct(outlen);
|
||||
|
||||
char* prev = ptr();
|
||||
char* myend = ptr()+length();
|
||||
while (true)
|
||||
{
|
||||
char* match = (char*)memmem(prev, myend-prev, in.ptr(), in.length());
|
||||
if (!match) break;
|
||||
|
||||
memcpy(retptr, prev, match-prev);
|
||||
retptr += match-prev;
|
||||
prev = match + in.length();
|
||||
|
||||
memcpy(retptr, out.ptr(), out.length());
|
||||
retptr += out.length();
|
||||
}
|
||||
memcpy(retptr, prev, myend-prev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
string& operator+=(const char * right)
|
||||
{
|
||||
append(right, strlen(right));
|
||||
|
|
@ -492,6 +539,12 @@ public:
|
|||
append(right.ptr(), right.length());
|
||||
return *this;
|
||||
}
|
||||
|
||||
string& operator+=(char right)
|
||||
{
|
||||
append(&right, 1);
|
||||
return *this;
|
||||
}
|
||||
#endif
|
||||
|
||||
//Shared between all string implementations.
|
||||
|
|
@ -513,15 +566,15 @@ public:
|
|||
operator const char * () const { return data(); }
|
||||
|
||||
private:
|
||||
class charref {
|
||||
class charref : nocopy {
|
||||
friend class string;
|
||||
string* parent;
|
||||
uint32_t index;
|
||||
charref(string* parent, uint32_t index) : parent(parent), index(index) {}
|
||||
|
||||
public:
|
||||
charref& operator=(char ch) { parent->setchar(index, ch); return *this; }
|
||||
operator char() { return parent->getchar(index); }
|
||||
|
||||
charref(string* parent, uint32_t index) : parent(parent), index(index) {}
|
||||
};
|
||||
friend class charref;
|
||||
|
||||
|
|
@ -541,6 +594,7 @@ public:
|
|||
return string(data()+start, end-start);
|
||||
}
|
||||
inline cstring csubstr(int32_t start, int32_t end) const;
|
||||
inline bool contains(cstring other) const;
|
||||
};
|
||||
|
||||
static inline bool string_eq(const char * left, uint32_t leftlen, const char * right, uint32_t rightlen)
|
||||
|
|
@ -561,6 +615,10 @@ inline string operator+(string&& left, const string& right) { left+=right; retur
|
|||
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; }
|
||||
|
||||
inline string operator+(string&& left, char right) { left+=right; return left; }
|
||||
inline string operator+(const string& left, char right) { string ret=left; ret+=right; return ret; }
|
||||
inline string operator+(char left, const string& right) { string ret; ret[0]=left; ret+=right; return ret; }
|
||||
|
||||
class cstring : public string {
|
||||
friend class string;
|
||||
public:
|
||||
|
|
@ -586,6 +644,11 @@ inline cstring string::csubstr(int32_t start, int32_t end) const
|
|||
else return cstring(nt()+start, end-start, (m_nul && (uint32_t)end == m_len));
|
||||
}
|
||||
|
||||
inline bool string::contains(cstring other) const
|
||||
{
|
||||
return memmem(this->ptr(), this->length(), other.ptr(), other.length());
|
||||
}
|
||||
|
||||
//TODO
|
||||
class wstring : public string {
|
||||
mutable uint32_t pos_bytes;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,15 @@ 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; }
|
||||
template<typename T> inline T fromstring(cstring s);
|
||||
template<> inline string fromstring<string>(cstring s) { return s; }
|
||||
template<> inline cstring fromstring<cstring>(cstring s) { return s; }
|
||||
//no const char *, their lifetime is unknowable
|
||||
template<> inline int fromstring<int>(string s) { return atoi(s); }
|
||||
|
||||
template<> inline int fromstring<int>(cstring s) { return strtol(s, NULL, 0); }
|
||||
template<> inline long int fromstring<long int>(cstring s) { return strtol(s, NULL, 0); }
|
||||
template<> inline unsigned int fromstring<unsigned int>(cstring s) { return strtoul(s, NULL, 0); }
|
||||
template<> inline float fromstring<float>(cstring s) { return strtod(s, NULL); }
|
||||
|
||||
template<> inline char fromstring<char>(cstring s) { return s[0]; }
|
||||
template<> inline bool fromstring<bool>(cstring s) { return s=="true"; }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
#ifdef ARLIB_TEST
|
||||
#include "test.h"
|
||||
#include "array.h"
|
||||
|
||||
struct testlist {
|
||||
bool(*func)();
|
||||
void(*func)();
|
||||
const char * name;
|
||||
testlist* next;
|
||||
};
|
||||
|
||||
static testlist* g_testlist;
|
||||
|
||||
_testdecl::_testdecl(bool(*func)(), const char * name)
|
||||
_testdecl::_testdecl(void(*func)(), const char * name)
|
||||
{
|
||||
testlist* next = malloc(sizeof(testlist));
|
||||
next->func = func;
|
||||
|
|
@ -18,27 +19,80 @@ _testdecl::_testdecl(bool(*func)(), const char * name)
|
|||
g_testlist = next;
|
||||
}
|
||||
|
||||
static bool thisfail;
|
||||
|
||||
static array<int> callstack;
|
||||
void _teststack_push(int line) { callstack.append(line); }
|
||||
void _teststack_pop() { callstack.resize(callstack.size()-1); }
|
||||
static string stack(int top)
|
||||
{
|
||||
string ret = " (line "+tostring(top);
|
||||
|
||||
for (int i=callstack.size();i>=0;i--)
|
||||
{
|
||||
ret += " from "+tostring(callstack[i]);
|
||||
}
|
||||
|
||||
return ret+")";
|
||||
}
|
||||
|
||||
static void _testfail(cstring why)
|
||||
{
|
||||
if (!thisfail) puts(why); // discard multiple failures from same test, they're probably caused by same thing
|
||||
thisfail = true;
|
||||
}
|
||||
|
||||
void _testfail(cstring why, int line)
|
||||
{
|
||||
_testfail(why+stack(line));
|
||||
}
|
||||
|
||||
void _testeqfail(cstring name, int line, cstring expected, cstring actual)
|
||||
{
|
||||
if (expected.contains("\n") || actual.contains("\n"))
|
||||
{
|
||||
_testfail("\nFailed assertion "+name+stack(line)+"\nexpected:\n"+expected+"\nactual:\n"+actual);
|
||||
}
|
||||
else
|
||||
{
|
||||
_testfail("\nFailed assertion "+name+stack(line)+": expected "+expected+", got "+actual);
|
||||
}
|
||||
}
|
||||
|
||||
#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};
|
||||
|
||||
//flip list backwards
|
||||
//order of static initializers is implementation defined, but this makes output better under gcc
|
||||
testlist* test = g_testlist;
|
||||
g_testlist = NULL;
|
||||
while (test)
|
||||
{
|
||||
testlist* next = test->next;
|
||||
test->next = g_testlist;
|
||||
g_testlist = test;
|
||||
test = next;
|
||||
}
|
||||
|
||||
test = g_testlist;
|
||||
while (test)
|
||||
{
|
||||
testlist* next = test->next;
|
||||
printf("Testing %s...", test->name);
|
||||
bool pass = test->func();
|
||||
count[pass]++;
|
||||
if (pass) puts(" pass");
|
||||
fflush(stdout);
|
||||
thisfail = false;
|
||||
test->func();
|
||||
count[thisfail]++;
|
||||
if (!thisfail) puts(" pass");
|
||||
free(test);
|
||||
test = next;
|
||||
}
|
||||
printf("Passed %i, failed %i\n", count[1], count[0]);
|
||||
printf("Passed %i, failed %i\n", count[0], count[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
test()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
test() {}
|
||||
test() {}
|
||||
#endif
|
||||
|
|
|
|||
26
arlib/test.h
26
arlib/test.h
|
|
@ -8,27 +8,35 @@
|
|||
|
||||
class _testdecl {
|
||||
public:
|
||||
_testdecl(bool(*func)(), const char * name);
|
||||
_testdecl(void(*func)(), const char * name);
|
||||
};
|
||||
|
||||
void _testfail(cstring name, int line);
|
||||
void _testeqfail(cstring name, int line, cstring expected, cstring actual);
|
||||
|
||||
void _teststack_push(int line);
|
||||
void _teststack_pop();
|
||||
|
||||
#define TESTFUNCNAME JOIN(_testfunc, __LINE__)
|
||||
#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)
|
||||
static void TESTFUNCNAME(); \
|
||||
static _testdecl JOIN(_testdecl, __LINE__)(TESTFUNCNAME, __FILE__ ":" STR(__LINE__)); \
|
||||
static void TESTFUNCNAME()
|
||||
#define assert(x) do { if (!(x)) { _testfail("\nFailed assertion " #x, __LINE__); return; } } 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; \
|
||||
_testeqfail(#x " == " #y, __LINE__, tostring(y), tostring(x)); \
|
||||
return; \
|
||||
} \
|
||||
} while(0)
|
||||
#define testcall(x) _teststack_push(__LINE__),x,_teststack_pop()
|
||||
|
||||
#else
|
||||
|
||||
#define test() static bool MAYBE_UNUSED _testfunc_##__LINE__()
|
||||
#define test() static void MAYBE_UNUSED JOIN(_testfunc_, __LINE__)()
|
||||
#define assert(x)
|
||||
#define assert_eq(x,y)
|
||||
#define testcall(x) x
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -18,17 +18,17 @@ multiple patches -> error
|
|||
anything else -> error
|
||||
|
||||
allow -a and -c for overriding this
|
||||
allow applying IPS, UPS and BPS, but only create BPS
|
||||
allow applying IPS, UPS and BPS, but only create BPS in GUI; CLI supports everything (maybe an advanced mode that exposes everything?)
|
||||
only use the delta creator; moremem is worthless, remove it
|
||||
|
||||
also need this feature:
|
||||
also need this new feature:
|
||||
--romhead=512 - discard 512 leading bytes in the ROM before sending to patcher
|
||||
--patchhead=512 - prepend 512 leading 00s before patching, discard afterwards
|
||||
--outhead=512 - prepend 512 leading 00s after patching
|
||||
if both romhead and patchhead are nonzero, replace the leading min(romhead,patchhead) 00s with data from the rom; same for outhead
|
||||
if patchhead or romhead isn't set, it's 0
|
||||
if outhead is not set, it's romhead
|
||||
if none are set, romsize modulo 32768 is 512, and file extension is sfc or smc, set romhead to 512
|
||||
if none are set, romsize modulo 32768 is 512, and file extension is sfc or smc, set rom/outhead to 512 and patchhead to 0
|
||||
if patching fails, retry with all headers 0 and throw a warning
|
||||
*/
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user