Template
feat(kdl): add KDL parser producing AST
This commit is contained in:
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
/* Parser fixture: a realistic multi-node build file exercising node names,
|
||||
arguments, properties, children blocks, comments, a slashdash, and
|
||||
semicolon separators. Parsed by tests/unit/test_parser.c against an
|
||||
exact expected AST (counts, order, nesting, spans). */
|
||||
|
||||
// top-level line comment
|
||||
project name="stupidtools" version="1.0.0"
|
||||
|
||||
target "default" {
|
||||
src "src/main.c" ; src "src/kdl/lexer.c"
|
||||
cc-flags "-std=c23" "-Wall" // pinned warning flags
|
||||
jobs 8
|
||||
optimize #true debug=#false
|
||||
feature "unit-tests" {
|
||||
option "munit" { default #true }
|
||||
}
|
||||
}
|
||||
|
||||
/-
|
||||
|
||||
(meta)credits "huntedbytheirs"
|
||||
@@ -0,0 +1,690 @@
|
||||
/* LINK: ../../src/kdl/parser.c ../../src/kdl/lexer.c ../../src/error.c ../../src/span.c */
|
||||
/* tests/unit/test_parser.c
|
||||
*
|
||||
* Unit tests for the KDL 2.0.0 recursive-descent parser (todo 7).
|
||||
*
|
||||
* The magic LINK comment on line 1 is REQUIRED by tests/run.sh: it lists
|
||||
* the extra .c sources to compile into this test binary (paths relative
|
||||
* to tests/unit/, space-separated). parser.c depends on lexer.c (token
|
||||
* stream), error.c (typed errors) and span.c (caret rendering).
|
||||
*
|
||||
* These tests assert on the REAL AST structure — node counts, argument
|
||||
* and property order, children nesting, spans — never just "no crash".
|
||||
* AST token slices are borrowed from the source buffer, so the source
|
||||
* must outlive the document (both kept alive until st_kdl_document_free).
|
||||
*/
|
||||
#include "munit.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "kdl/ast.h"
|
||||
#include "kdl/lexer.h"
|
||||
#include "span.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- fixture loading -------------------------------------------------- */
|
||||
|
||||
/* Locate tests/fixtures/basic.kdl. The harness runs the test binary with
|
||||
* cwd = repo top (make check) or tests/unit (manual run); probe both. */
|
||||
static const char *
|
||||
fixture_path(void)
|
||||
{
|
||||
static const char *const candidates[] = {
|
||||
"tests/fixtures/basic.kdl",
|
||||
"../fixtures/basic.kdl",
|
||||
};
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
|
||||
FILE *f = fopen(candidates[i], "rb");
|
||||
if (f != NULL) {
|
||||
fclose(f);
|
||||
return candidates[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Slurp the fixture into a NUL-terminated buffer (the lexer requires NUL
|
||||
* termination). Returns NULL on any I/O failure. Caller frees. */
|
||||
static char *
|
||||
slurp_fixture(const char *path)
|
||||
{
|
||||
FILE *f;
|
||||
long n;
|
||||
char *buf;
|
||||
|
||||
if (path == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
f = fopen(path, "rb");
|
||||
if (f == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
n = ftell(f);
|
||||
if (n < 0 || fseek(f, 0, SEEK_SET) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
buf = munit_malloc((size_t)n + 1);
|
||||
if (fread(buf, 1, (size_t)n, f) != (size_t)n) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ---- token-ref assertions --------------------------------------------- */
|
||||
|
||||
static void
|
||||
assert_ref(const struct st_kdl_token_ref *ref, enum st_token_kind kind,
|
||||
const char *text, size_t line, size_t col)
|
||||
{
|
||||
munit_assert_not_null(ref);
|
||||
munit_assert_int(ref->kind, ==, kind);
|
||||
if (text != NULL) {
|
||||
munit_assert_not_null(ref->text);
|
||||
munit_assert_size(ref->len, ==, strlen(text));
|
||||
munit_assert_memory_equal(ref->len, ref->text, text);
|
||||
}
|
||||
munit_assert_size(ref->span.line, ==, line);
|
||||
munit_assert_size(ref->span.col, ==, col);
|
||||
}
|
||||
|
||||
/* ---- tests ------------------------------------------------------------ */
|
||||
|
||||
/* (a) tests/fixtures/basic.kdl parses to the expected AST: node count,
|
||||
* names, arg order, prop key/value kinds, children nesting, spans. */
|
||||
static MunitResult
|
||||
test_parse_fixture(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
const char *path = fixture_path();
|
||||
char *src;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
struct st_kdl_prop *p;
|
||||
|
||||
munit_assert_not_null(path);
|
||||
src = slurp_fixture(path);
|
||||
munit_assert_not_null(src);
|
||||
|
||||
doc = st_kdl_parse(src, path, &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
/* --- three top-level nodes: project, target, credits ------------- */
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "project", 7, 1);
|
||||
|
||||
munit_assert_null(n->args);
|
||||
p = n->props;
|
||||
munit_assert_not_null(p);
|
||||
assert_ref(&p->key, ST_TOK_IDENT, "name", 7, 9);
|
||||
assert_ref(&p->value, ST_TOK_STRING, "\"stupidtools\"", 7, 14);
|
||||
p = p->next;
|
||||
munit_assert_not_null(p);
|
||||
assert_ref(&p->key, ST_TOK_IDENT, "version", 7, 28);
|
||||
assert_ref(&p->value, ST_TOK_STRING, "\"1.0.0\"", 7, 36);
|
||||
munit_assert_null(p->next);
|
||||
|
||||
/* --- target "default" with 6 children ---------------------------- */
|
||||
n = n->next;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "target", 9, 1);
|
||||
munit_assert_not_null(n->args);
|
||||
assert_ref(&n->args->value, ST_TOK_STRING, "\"default\"", 9, 8);
|
||||
munit_assert_null(n->args->next);
|
||||
munit_assert_null(n->props);
|
||||
|
||||
{
|
||||
struct st_kdl_node *c = n->children;
|
||||
|
||||
/* child 1: src "src/main.c" */
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "src", 10, 5);
|
||||
munit_assert_not_null(c->args);
|
||||
assert_ref(&c->args->value, ST_TOK_STRING, "\"src/main.c\"", 10, 9);
|
||||
munit_assert_null(c->args->next);
|
||||
|
||||
/* child 2: src "src/kdl/lexer.c" (after ';') */
|
||||
c = c->next;
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "src", 10, 24);
|
||||
assert_ref(&c->args->value, ST_TOK_STRING, "\"src/kdl/lexer.c\"", 10,
|
||||
28);
|
||||
munit_assert_null(c->args->next);
|
||||
|
||||
/* child 3: cc-flags "-std=c23" "-Wall" */
|
||||
c = c->next;
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "cc-flags", 11, 5);
|
||||
munit_assert_not_null(c->args);
|
||||
assert_ref(&c->args->value, ST_TOK_STRING, "\"-std=c23\"", 11, 14);
|
||||
assert_ref(&c->args->next->value, ST_TOK_STRING, "\"-Wall\"", 11, 25);
|
||||
munit_assert_null(c->args->next->next);
|
||||
|
||||
/* child 4: jobs 8 */
|
||||
c = c->next;
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "jobs", 12, 5);
|
||||
assert_ref(&c->args->value, ST_TOK_NUMBER, "8", 12, 10);
|
||||
munit_assert_null(c->args->next);
|
||||
|
||||
/* child 5: optimize #true debug=#false */
|
||||
c = c->next;
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "optimize", 13, 5);
|
||||
assert_ref(&c->args->value, ST_TOK_TRUE, "#true", 13, 14);
|
||||
munit_assert_null(c->args->next);
|
||||
munit_assert_not_null(c->props);
|
||||
assert_ref(&c->props->key, ST_TOK_IDENT, "debug", 13, 20);
|
||||
assert_ref(&c->props->value, ST_TOK_FALSE, "#false", 13, 26);
|
||||
munit_assert_null(c->props->next);
|
||||
|
||||
/* child 6: feature "unit-tests" { option "munit" { default #true } } */
|
||||
c = c->next;
|
||||
munit_assert_not_null(c);
|
||||
assert_ref(&c->name, ST_TOK_IDENT, "feature", 14, 5);
|
||||
assert_ref(&c->args->value, ST_TOK_STRING, "\"unit-tests\"", 14, 13);
|
||||
munit_assert_null(c->args->next);
|
||||
munit_assert_not_null(c->children);
|
||||
assert_ref(&c->children->name, ST_TOK_IDENT, "option", 15, 9);
|
||||
assert_ref(&c->children->args->value, ST_TOK_STRING, "\"munit\"", 15,
|
||||
16);
|
||||
munit_assert_not_null(c->children->children);
|
||||
assert_ref(&c->children->children->name, ST_TOK_IDENT, "default", 15,
|
||||
26);
|
||||
assert_ref(&c->children->children->args->value, ST_TOK_TRUE, "#true",
|
||||
15, 34);
|
||||
munit_assert_null(c->children->children->args->next);
|
||||
munit_assert_null(c->next);
|
||||
}
|
||||
|
||||
/* --- credits with type annotation (meta) -------------------------- */
|
||||
n = n->next;
|
||||
munit_assert_not_null(n);
|
||||
munit_assert_not_null(n->type);
|
||||
/* the TYPE token's span starts at the opening '('; its text slice is
|
||||
* the inner identifier without the parens (see lexer.h) */
|
||||
assert_ref(n->type, ST_TOK_TYPE, "meta", 21, 1);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "credits", 21, 7);
|
||||
assert_ref(&n->args->value, ST_TOK_STRING, "\"huntedbytheirs\"", 21, 15);
|
||||
munit_assert_null(n->args->next);
|
||||
munit_assert_null(n->props);
|
||||
munit_assert_null(n->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
free(src);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (b) `node {` — unterminated children block errors with the span of the
|
||||
* `{` token. */
|
||||
static MunitResult
|
||||
test_unterminated_children(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
const char *src = "node {";
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse(src, "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
|
||||
/* the span must point at the `{` (line 1, col 6), not at EOF */
|
||||
munit_assert_not_null(err->span);
|
||||
munit_assert_size(err->span->line, ==, 1);
|
||||
munit_assert_size(err->span->col, ==, 6);
|
||||
munit_assert_true(strstr(st_error_message(err), "children") != NULL);
|
||||
st_error_free(err);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (c) a stray `}` at top level errors with the span of the `}`. */
|
||||
static MunitResult
|
||||
test_stray_rbrace(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse("}", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
|
||||
munit_assert_not_null(err->span);
|
||||
munit_assert_size(err->span->line, ==, 1);
|
||||
munit_assert_size(err->span->col, ==, 1);
|
||||
st_error_free(err);
|
||||
|
||||
/* also stray inside a completed children block: `node { child } }` */
|
||||
err = NULL;
|
||||
doc = st_kdl_parse("node { child } }", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_size(err->span->col, ==, 16);
|
||||
st_error_free(err);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (d) prop classification: a token followed by `=` is a key, the next
|
||||
* value token is its value; bare values are args, in source order. */
|
||||
static MunitResult
|
||||
test_prop_classification(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
struct st_kdl_arg *a;
|
||||
struct st_kdl_prop *p;
|
||||
|
||||
doc = st_kdl_parse("node key=value key2=\"v2\" 1 2 #true", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
munit_assert_null(n->next);
|
||||
|
||||
p = n->props;
|
||||
munit_assert_not_null(p);
|
||||
assert_ref(&p->key, ST_TOK_IDENT, "key", 1, 6);
|
||||
assert_ref(&p->value, ST_TOK_IDENT, "value", 1, 10);
|
||||
p = p->next;
|
||||
munit_assert_not_null(p);
|
||||
assert_ref(&p->key, ST_TOK_IDENT, "key2", 1, 16);
|
||||
assert_ref(&p->value, ST_TOK_STRING, "\"v2\"", 1, 21);
|
||||
munit_assert_null(p->next);
|
||||
|
||||
a = n->args;
|
||||
munit_assert_not_null(a);
|
||||
assert_ref(&a->value, ST_TOK_NUMBER, "1", 1, 26);
|
||||
a = a->next;
|
||||
munit_assert_not_null(a);
|
||||
assert_ref(&a->value, ST_TOK_NUMBER, "2", 1, 28);
|
||||
a = a->next;
|
||||
munit_assert_not_null(a);
|
||||
assert_ref(&a->value, ST_TOK_TRUE, "#true", 1, 30);
|
||||
munit_assert_null(a->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (e) interleaved args/props keep their relative order within each list:
|
||||
* `node a=1 b c=2 d` -> args [b, d], props [a=1, c=2]. */
|
||||
static MunitResult
|
||||
test_interleaved_order(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
doc = st_kdl_parse("node a=1 b c=2 d", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
|
||||
/* args, in order: b then d */
|
||||
munit_assert_not_null(n->args);
|
||||
assert_ref(&n->args->value, ST_TOK_IDENT, "b", 1, 10);
|
||||
assert_ref(&n->args->next->value, ST_TOK_IDENT, "d", 1, 16);
|
||||
munit_assert_null(n->args->next->next);
|
||||
|
||||
/* props, in order: a=1 then c=2 */
|
||||
munit_assert_not_null(n->props);
|
||||
assert_ref(&n->props->key, ST_TOK_IDENT, "a", 1, 6);
|
||||
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 1, 8);
|
||||
assert_ref(&n->props->next->key, ST_TOK_IDENT, "c", 1, 12);
|
||||
assert_ref(&n->props->next->value, ST_TOK_NUMBER, "2", 1, 14);
|
||||
munit_assert_null(n->props->next->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (f) newline terminates a node: `a 1\nb 2` is two nodes, not one node
|
||||
* with four entries. */
|
||||
static MunitResult
|
||||
test_newline_separates_nodes(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
doc = st_kdl_parse("a 1\nb 2", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "a", 1, 1);
|
||||
assert_ref(&n->args->value, ST_TOK_NUMBER, "1", 1, 3);
|
||||
munit_assert_null(n->args->next);
|
||||
|
||||
n = n->next;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "b", 2, 1);
|
||||
assert_ref(&n->args->value, ST_TOK_NUMBER, "2", 2, 3);
|
||||
munit_assert_null(n->args->next);
|
||||
munit_assert_null(n->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (g) semicolons separate nodes; repeated separators are trivia. */
|
||||
static MunitResult
|
||||
test_semicolon_separators(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
size_t count = 0;
|
||||
|
||||
doc = st_kdl_parse("node ; ; node", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
for (n = doc->nodes; n != NULL; n = n->next) {
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "node", 1,
|
||||
(size_t)(count == 0 ? 1 : 10));
|
||||
count++;
|
||||
}
|
||||
munit_assert_size(count, ==, 2);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (h) an empty document parses to a document with zero nodes. */
|
||||
static MunitResult
|
||||
test_empty_document(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse("", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
munit_assert_null(doc->nodes);
|
||||
st_kdl_document_free(doc);
|
||||
|
||||
/* whitespace/comment-only is also an empty document */
|
||||
err = NULL;
|
||||
doc = st_kdl_parse(" // nothing\n/* nor here */\n/- ;", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
munit_assert_null(doc->nodes);
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (i) `node = x` is invalid: the node name cannot be a property key. */
|
||||
static MunitResult
|
||||
test_equals_after_name(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse("node = x", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
|
||||
munit_assert_not_null(err->span);
|
||||
munit_assert_size(err->span->line, ==, 1);
|
||||
munit_assert_size(err->span->col, ==, 6);
|
||||
st_error_free(err);
|
||||
|
||||
/* a non-string value before `=` is equally invalid: `node 1 = 2` */
|
||||
err = NULL;
|
||||
doc = st_kdl_parse("node 1 = 2", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_size(err->span->col, ==, 8);
|
||||
st_error_free(err);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (j) a property needs a value: `node key =` errors at EOF; `node key = {`
|
||||
* errors at the `{`. */
|
||||
static MunitResult
|
||||
test_prop_missing_value(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse("node key =", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_true(strstr(st_error_message(err), "value") != NULL);
|
||||
munit_assert_not_null(err->span);
|
||||
munit_assert_size(err->span->col, ==, 11);
|
||||
st_error_free(err);
|
||||
|
||||
err = NULL;
|
||||
doc = st_kdl_parse("node key = {", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_true(strstr(st_error_message(err), "value") != NULL);
|
||||
munit_assert_size(err->span->col, ==, 12);
|
||||
st_error_free(err);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (k) a lexer error is propagated with its span, copied into owned
|
||||
* storage: the span must still be readable after st_kdl_parse returns. */
|
||||
static MunitResult
|
||||
test_lexer_error_propagates(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
|
||||
doc = st_kdl_parse("node \"unterminated", "t.kdl", &err);
|
||||
munit_assert_null(doc);
|
||||
munit_assert_not_null(err);
|
||||
munit_assert_int(st_error_category_of(err), ==, ST_ERR_KDL_PARSE);
|
||||
munit_assert_not_null(err->span);
|
||||
munit_assert_size(err->span->line, ==, 1);
|
||||
munit_assert_size(err->span->col, ==, 6);
|
||||
munit_assert_true(strstr(st_error_message(err), "unterminated") != NULL);
|
||||
st_error_free(err);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (l) type annotation attaches to the node name; quoted node names and
|
||||
* string keys work. */
|
||||
static MunitResult
|
||||
test_annotation_and_string_names(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
doc = st_kdl_parse("(u8)size 4\n\"quoted node\" x\np \"k s\"=1", "t.kdl",
|
||||
&err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
munit_assert_not_null(n->type);
|
||||
assert_ref(n->type, ST_TOK_TYPE, "u8", 1, 1);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "size", 1, 5);
|
||||
assert_ref(&n->args->value, ST_TOK_NUMBER, "4", 1, 10);
|
||||
munit_assert_null(n->args->next);
|
||||
|
||||
n = n->next;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_STRING, "\"quoted node\"", 2, 1);
|
||||
assert_ref(&n->args->value, ST_TOK_IDENT, "x", 2, 15);
|
||||
munit_assert_null(n->args->next);
|
||||
|
||||
n = n->next;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "p", 3, 1);
|
||||
munit_assert_not_null(n->props);
|
||||
assert_ref(&n->props->key, ST_TOK_STRING, "\"k s\"", 3, 3);
|
||||
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 3, 9);
|
||||
munit_assert_null(n->props->next);
|
||||
munit_assert_null(n->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (m) single-line children blocks and nested blocks parse recursively. */
|
||||
static MunitResult
|
||||
test_children_nesting(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
doc = st_kdl_parse("p { a; b; c { d } }", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "p", 1, 1);
|
||||
munit_assert_not_null(n->children);
|
||||
assert_ref(&n->children->name, ST_TOK_IDENT, "a", 1, 5);
|
||||
assert_ref(&n->children->next->name, ST_TOK_IDENT, "b", 1, 8);
|
||||
assert_ref(&n->children->next->next->name, ST_TOK_IDENT, "c", 1, 11);
|
||||
munit_assert_not_null(n->children->next->next->children);
|
||||
assert_ref(&n->children->next->next->children->name, ST_TOK_IDENT, "d",
|
||||
1, 15);
|
||||
munit_assert_null(n->children->next->next->next);
|
||||
munit_assert_null(n->next);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (n) `/-` and comments are trivia: a slashdash between entries vanishes,
|
||||
* and a node ends at the slashdash/semicolon regardless of line. */
|
||||
static MunitResult
|
||||
test_trivia_skipped(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
struct st_error *err = NULL;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *n;
|
||||
size_t count = 0;
|
||||
|
||||
doc = st_kdl_parse("node a=1 /-\n; // still trivia\nb", "t.kdl", &err);
|
||||
munit_assert_not_null(doc);
|
||||
munit_assert_null(err);
|
||||
|
||||
n = doc->nodes;
|
||||
munit_assert_not_null(n);
|
||||
assert_ref(&n->name, ST_TOK_IDENT, "node", 1, 1);
|
||||
munit_assert_not_null(n->props);
|
||||
assert_ref(&n->props->key, ST_TOK_IDENT, "a", 1, 6);
|
||||
assert_ref(&n->props->value, ST_TOK_NUMBER, "1", 1, 8);
|
||||
munit_assert_null(n->props->next);
|
||||
munit_assert_null(n->args);
|
||||
|
||||
for (n = n->next; n != NULL; n = n->next) {
|
||||
count++;
|
||||
}
|
||||
munit_assert_size(count, ==, 1);
|
||||
assert_ref(&doc->nodes->next->name, ST_TOK_IDENT, "b", 3, 1);
|
||||
|
||||
st_kdl_document_free(doc);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
/* (o) `st_kdl_document_free(NULL)` is a safe no-op. */
|
||||
static MunitResult
|
||||
test_free_null(const MunitParameter params[], void *data)
|
||||
{
|
||||
(void)params;
|
||||
(void)data;
|
||||
|
||||
st_kdl_document_free(NULL);
|
||||
return MUNIT_OK;
|
||||
}
|
||||
|
||||
static MunitTest tests[] = {
|
||||
{ "/parse/fixture-basic-kdl", test_parse_fixture, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/unterminated-children", test_unterminated_children, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/stray-rbrace", test_stray_rbrace, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/prop-classification", test_prop_classification, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/interleaved-order", test_interleaved_order, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/newline-separates-nodes", test_newline_separates_nodes, NULL,
|
||||
NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/semicolon-separators", test_semicolon_separators, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/empty-document", test_empty_document, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/equals-after-name", test_equals_after_name, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/prop-missing-value", test_prop_missing_value, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/lexer-error-propagates", test_lexer_error_propagates, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/annotation-and-string-names", test_annotation_and_string_names,
|
||||
NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/children-nesting", test_children_nesting, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/trivia-skipped", test_trivia_skipped, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ "/parse/free-null", test_free_null, NULL, NULL,
|
||||
MUNIT_TEST_OPTION_NONE, NULL },
|
||||
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
|
||||
};
|
||||
|
||||
static const MunitSuite suite = {
|
||||
"/parser", tests, NULL, 1, MUNIT_SUITE_OPTION_NONE,
|
||||
};
|
||||
|
||||
int
|
||||
main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)])
|
||||
{
|
||||
return munit_suite_main(&suite, NULL, argc, argv);
|
||||
}
|
||||
Reference in New Issue
Block a user