#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* Including the c file to give access to its static functions for testing. */
#include "freekdl.c"
int checkUtf8Read(const char *str, size_t strLen, uint32_t expectedValue, size_t expectedSize)
{
uint32_t value;
size_t size;
if (!readUtf8Char(str, strLen, &value, &size)) {
printf("checkUtf8 for \"%s\" returned false.\n", str);
return 0;
}
if (value != expectedValue) {
printf("checkUtf8 for \"%s\" failed. value=0x%x != expected=0x%x\n", str, value, expectedValue);
return 0;
}
if (size != expectedSize) {
printf("checkUtf8 for \"%s\" failed. size=0x%zx != expected=0x%zx\n", str, size, expectedSize);
return 0;
}
return 1;
}
int checkUtf8Write(uint32_t input, const char *expected, size_t expectedSize)
{
char buf[4];
size_t size;
if (!writeUtf8Char(buf, sizeof buf, input, &size)) {
printf("checkUtf8 for 0x%x returned false.\n", input);
return 0;
}
if (memcmp(buf, expected, expectedSize < size ? expectedSize : size)) {
printf("checkUtf8 for 0x%x failed. value=0x%hhx 0x%hhx 0x%hhx 0x%hhx != expected=0x%hhx 0x%hhx 0x%hhx 0x%hhx\n",
input,
(unsigned char)(size >= 1 ? buf[0] : 0),
(unsigned char)(size >= 2 ? buf[1] : 0),
(unsigned char)(size >= 3 ? buf[2] : 0),
(unsigned char)(size >= 4 ? buf[3] : 0),
(unsigned char)(expectedSize >= 1 ? expected[0] : 0),
(unsigned char)(expectedSize >= 2 ? expected[1] : 0),
(unsigned char)(expectedSize >= 3 ? expected[2] : 0),
(unsigned char)(expectedSize >= 4 ? expected[3] : 0));
return 0;
}
if (size != expectedSize) {
printf("checkUtf8 for 0x%x failed. size=0x%zx != expected=0x%zx\n", input, size, expectedSize);
return 0;
}
return 1;
}
int testUtf8(void)
{
return
checkUtf8Read("hello", 5, 'h', 1) &&
checkUtf8Read("\0hello", 6, '\0', 1) &&
checkUtf8Read("β test", 7, 0x03B2, 2) &&
checkUtf8Read("Җ test", 7, 0x0496, 2) &&
checkUtf8Read("\xef\xbb\xbf test", 8, 0xFEFF, 3) &&
checkUtf8Read("🙈", 4, 0x1F648, 4);
checkUtf8Write('h', "h", 1) &&
checkUtf8Write('\0', "\0", 1) &&
checkUtf8Write(0x03B2, "β", 2) &&
checkUtf8Write(0x0496, "Җ", 2) &&
checkUtf8Write(0xFEFF, "\xef\xbb\xbe", 3) &&
checkUtf8Write(0x1F648, "🙈", 4);
}
int testPeekCharSuccess(struct fkdl_cursor *cursor, uint32_t expected, size_t expectedSize)
{
struct fkdl_cursor before = *cursor;
uint32_t out = 0;
size_t outSize = 0;
struct fkdl_error error = {0};
if (!peekChar(cursor, &out, &outSize, &error)) {
printf("peekChar for \"%s\" returned false.\n", cursor->input);
return 0;
}
struct fkdl_cursor after = *cursor;
if (memcmp(&before, &after, sizeof before)) {
printf("peekChar changed the state of the cursor\n");
return 0;
}
if (out != expected) {
printf("peekChar for \"%s\" output '%c' not '%c'\n", cursor->input, out, expected);
return 0;
}
if (outSize != expectedSize) {
printf("peekChar for \"%s\" output size %zd not %zd\n", cursor->input, outSize, expectedSize);
return 0;
}
return 1;
}
int testPeekChar_Start(void)
{
const char test[] = "this is a test cursor";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.line = 1,
.prevWhitespace = FKDL_FALSE,
};
return testPeekCharSuccess(&cursor, 't', 1);
}
int testPeekChar_Middle(void)
{
const char test[] = "this is a test cursor";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 8,
.line = 1,
.column = 8,
.prevWhitespace = FKDL_TRUE,
};
return testPeekCharSuccess(&cursor, 'a', 1);
}
int testPeekChar_End(void)
{
const char test[] = "this is a test cursor";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = sizeof test,
.line = 1,
.column = sizeof test,
.prevWhitespace = FKDL_TRUE,
};
uint32_t out = 0;
size_t outSize = 0;
struct fkdl_error error = {0};
if (peekChar(&cursor, &out, &outSize, &error)) {
printf("peekChar for end returned true.\n");
return 0;
}
if (error.column != 22) {
printf("peekChar for for error set error.column to %zd not %zd\n", error.column, (size_t)22);
return 0;
}
if (error.line != 1) {
printf("peekChar for for error set error.column to %zd not %zd\n", error.line, (size_t)1);
return 0;
}
if (error.index != 22) {
printf("peekChar for for error set error.index to %zd not %zd\n", error.index, (size_t)22);
return 0;
}
return 1;
}
int testPeekChar(void)
{
return
testPeekChar_Start() &&
testPeekChar_Middle() &&
testPeekChar_End();
}
int testPeekWordCase(struct fkdl_cursor *cursor, const char *word, int shouldBeTrue)
{
struct fkdl_cursor before = *cursor;
if (shouldBeTrue != peekWord(cursor, word)) {
printf("For \"%s\" and \"%s\" peekWord returned %d not %d.\n",
cursor->input + cursor->index,
word,
!shouldBeTrue, shouldBeTrue);
return 0;
}
struct fkdl_cursor after = *cursor;
if (memcmp(&before, &after, sizeof before)) {
printf("peekWord changed the state of the cursor\n");
return 0;
}
return 1;
}
int testPeekStrCase(struct fkdl_cursor *cursor, const char *str, int shouldBeTrue)
{
struct fkdl_cursor before = *cursor;
if (shouldBeTrue != peekStr(cursor, str)) {
printf("For \"%s\" and \"%s\" peekStr returned %d not %d.\n",
cursor->input + cursor->index,
str,
!shouldBeTrue, shouldBeTrue);
return 0;
}
struct fkdl_cursor after = *cursor;
if (memcmp(&before, &after, sizeof before)) {
printf("peekStr changed the state of the cursor\n");
return 0;
}
return 1;
}
int testPeekStr(void)
{
const char test[] = "this is a test cursor";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 0,
.line = 1,
.column = 0,
.prevWhitespace = FKDL_TRUE,
};
if (!testPeekStrCase(&cursor, "this", 1)) {
return 0;
}
if (!testPeekStrCase(&cursor, "fail", 0)) {
return 0;
}
cursor.index = 4;
if (!testPeekStrCase(&cursor, "is", 0)) {
return 0;
}
cursor.index = 10;
if (!testPeekStrCase(&cursor, "test", 1)) {
return 0;
}
cursor.index = 15;
if (!testPeekStrCase(&cursor, "cursor", 1)) {
return 0;
}
cursor.index = 17;
if (!testPeekStrCase(&cursor, "anything", 0)) {
return 0;
}
cursor.index = sizeof test;
if (!testPeekStrCase(&cursor, "anything", 0)) {
return 0;
}
return 1;
}
int testPeekWord(void)
{
const char test[] = "this is\ta test cursor\n";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 0,
.line = 1,
.column = 0,
.prevWhitespace = FKDL_TRUE,
};
if (!testPeekWordCase(&cursor, "this", 1)) {
return 0;
}
cursor.index = 4;
if (!testPeekWordCase(&cursor, "is", 0)) {
return 0;
}
cursor.index = 5;
if (!testPeekWordCase(&cursor, "is", 1)) {
return 0;
}
cursor.index = 10;
if (!testPeekWordCase(&cursor, "test", 1)) {
return 0;
}
cursor.index = 15;
if (!testPeekWordCase(&cursor, "cursor", 1)) {
return 0;
}
cursor.index = 17;
if (!testPeekWordCase(&cursor, "anything", 0)) {
return 0;
}
cursor.index = sizeof test;
if (!testPeekWordCase(&cursor, "anything", 0)) {
return 0;
}
return 1;
}
int testConsumeCharSuccess(
struct fkdl_cursor *cursor,
uint32_t expected,
size_t expectedSize,
int prevWhitespace)
{
struct fkdl_cursor before = *cursor;
uint32_t out = 0;
size_t outSize = 0;
struct fkdl_error error = {0};
if (!consumeChar(cursor, &out, &outSize, &error)) {
printf("consumeChar for \"%s\" returned false.\n", cursor->input);
return 0;
}
struct fkdl_cursor after = *cursor;
if (out != expected) {
printf("consumeChar for \"%s\" output '%c' not '%c'\n", cursor->input, out, expected);
return 0;
}
if (outSize != expectedSize) {
printf("consumeChar for \"%s\" output size %zd not %zd\n", cursor->input, outSize, expectedSize);
return 0;
}
if (after.index != before.index + expectedSize) {
printf("consumeChar changed index to %zd but expected %zd\n", after.index, before.index + expectedSize);
return 0;
}
if (after.column != before.column + expectedSize) {
printf("consumeChar changed index to %zd but expected %zd\n", after.index, before.index + expectedSize);
return 0;
}
if (after.prevWhitespace != prevWhitespace) {
printf("consumeChar set prevWhitespace to %d but expected %d\n", after.prevWhitespace, prevWhitespace);
return 0;
}
return 1;
}
int testConsumeChar(void)
{
const char test[] = "this Җ\0ok\n";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.line = 1,
.prevWhitespace = FKDL_FALSE,
};
return
testConsumeCharSuccess(&cursor, 't', 1, 0) &&
testConsumeCharSuccess(&cursor, 'h', 1, 0) &&
testConsumeCharSuccess(&cursor, 'i', 1, 0) &&
testConsumeCharSuccess(&cursor, 's', 1, 0) &&
testConsumeCharSuccess(&cursor, ' ', 1, 1) &&
testConsumeCharSuccess(&cursor, 0x496, 2, 0) &&
testConsumeCharSuccess(&cursor, 0, 1, 0) &&
testConsumeCharSuccess(&cursor, 'o', 1, 0) &&
testConsumeCharSuccess(&cursor, 'k', 1, 0) &&
testConsumeCharSuccess(&cursor, '\n', 1, 0);
}
int testConsumeNewline(void)
{
const char test[] =
"this\n"
"is\r\n"
"a test\r"
"of newline\n";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 0,
.line = 1,
.column = 0,
.prevWhitespace = FKDL_TRUE,
};
return
testConsumeCharSuccess(&cursor, 't', 1, 0) &&
testConsumeCharSuccess(&cursor, 'h', 1, 0) &&
testConsumeCharSuccess(&cursor, 'i', 1, 0) &&
testConsumeCharSuccess(&cursor, 's', 1, 0) &&
consumeNewline(&cursor, NULL) &&
cursor.line == 2 && cursor.column == 0 &&
cursor.prevWhitespace == 0 && 1 &&
testConsumeCharSuccess(&cursor, 'i', 1, 0) &&
testConsumeCharSuccess(&cursor, 's', 1, 0) &&
cursor.line == 2 && cursor.column == 2 &&
consumeNewline(&cursor, NULL) &&
cursor.line == 3 && cursor.column == 0 &&
cursor.prevWhitespace == 0 &&
testConsumeCharSuccess(&cursor, 'a', 1, 0);
}
int testConsumeSingleComment(void)
{
const char test[] =
"// this is a single comment\n"
"a//this is another\r\n"
"b";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 0,
.line = 1,
.column = 0,
.prevWhitespace = FKDL_TRUE,
};
consumeSingleComment(&cursor);
if (!testConsumeCharSuccess(&cursor, 'a', 1, 0)) {
return 0;
}
consumeSingleComment(&cursor);
if (!testConsumeCharSuccess(&cursor, 'b', 1, 0)) {
return 0;
}
return 1;
}
int testConsumeWhitspaceSuccess(struct fkdl_cursor *cursor)
{
if (!consumeWhitespace(cursor, NULL)) {
printf("consumeWhitespace for \"%s\" returned false\n", cursor->input + cursor->index);
return 0;
}
return 1;
}
int testConsumeWhitespace(void)
{
const char test[] =
"a b\t \tc /* comment */d"
"/* comment /* nesting */ works */e"
" \nf";
struct fkdl_cursor cursor = {
.input = test,
.inputLen = sizeof test,
.index = 0,
.line = 1,
.column = 0,
.prevWhitespace = FKDL_TRUE,
};
return
testConsumeCharSuccess(&cursor, 'a', 1, 0) &&
testConsumeWhitspaceSuccess(&cursor) &&
testConsumeCharSuccess(&cursor, 'b', 1, 0) &&
testConsumeWhitspaceSuccess(&cursor) &&
testConsumeCharSuccess(&cursor, 'c', 1, 0) &&
testConsumeWhitspaceSuccess(&cursor) &&
testConsumeCharSuccess(&cursor, 'd', 1, 0) &&
testConsumeWhitspaceSuccess(&cursor) &&
testConsumeCharSuccess(&cursor, 'e', 1, 0) &&
testConsumeWhitspaceSuccess(&cursor) &&
testConsumeCharSuccess(&cursor, '\n', 1, 0) &&
testConsumeCharSuccess(&cursor, 'f', 1, 0);
}
int testUnescapingLen(const char *str, size_t expected)
{
size_t out;
if (!unescapedStringLength(str, strlen(str), &out)) {
printf("failed to measure unescaped length of '%s'\n", str);
return 0;
}
if (out != expected) {
printf("unescapedStringLength for '%s' should be %zd but is %zd\n", str, expected, out);
return 0;
}
return 1;
}
int testUnescapingMatch(const char *before, const char *after)
{
char buf[256];
if (!unescapeString(buf, before, strlen(before))) {
printf("Failed to unescape '%s'\n", before);
return 0;
}
if (memcmp(buf, after, strlen(after))) {
printf("Unescaping of '%s' should be '%s' but is '%s'\n", before, after, buf);
return 0;
}
return 1;
}
int testUnescaping(void)
{
return
testUnescapingLen("simple", 6) &&
testUnescapingLen("two\\nlines", 3 + 1 + 5) &&
testUnescapingLen("one\\ttab", 3 + 1 + 3) &&
testUnescapingLen("one\\\\slash", 3 + 1 + 5) &&
testUnescapingLen("\\n", 1) &&
testUnescapingLen("\\u{20}", 1) &&
testUnescapingLen("\\u{3B2}", 2) &&
testUnescapingLen("\\u{feff}", 3) &&
testUnescapingLen("\\u{1f648}", 4) &&
testUnescapingMatch("simple", "simple") &&
testUnescapingMatch("two\\nlines", "two\nlines") &&
testUnescapingMatch("one\\ttab", "one\ttab") &&
testUnescapingMatch("one\\\\slash", "one\\slash") &&
testUnescapingMatch("\\n", "\n") &&
testUnescapingMatch("\\u{20}", " ") &&
testUnescapingMatch("\\u{3B2}", "β") &&
testUnescapingMatch("\\u{feff}", "\xef\xbb\xbf") &&
testUnescapingMatch("\\u{1f648}", "🙈");
}
const char *tokenType(enum fkdl_token_type type)
{
switch(type) {
case FKDL_TOK_NULL: return "NULL";
case FKDL_TOK_BOOL: return "BOOL";
case FKDL_TOK_IDENTIFIER: return "IDENTIFIER";
case FKDL_TOK_STRING: return "STRING";
case FKDL_TOK_RAW_STRING: return "RAW_STRING";
case FKDL_TOK_NUMBER: return "NUMBER";
case FKDL_TOK_TYPE_ANNOTATION: return "TYPE_ANNOTATION";
case FKDL_TOK_PROPERTY_EQUALS: return "PROPERTY_EQUALS";
case FKDL_TOK_NODE_TERMINATOR: return "NODE_TERMINATOR";
case FKDL_TOK_OPEN_BRACE: return "OPEN_BRACE";
case FKDL_TOK_CLOSE_BRACE: return "CLOSE_BRACE";
case FKDL_TOK_SLASHDASH: return "SLASHDASH";
default: return "UNKNOWN";
}
}
struct token_expectation {
enum fkdl_token_type type;
const char *text;
};
int testTokeniseSuccess(
const char *input,
size_t inputLen,
const struct token_expectation *expectations,
size_t expectationsLen)
{
struct fkdl_array tokens = {
.itemSize = sizeof(struct fkdl_token),
};
struct fkdl_error error;
struct fkdl_allocator *alloc = NULL;
fkdl_defaultAllocator(&alloc);
if (!tokenise(input, inputLen, &tokens, &error, alloc)) {
printf("fkdl_tokenise failed for: \"%s\"\n", input);
printf("error: %s index=%zd line=%zd column=%zd\n", error.message, error.index, error.line, error.column);
return 0;
}
if (expectationsLen != tokens.len) {
printf("fkdl_tokenise wrote %zd tokens. Expected %zd for \"%s\".\n",
tokens.len,
expectationsLen,
input);
return 0;
}
struct fkdl_token *outTokens = tokens.data;
size_t i, j;
for (i = 0; i < tokens.len; ++i) {
if (outTokens[i].type != expectations[i].type) {
printf("token[%zd].type = %s but expected %s\n",
i,
tokenType(outTokens[i].type),
tokenType(expectations[i].type));
return 0;
}
if (expectations[i].text) {
if (memcmp(input + outTokens[i].index, expectations[i].text, strlen(expectations[i].text))) {
printf("token text was \"");
for (j = 0; j < outTokens[i].length; ++j) {
putchar(input[outTokens[i].index + j]);
}
printf("\" expected \"%s\"\n", expectations[i].text);
return 0;
}
}
}
return 1;
}
int testConsumeHexCodeCase(const char *str, uint32_t expectedValue, int shouldReturnTrue)
{
struct fkdl_cursor cursor = {
.input = str,
.inputLen = strlen(str),
.line = 1,
};
uint32_t out = 0;
size_t outSize = 0;
struct fkdl_error error = {0};
if (shouldReturnTrue != consumeHexCode(&cursor, &out, &outSize, &error)) {
printf("For \"%s\" consumeHexCode returned %d not %d.\n",
str, !shouldReturnTrue, shouldReturnTrue);
return 0;
}
if (expectedValue != out) {
printf("For \"%s\" consumeHexCode read 0x%x not 0x%x.\n",
str, out, expectedValue);
return 0;
}
return 1;
}
static int testConsumeHexCode(void)
{
return testConsumeHexCodeCase("00cc00", 0x00cc00, 1) &&
testConsumeHexCodeCase("10", 0x10, 1) &&
testConsumeHexCodeCase("F", 0xf, 1) &&
testConsumeHexCodeCase("310}", 0x310, 1) &&
testConsumeHexCodeCase("garbage", 0x0, 0) &&
testConsumeHexCodeCase("cafefe!", 0xcafefe, 1);
}
int testIsRawStringStartCase(const char *str, int shouldReturnTrue)
{
struct fkdl_cursor cursor = {
.input = str,
.inputLen = strlen(str),
.line = 1,
};
if (shouldReturnTrue != isRawStringStart(&cursor)) {
printf("For \"%s\" isRawStringStart returned %d not %d.\n",
str, !shouldReturnTrue, shouldReturnTrue);
return 0;
}
return 1;
}
int testIsRawStringStart(void)
{
return testIsRawStringStartCase("no", 0) &&
testIsRawStringStartCase("'no'", 0) &&
testIsRawStringStartCase("\"no'", 0) &&
testIsRawStringStartCase("\\\"no'", 0) &&
testIsRawStringStartCase("r\"yes", 1) &&
testIsRawStringStartCase("r#\"yes", 1) &&
testIsRawStringStartCase("r######\"yes", 1) &&
testIsRawStringStartCase("r##########\"yes", 1) &&
testIsRawStringStartCase("r##########no", 0) &&
testIsRawStringStartCase("r##########'no", 0);
}
int testCountHashesCase(const char *str, size_t expected)
{
size_t count = countHashes(str, strlen(str));
if (count != expected) {
printf("For \"%s\" countHashes returned %zd not %zd.\n",
str, count, expected);
return 0;
}
return 1;
}
int testCountHashes(void)
{
return testCountHashesCase("foobar", 0) &&
testCountHashesCase("#one", 1) &&
testCountHashesCase("##two", 2) &&
testCountHashesCase("###", 3) &&
testCountHashesCase("####four####", 4) &&
testCountHashesCase("\"", 0) &&
testCountHashesCase("\\#", 0);
}
int testConsumeStringCase(const char *str, size_t offset, const char *body)
{
struct fkdl_cursor cursor = {
.input = str,
.inputLen = strlen(str),
.line = 1,
};
struct fkdl_token token;
struct fkdl_error error;
if (!consumeString(&cursor, &token, &error)) {
printf("consumeString failed for \"%s\" because: %s\n", str, error.message);
return 0;
}
if (token.index != offset) {
printf("consumeString length expected offset %zd but actually %zd\n", offset, token.index);
return 0;
}
if (memcmp(body, str + token.index, strlen(body))) {
printf("consumeString expected \"%s\" but pointed to: \"%s\"\n", body, str + token.index);
return 0;
}
if (token.length != strlen(body)) {
printf("consumeString length expected len %zd but actually %zd\n", strlen(body), token.length);
return 0;
}
return 1;
}
int testConsumeString(void)
{
return
testConsumeStringCase("\"simple\" other stuff", 1, "simple") &&
testConsumeStringCase("\"simple_\\\"escaped\" other stuff", 1, "simple_\\\"escaped") &&
testConsumeStringCase("r\"simple raw\" other", 2, "simple raw") &&
testConsumeStringCase("r###\" raw with hashes\"### other", 5, " raw with hashes") &&
testConsumeStringCase("r###\" raw \" hashes\"### other", 5, " raw \" hashes") &&
testConsumeStringCase("r#####\" raw ###\" hashes\"##### other", 7, " raw ###\" hashes") &&
testConsumeStringCase("r#####\"eof string\"#####", 7, "eof string");
}
int testBasicTokenise(void)
{
const char keywords[] = "true false null { } ;";
const struct token_expectation keywordTokens[] = {
{ .type = FKDL_TOK_BOOL, .text = "true" },
{ .type = FKDL_TOK_BOOL, .text = "false" },
{ .type = FKDL_TOK_NULL, .text = "null"},
{ .type = FKDL_TOK_OPEN_BRACE, .text = "{"},
{ .type = FKDL_TOK_NODE_TERMINATOR, .text = "}"},
{ .type = FKDL_TOK_CLOSE_BRACE, .text = "}"},
{ .type = FKDL_TOK_NODE_TERMINATOR, .text = ";"},
{ .type = FKDL_TOK_NODE_TERMINATOR}
};
if (!testTokeniseSuccess(keywords, sizeof keywords - 1,
keywordTokens, sizeof keywordTokens / sizeof keywordTokens[0])) {
return 0;
}
const char numbers[] = "1.5 0x10 0o7 0b10101 -5 +2 -2.5 1.5e17 2.5E-3";
const struct token_expectation numberTokens[] = {
{ .type = FKDL_TOK_NUMBER, .text = "1.5"},
{ .type = FKDL_TOK_NUMBER, .text = "0x10"},
{ .type = FKDL_TOK_NUMBER, .text = "0o7"},
{ .type = FKDL_TOK_NUMBER, .text = "0b10101"},
{ .type = FKDL_TOK_NUMBER, .text = "-5"},
{ .type = FKDL_TOK_NUMBER, .text = "+2"},
{ .type = FKDL_TOK_NUMBER, .text = "-2.5"},
{ .type = FKDL_TOK_NUMBER, .text = "1.5e17"},
{ .type = FKDL_TOK_NUMBER, .text = "2.5E-3"},
{ .type = FKDL_TOK_NODE_TERMINATOR}
};
if (!testTokeniseSuccess(numbers, sizeof numbers - 1,
numberTokens, sizeof numberTokens / sizeof numberTokens[0])) {
return 0;
}
const char strings[] = "\"abc\" \"abc \\\" escaped\" r\"def\" r#\"ghi\"# r###\"jkl \" ## \" \"### \"mno\"";
const struct token_expectation stringTokens[] = {
{ .type = FKDL_TOK_STRING, .text = "abc"},
{ .type = FKDL_TOK_STRING, .text = "abc \\\" escaped"},
{ .type = FKDL_TOK_RAW_STRING, .text = "def"},
{ .type = FKDL_TOK_RAW_STRING, .text = "ghi"},
{ .type = FKDL_TOK_RAW_STRING, .text = "jkl \" ## \" "},
{ .type = FKDL_TOK_STRING, .text = "mno"},
{ .type = FKDL_TOK_NODE_TERMINATOR}
};
if (!testTokeniseSuccess(strings, sizeof strings - 1,
stringTokens, sizeof stringTokens / sizeof stringTokens[0])) {
return 0;
}
return 1;
}
int testEscapedUnicode(void)
{
struct fkdl_allocator *alloc = NULL;
fkdl_defaultAllocator(&alloc);
const char input[] = "🙈 emoji 😁";
const char expected[] = "🙈 emoji 😁";
struct fkdl_string str = copyString(alloc, input, sizeof input);
if (!str.data) {
printf("copyString failed for unicode\n");
return 0;
}
if (strcmp(expected, str.data)) {
printf("copystring didn't match input. expected '%s' but got '%s'\n", expected, str.data);
return 0;
}
char output[32];
struct fkdl_writeCursor cursor = {
.output = output,
.outputLen = sizeof output,
.index = 0,
};
if (!writeEscapedString(&cursor, &str)) {
printf("writeEscapedString failed for unicode\n");
return 0;
}
if (strcmp(expected, output)) {
printf("writeEscapedString didn't match expected. expected '%s' but got '%s'\n", expected, output);
return 0;
}
return 1;
}
int testMixedTokenise(void)
{
const char keywords[] = "test \"str\"\n 1.5E2 r#\"mix\"ed\"# {100_000 true} null (int)2.7 foo=\"bar\"";
const struct token_expectation keywordTokens[] = {
{ .type = FKDL_TOK_IDENTIFIER, .text = "test" },
{ .type = FKDL_TOK_STRING, .text = "str" },
{ .type = FKDL_TOK_NODE_TERMINATOR, .text = "\n" },
{ .type = FKDL_TOK_NUMBER, .text = "1.5E2"},
{ .type = FKDL_TOK_RAW_STRING, .text = "mix\"ed"},
{ .type = FKDL_TOK_OPEN_BRACE, .text = "{"},
{ .type = FKDL_TOK_NUMBER, .text = "100_000"},
{ .type = FKDL_TOK_BOOL, .text = "true"},
{ .type = FKDL_TOK_NODE_TERMINATOR, .text = "}"},
{ .type = FKDL_TOK_CLOSE_BRACE, .text = "}"},
{ .type = FKDL_TOK_NULL, .text = "null"},
{ .type = FKDL_TOK_TYPE_ANNOTATION, .text = "int"},
{ .type = FKDL_TOK_NUMBER, .text = "2.7"},
{ .type = FKDL_TOK_IDENTIFIER, .text = "foo"},
{ .type = FKDL_TOK_PROPERTY_EQUALS, .text = "="},
{ .type = FKDL_TOK_STRING, .text = "bar"},
{ .type = FKDL_TOK_NODE_TERMINATOR}
};
if (!testTokeniseSuccess(keywords, sizeof keywords - 1,
keywordTokens, sizeof keywordTokens / sizeof keywordTokens[0])) {
return 0;
}
return 1;
}
int testReadBasicDocument(void)
{
const char docStr[] =
"title \"Hello World\" slug=(url)\"hello_world\" id=1234\n"
"subtitle \"My first document\"\n"
"body {\n"
" paragraph \"This is the first paragraph\"\n"
" paragraph \"This is the second paragraph\"\n"
" paragraph \"This is the third paragraph\"\n"
"}\n"
"footer text=\"\xf0\x9f\x98\x81 All rights reserved\"\n";
struct fkdl_document document;
struct fkdl_error error;
if (!fkdl_readDocument(docStr, strlen(docStr), &document, NULL, &error)) {
printf("fkdl_readDocument failed because: %s\n", error.message);
printf("index=%zd line=%zd column=%zd\n", error.index, error.line, error.column);
return 0;
}
if (document.nodesLen != 4) {
printf("expected 4 nodes, found: %zd\n", document.nodesLen);
return 0;
}
if (!document.nodes) {
printf("document had null nodes pointer\n");
return 0;
}
if (document.nodes[0].identifier.len != 5 ||
strcmp(document.nodes[0].identifier.data, "title")) {
printf("expected first node to be title\n");
return 0;
}
if (document.nodes[0].argumentsLen != 1) {
printf("expected first node to have 1 argument\n");
return 0;
}
if (document.nodes[0].arguments[0].type != FKDL_STRING ||
strcmp(document.nodes[0].arguments[0].as_string.data, "Hello World")) {
printf("expected first node's first argument to be 'Hello World'\n");
return 0;
}
if (document.nodes[0].propertiesLen != 2) {
printf("expected first node to have 2 properties, found %zd\n", document.nodes[0].propertiesLen);
return 0;
}
if (strcmp(document.nodes[0].properties[0].key.data, "id")) {
printf("expected first node's first property key to be id\n");
return 0;
}
if (document.nodes[0].properties[0].value.type != FKDL_NUMBER) {
printf("expected first node's first property value to be a string\n");
return 0;
}
if (strcmp(document.nodes[0].properties[0].value.as_string.data, "1234")) {
printf("expected first node's first property value to be 1234\n");
return 0;
}
if (strcmp(document.nodes[0].properties[1].key.data, "slug")) {
printf("expected first node's second property key to be slug\n");
return 0;
}
if (document.nodes[0].properties[1].value.annotation.len == 0 ||
strcmp(document.nodes[0].properties[1].value.annotation.data, "url")) {
printf("expected first node's second property value to have type url\n");
return 0;
}
if (document.nodes[0].properties[1].value.type != FKDL_STRING) {
printf("expected first node's second property value to be a string\n");
return 0;
}
if (strcmp(document.nodes[0].properties[1].value.as_string.data, "hello_world")) {
printf("expected first node's second property value to be hello_world\n");
return 0;
}
if (document.nodes[2].childrenLen != 3) {
printf("expected third node to have 3 children\n");
return 0;
}
size_t i;
for (i = 0; i < 3; ++i) {
if (strcmp("paragraph", document.nodes[2].children[i].identifier.data)) {
printf("expected third node's child %zd to be a paragraph\n", i);
return 0;
}
}
if (strcmp("footer", document.nodes[3].identifier.data)) {
printf("expected fourth node to be a footer but was: %s\n",
document.nodes[3].identifier.data);
return 0;
}
if (strcmp("😁 All rights reserved", document.nodes[3].properties[0].value.as_string.data)) {
printf("expected fourth node first property to have an emoji but was: %s\n",
document.nodes[3].properties[0].value.as_string.data);
return 0;
}
/* TODO check child nodes */
char output[8192];
if (!fkdl_writeDocument(output, sizeof output, NULL, &document)) {
printf("error writing document\n");
return 0;
}
/* printf("document:\n----\n%s----\n", output); */
return 1;
}
int testBeforeAfter(const char *before, const char *after) {
struct fkdl_document document;
struct fkdl_error error;
if (!fkdl_readDocument(before, strlen(before), &document, NULL, &error)) {
printf("fkdl_readDocument failed because: %s\n", error.message);
printf("index=%zd line=%zd column=%zd\n", error.index, error.line, error.column);
printf("text:\n%s\n", before);
return 0;
}
char output[8192];
if (!fkdl_writeDocument(output, sizeof output, NULL, &document)) {
printf("error writing document after reading: %s\n", before);
return 0;
}
if (strcmp(output, after)) {
printf("expected/actual did not match.\ninput:\n%s\n------\nexpected:\n%s\n------\nactual:\n%s\n------\n",
before, after, output);
return 0;
}
return 1;
}
int testExamples(void)
{
const char *cases[] = {
"node (int)1 (string)\"foo\" \"bar\"\n",
"node (int)1 (string)\"foo\" \"bar\"\n",
"\"foo bar\" 1 2 3\n",
"\"foo bar\" 1 2 3\n",
"\"foobar\" 1 2 3\n",
"foobar 1 2 3\n",
"foobar /-1 2 3\n",
"foobar 2 3\n",
"foo 1; /-bar 2; baz 3\n",
"foo 1\nbaz 3\n",
"foo { a 1; bar b=2 { c 3 };baz 7}final 0b101",
"foo {\n"
" a 1\n"
" bar b=2 {\n"
" c 3\n"
" }\n"
" baz 7\n"
"}\n"
"final 0b101\n",
/* examples from the kdl README */
"title \"Hello, World\"\n",
"title \"Hello, World\"\n",
"bookmarks 12 15 188 1234\n",
"bookmarks 12 15 188 1234\n",
"author \"Alex Monad\" email=\"alex@example.com\" active=true\n",
"author \"Alex Monad\" active=true email=\"alex@example.com\"\n",
"contents {\n"
" section \"First section\" {\n"
" paragraph \"This is the first paragraph\"\n"
" paragraph \"This is the second paragraph\"\n"
" }\n"
"}\n",
"contents {\n"
" section \"First section\" {\n"
" paragraph \"This is the first paragraph\"\n"
" paragraph \"This is the second paragraph\"\n"
" }\n"
"}\n",
"node1; node2; node3;",
"node1\nnode2\nnode3\n",
"node \"this\\nhas\\tescapes\"\n",
"node \"this\\nhas\\tescapes\"\n",
"other r\"C:\\Users\\zkat\\\"\n",
"other \"C:\\\\Users\\\\zkat\\\\\"\n",
"string \"my\nmultiline\nvalue\"\n",
"string \"my\\nmultiline\\nvalue\"\n",
"other-raw r#\"hello\"world\"#\n",
"other-raw \"hello\\\"world\"\n",
"num 1.234e-42\n",
"num 1.234e-42\n",
"my-hex 0xdeadbeef\n"
"my-octal 0o755\n"
"my-binary 0b10101101\n",
"my-hex 0xdeadbeef\n"
"my-octal 0o755\n"
"my-binary 0b10101101\n",
"bignum 1_000_000\n",
"bignum 1000000\n",
"// C style\n"
"\n"
"/*\n"
"C style muiltiline\n"
"*/\n"
"\n"
"tag /*foo=true*/ bar=false\n"
"\n"
"/*/*\n"
"hello\n"
"*/*/\n",
"tag bar=false\n",
"// This entire node and its children are all commented out.\n"
"/-mynode \"foo\" key=1 {\n"
" a\n"
" b\n"
" c\n"
"}\n"
"\n"
"mynode /-\"commented\" \"not commented\" /-key=\"value\" /-{\n"
" a\n"
" b\n"
"}\n",
"mynode \"not commented\"\n",
"numbers (u8)10 (i32)20 myfloat=(f32)1.5 {\n"
" strings (uuid)\"123e4567-e89b-12d3-a456-426614174000\" (date)\"2021-02-03\" filter=(regex)r\"$\\d+\"\n"
" (author)person name=\"Alex\"\n"
"}\n",
"numbers (u8)10 (i32)20 myfloat=(f32)1.5 {\n"
" strings (uuid)\"123e4567-e89b-12d3-a456-426614174000\" (date)\"2021-02-03\" filter=(regex)\"$\\\\d+\"\n"
" (author)person name=\"Alex\"\n"
"}\n",
"title \\ \n"
" \"Some title\"\n",
"title \"Some title\"\n",
"smile \"😁\"\n",
"smile \"😁\"\n",
"\"!@#$@$%Q#$%~@!40\" \"1.2.3\" \"!!!!!\"=true\n",
"!@#$@$%Q#$%~@!40 \"1.2.3\" !!!!!=true\n",
"foo123~!@#$%^&*.:'|?+ \"weeee\"\n",
"foo123~!@#$%^&*.:'|?+ \"weeee\"\n",
"ノード お名前=\"☜(゚ヮ゚☜)\"\n",
"ノード お名前=\"☜(゚ヮ゚☜)\"\n",
"foo bar=true \"baz\" quux=false 1 2 3\n",
"foo \"baz\" 1 2 3 bar=true quux=false\n",
/* test cases from kdl repo */
/* all_escapes */
"node \"\\\\ \\\" \\/ \\b \\f \\n \\r \\t \"\n",
"node \"\\\\ \\\" \\/ \\b \\f \\n \\r \\t \"\n",
/* all_node_fields */
"node \"arg\" prop=\"val\" {\n"
" inner_node\n"
"}\n",
"node \"arg\" prop=\"val\" {\n"
" inner_node\n"
"}\n",
/* arg_and_prop_same_Name */
"node \"arg\" arg=\"val\"\n",
"node \"arg\" arg=\"val\"\n",
/* arg_false_type */
"node (type)false\n",
"node (type)false\n",
/* arg_float_type */
"node (type)2.5\n",
"node (type)2.5\n",
/* arg_hex_type */
"node (type)0x10\n",
"node (type)0x10\n",
/* arg_null_type */
"node (type)null\n",
"node (type)null\n",
/* arg_raw_string_type */
"node (type)r\"str\"\n",
"node (type)\"str\"\n",
/* arg_string_type */
"node (type)\"str\"\n",
"node (type)\"str\"\n",
/* arg_true_type */
"node (type)true\n",
"node (type)true\n",
/* arg_type */
"node (type)\"arg\"\n",
"node (type)\"arg\"\n",
/* arg_zero_type */
"node (type)0\n",
"node (type)0\n",
/* asterisk_in_block_comment */
"node /* * */\n",
"node\n",
/* bare_emoji */
"😁 \"happy!\"\n",
"😁 \"happy!\"\n",
/* binary */
"node 0b10\n",
"node 0b10\n",
/* binary_trailing_underscore */
"node 0b10_\n",
"node 0b10\n",
/* binary_underscore */
"node 0b1_0\n",
"node 0b10\n",
/* block_comment */
"node /* comment */ \"arg\"\n",
"node \"arg\"\n",
/* block_comment_after_node */
"node /* hey */ \"arg\"\n",
"node \"arg\"\n",
/* block_comment_before_node */
"/* hey */ node\n",
"node\n",
/* block_comment_before_node_no_space */
"/* hey*/node\n",
"node\n",
/* block_comment_newline */
"/* hey */\n",
"",
/* boolean_arg */
"node false true\n",
"node false true\n",
/* boolean_prop */
"node prop1=true prop2=false\n",
"node prop1=true prop2=false\n",
/* commented_arg */
"node /-\"arg1\" \"arg2\"\n",
"node \"arg2\"\n",
/* commented_child */
"node \"arg\" /-{\n"
" inner_node\n"
"}\n",
"node \"arg\"\n",
/* commented_line */
"// node_1\n"
"node_2\n",
"node_2\n",
/* commented_node */
"/-node_1\n"
"node_2\n",
"node_2\n",
/* commented_prop */
"node /-prop=\"val\" \"arg\"\n",
"node \"arg\"\n",
/* crlf_between_nodes */
"node1\r\nnode2\n",
"node1\n"
"node2\n",
/* emoji */
"node \"😀\"\n",
"node \"😀\"\n",
/* empty */
"",
"",
/* empty_child */
"node {\n"
"}\n",
"node\n",
/* empty_child_same_line */
"node {}\n",
"node\n",
/* empty_child_whitespace */
"node {\n"
"\n"
" }\n",
"node\n",
/* empty_quoted_node_id */
"\"\" \"arg\"\n",
"\"\" \"arg\"\n",
/* empty_quoted_prop_key */
"node \"\"=\"empty\"\n",
"node \"\"=\"empty\"\n",
/* empty_string_arg */
"node \"\"\n",
"node \"\"\n",
/* esc_newline_in_string */
"node \"hello\\nworld\"\n",
"node \"hello\\nworld\"\n",
/* esc_unicode_in_string */
"node \"hello\\u{0a}world\"\n",
"node \"hello\\nworld\"\n",
/* escline */
"node \\\n"
" \"arg\"\n",
"node \"arg\"\n",
/* escline_comment_node */
"node1\n"
" \\// hey\n"
" node2\n",
"node1\n"
"node2\n",
/* escline_line_comment */
"node \\ // comment\n"
" \"arg\" \\// comment\n"
" \"arg2\n"
"\"\n",
"node \"arg\" \"arg2\\n\"\n",
/* escline_node */
"node1\n"
"node2\n",
"node1\n"
"node2\n",
/* false_prefix_in_bare_id */
"false_id\n",
"false_id\n",
/* false_prefix_in_prop_key */
"node false_id=1\n",
"node false_id=1\n",
/* hex */
"node 0xabcdef1234567890\n",
"node 0xabcdef1234567890\n",
/* hex_int - TODO force case? */
"node 0xABCDEF0123456789abcdef\n",
"node 0xABCDEF0123456789abcdef\n",
/* hex_int_underscores */
"node 0xABC_def_0123\n",
"node 0xABCdef0123\n",
/* hex_leading_zero - TODO remove leading zeroes */
"node 0x01\n",
"node 0x01\n",
/* int_multiple_underscores */
"node 1_2_3_4\n",
"node 1234\n",
/* just_block_comment */
"/* hey */\n",
"",
/* just_child */
"node {\n"
" inner_node \n"
"}\n",
"node {\n"
" inner_node\n"
"}\n",
/* just_newline */
"\n",
"",
/* just_node_id */
"node",
"node\n",
/* just_space */
" ",
"",
/* leading_newline */
"\n"
"node",
"node\n",
/* leading_zero_binary - TODO remove leading zeroes */
"node 0b01\n",
"node 0b01\n",
/* leading_zero_int - TODO remove leading zeroes */
"node 011\n",
"node 011\n",
/* leading_zero_oct - TODO remove leading zeroes */
"node 0o01\n",
"node 0o01\n",
/* multiline_comment */
"node /*\n"
"some\n"
"comments\n"
"*/ \"arg\"",
"node \"arg\"\n",
/* multiline_nodes */
"node \\\n"
" \"arg1\" \\// comment\n"
" \"arg2\"\n",
"node \"arg1\" \"arg2\"\n",
/* multiline_string */
"node \" hey\n"
"everyone\n"
"how goes?\n"
"\"",
"node \" hey\\neveryone\\nhow goes?\\n\"\n",
/* negative_exponent - TODO capitalise e */
"node 1.0e-10\n",
"node 1.0e-10\n",
/* negative float */
"node -1.0 key=-10.0\n",
"node -1.0 key=-10.0\n",
/* negative_int */
"node -10 prop=-15\n",
"node -10 prop=-15\n",
/* nested_block_comment */
"node /* hi /* there */ everyone */ \"arg\"\n",
"node \"arg\"\n",
/* nested_children */
"node1 {\n"
" node2 {\n"
" node\n"
" }\n"
"}\n",
"node1 {\n"
" node2 {\n"
" node\n"
" }\n"
"}\n",
/* nested comments */
"node /*/* nested */*/ \"arg\"",
"node \"arg\"\n",
/* nested_multiline_block_comments */
"node /*\n"
"hey /*\n"
"how's\n"
"*/\n"
" it going\n"
" */ \"arg\"\n"
" ",
"node \"arg\"\n",
/* newline_between_nodes */
"node1\nnode2",
"node1\n"
"node2\n",
/* newline_in_block_comment */
"node /* hey so\n"
"I was thinking\n"
"about newts */ \"arg\"",
"node \"arg\"\n",
/* no_decimal_exponent - TODO capitalise e and add +*/
"node 1e10",
"node 1e10\n",
/* node_false */
"node false\n",
"node false\n",
/* node_true */
"node true\n",
"node true\n",
/* node_type */
"(type)node\n",
"(type)node\n",
/* null_arg */
"node null\n",
"node null\n",
/* null_prefix_in_bare_id */
"null_id\n",
"null_id\n",
/* null_prefix_in_prop_key */
"node null_id=1\n",
"node null_id=1\n",
/* null_prop */
"node prop=null\n",
"node prop=null\n",
/* numeric_arg */
"node 15.7\n",
"node 15.7\n",
/* numeric_prop */
"node prop=10.0\n",
"node prop=10.0\n",
/* octal */
"node 0o76543210\n",
"node 0o76543210\n",
/* only_cr */
"\r",
"",
/* only_line_comment */
"// hi",
"",
/* only_line_comment_crlf */
"// hi\r\n",
"",
/* only_line_comment_newline */
"// hi\n",
"",
/* positive_exponent */
"node 1.0e+10\n",
"node 1.0e+10\n",
/* positive_int */
"node +10\n",
"node +10\n",
/* parse_all_arg_types */
"node 1 1.0 1.0e10 1.0e-10 0x01 0o07 0b10 \"arg\" r\"arg\\\\\" true false null",
"node 1 1.0 1.0e10 1.0e-10 0x01 0o07 0b10 \"arg\" \"arg\\\\\\\\\" true false null\n",
/* preserve_duplicate_nodes */
"node\n"
"node\n",
"node\n"
"node\n",
/* preserve_node_order */
"node2\n"
"node5\n"
"node1\n",
"node2\n"
"node5\n"
"node1\n",
/* prop_false_type */
"node key=(type)false\n",
"node key=(type)false\n",
/* prop_float_type */
"node key=(type)2.5E10\n",
"node key=(type)2.5E10\n",
/* prop_hex_type */
"node key=(type)0x10\n",
"node key=(type)0x10\n",
/* prop_null_type */
"node key=(type)null\n",
"node key=(type)null\n",
/* prop_raw_string_type */
"node key=(type)r\"str\"\n",
"node key=(type)\"str\"\n",
/* prop_string_type */
"node key=(type)\"str\"\n",
"node key=(type)\"str\"\n",
/* prop_true_type */
"node key=(type)true\n",
"node key=(type)true\n",
/* prop_type */
"node key=(type)true\n",
"node key=(type)true\n",
/* prop_zero_type */
"node key=(type)0\n",
"node key=(type)0\n",
/* quoted_node_name */
"\"0node\"",
"0node\n",
/* quoted_numeric */
"node prop=\"10.0\"",
"node prop=\"10.0\"\n",
/* quoted_prop_name */
"node \"0prop\"=\"val\"\n",
"node 0prop=\"val\"\n",
/* r_node */
"r \"arg\"\n",
"r \"arg\"\n",
/* raw_arg_type */
"node (type)true\n",
"node (type)true\n",
/* raw_node_name */
"r\"\\node\"\n",
"\"\\\\node\"\n",
/* raw_node_type */
"(type)node\n",
"(type)node\n",
/* raw_prop_type */
"node key=(type)true\n",
"node key=(type)true\n",
/* raw_string_arg */
"node_1 r\"arg\\n\"\n"
"node_2 r#\"\"arg\\n\"and stuff\"#\n"
"node_3 r##\"#\"arg\\n\"#and stuff\"##\n",
"node_1 \"arg\\\\n\"\n"
"node_2 \"\\\"arg\\\\n\\\"and stuff\"\n"
"node_3 \"#\\\"arg\\\\n\\\"#and stuff\"\n",
/* raw_string_backslash */
"node r\"\n\"",
"node \"\\n\"\n",
/* raw_string_hash_no_esc */
"node r\"#\"\n",
"node \"#\"\n",
/* raw_string_just_backslash */
"node r\"\\\"\n",
"node \"\\\\\"\n",
/* raw_string_just_quote */
"node r#\"\"\"#\n",
"node \"\\\"\"\n",
/* raw_string_multiple_hash */
"node r###\"\"#\"##\"###\n",
"node \"\\\"#\\\"##\"\n",
/* raw_string_newline */
"node r\"\n"
"hello\n"
"world\n"
"\"",
"node \"\\nhello\\nworld\\n\"\n",
/* raw_string_prop */
"node_1 prop=r\"arg\\n\"\n"
"node_2 prop=r#\"\"arg\"\\n\"#\n"
"node_3 prop=r##\"#\"arg\"#\\n\"##\n",
"node_1 prop=\"arg\\\\n\"\n"
"node_2 prop=\"\\\"arg\\\"\\\\n\"\n"
"node_3 prop=\"#\\\"arg\\\"#\\\\n\"\n",
/* raw_string_quote */
"node r#\"a\"b\"#\n",
"node \"a\\\"b\"\n",
/* repeated_arg */
"node \"arg\" \"arg\"\n",
"node \"arg\" \"arg\"\n",
/* repeated_prop */
"node prop=10 prop=11\n",
"node prop=11\n",
/* same_args */
"node \"whee\" \"whee\"\n",
"node \"whee\" \"whee\"\n",
/* same_name_nodes */
"node\nnode\n",
"node\nnode\n",
/* sci_notation_large */
"node prop=1.23E+1000\n",
"node prop=1.23E+1000\n",
/* sci_notation_small */
"node prop=1.23E-1000\n",
"node prop=1.23E-1000\n",
/* semicolon_after_child */
"node {\n"
" childnode\n"
"};\n",
"node {\n"
" childnode\n"
"}\n",
/* semicolon_in_child */
"node1 {\n"
" node2;\n"
"}\n",
"node1 {\n"
" node2\n"
"}\n",
/* semicolon_separated */
"node1;node2",
"node1\n"
"node2\n",
/* semicolon_separated_nodes */
"node1; node2",
"node1\n"
"node2\n",
/* semicolon_terminated */
"node1;\n",
"node1\n",
/* single_arg */
"node \"arg\"\n",
"node \"arg\"\n",
/* single_prop */
"node prop=\"val\"\n",
"node prop=\"val\"\n",
/* slashdash_arg_after_newline_esc */
"node \\\n"
" /- \"arg\" \"arg2\"\n",
"node \"arg2\"\n",
/* slashdash_child */
"node /- {\n"
" node2\n"
"}",
"node\n",
/* slashdash_empty_child */
"node /- {\n"
"}",
"node\n",
/* slashdash_full_node */
"/- node 1.0 \"a\" b=\"b\n"
"\"",
"",
/* slashdash_in_slashdash */
"/-node1 /-1.0\n"
"node2\n",
"node2\n",
/* slashdash_negative_number */
"node /--1.0 2.0\n",
"node 2.0\n",
/* slashdash_node_in_child */
"node1 {\n"
" /- node2\n"
"}\n",
"node1\n",
/* slashdash_node_with_child */
"/-node {\n"
" node2\n"
"}\n",
"",
/* slashdash_only_node */
"/-node\n",
"",
/* slashdash_only_node_space */
"/- node\n",
"",
/* slashdash_prop */
"node /-key=\"value\" \"arg\"\n",
"node \"arg\"\n",
/* slashdash_raw_prop_key */
"node /-key=\"value\"\n",
"node\n",
/* string_arg */
"node \"arg\"\n",
"node \"arg\"\n",
/* string_prop */
"node prop=\"val\"\n",
"node prop=\"val\"\n",
/* tab_space */
"node\t",
"node\n",
/* trailing_crlf */
"node\r\n",
"node\n",
/* trailing_underscore_hex */
"node 0x123abc_",
"node 0x123abc\n",
/* trailing_underscore_octal */
"node 0o123_",
"node 0o123\n",
/* true_prefix_in_bare_id */
"true_id",
"true_id\n",
/* true_prefix_in_prop_key */
"node true_id=1\n",
"node true_id=1\n",
/* two_nodes */
"node1\n"
"node2\n",
"node1\n"
"node2\n",
/* underscore_in_exponent */
"node 1.0e-10_0\n",
"node 1.0e-100\n",
/* underscore_in_float */
"node 1_1.0\n",
"node 11.0\n",
/* underscore_in_fraction */
"node 1.0_2\n",
"node 1.02\n",
/* underscore_in_int */
"node 1_0\n",
"node 10\n",
/* underscore_in_octal */
"node 0o012_3456_7\n",
"node 0o01234567\n",
/* unusual_bare_id_chars_in_quoted_id */
"\"foo123~!@#$%^&*.:'|?+\" \"weeee\"\n",
"foo123~!@#$%^&*.:'|?+ \"weeee\"\n",
/* unusual_chars_in_bare_id */
"foo123~!@#$%^&*.:'|?+ \"weeee\"\n",
"foo123~!@#$%^&*.:'|?+ \"weeee\"\n",
/* zero_arg */
"node 0\n",
"node 0\n",
/* zero_float */
"node 0.0\n",
"node 0.0\n",
/* zero_int */
"node 0\n",
"node 0\n",
};
size_t i;
for (i = 0; i < sizeof cases / sizeof *cases; i += 2) {
if (!testBeforeAfter(cases[i], cases[i+1])) {
return 0;
}
}
return 1;
}
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
if (testUtf8() &&
testPeekChar() &&
testPeekStr() &&
testPeekWord() &&
testConsumeChar() &&
testConsumeNewline() &&
testConsumeHexCode() &&
testIsRawStringStart() &&
testEscapedUnicode() &&
testCountHashes() &&
testConsumeSingleComment() &&
testConsumeString() &&
testConsumeWhitespace() &&
testUnescaping() &&
testBasicTokenise() &&
testMixedTokenise() &&
testReadBasicDocument() &&
testExamples()) {
printf("Tests passed\n");
return 0;
}
return 1;
}