Template
feat(kdl): add KDL parser producing AST
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* ast.h - KDL 2.0.0 syntax tree for stupidtools (todo 7).
|
||||
*
|
||||
* The tree mirrors the KDL node grammar:
|
||||
*
|
||||
* document := node*
|
||||
* node := [type-annotation] name (arg | prop)* children?
|
||||
*
|
||||
* Every element is a RAW TOKEN REFERENCE into the original source buffer:
|
||||
* kind + span + text slice. Strings keep their delimiters and escapes,
|
||||
* numbers keep their radix spelling, keywords keep their `#` spelling.
|
||||
* INTERPRETATION (unescaping, radix conversion, keyword-to-value mapping,
|
||||
* value-level type annotations) is the typed value model (todo 8) and
|
||||
* deliberately does NOT happen here.
|
||||
*
|
||||
* OWNERSHIP
|
||||
* ---------
|
||||
* st_kdl_parse() allocates every node/arg/prop on the heap;
|
||||
* st_kdl_document_free() releases the whole tree (NULL is a safe no-op).
|
||||
* Token text slices and span `file` pointers are BORROWED from the
|
||||
* caller's `src`/`filename` buffers, which must therefore outlive the
|
||||
* document.
|
||||
*
|
||||
* ERROR-SPAN OWNERSHIP (unlike the lexer's borrowed span)
|
||||
* ---------
|
||||
* The lexer borrows its error span from the lexer object (src/kdl/lexer.h).
|
||||
* The parser cannot do that: its context dies before st_kdl_parse()
|
||||
* returns. Parse errors therefore carry a span that is heap-allocated IN
|
||||
* THE SAME BLOCK as the error itself (error struct first, span struct
|
||||
* immediately after it, aligned). st_error_free() frees the block, so the
|
||||
* span needs no separate cleanup and is never dangling. Callers may read
|
||||
* err->span until st_error_free(err), exactly like err->message.
|
||||
*
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef ST_KDL_AST_H
|
||||
#define ST_KDL_AST_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "lexer.h" /* enum st_token_kind */
|
||||
|
||||
struct st_error;
|
||||
|
||||
/* One raw token reference. `text` is a borrowed slice into the source
|
||||
* buffer; it is NOT NUL-terminated (use `len`). */
|
||||
struct st_kdl_token_ref {
|
||||
enum st_token_kind kind;
|
||||
struct st_span span;
|
||||
const char *text;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
/* A positional argument, in source order (spec mandates order). */
|
||||
struct st_kdl_arg {
|
||||
struct st_kdl_token_ref value;
|
||||
struct st_kdl_arg *next;
|
||||
};
|
||||
|
||||
/* A `key = value` property, in source order. The key is a string-ish
|
||||
* token (identifier or any string form); the value is any value token. */
|
||||
struct st_kdl_prop {
|
||||
struct st_kdl_token_ref key;
|
||||
struct st_kdl_token_ref value;
|
||||
struct st_kdl_prop *next;
|
||||
};
|
||||
|
||||
/* A node: optional type annotation, name, ordered arguments, ordered
|
||||
* properties (interleaved in source, collapsed into the two lists while
|
||||
* preserving relative order within each), and an optional children
|
||||
* block. `next` links siblings in document order. */
|
||||
struct st_kdl_node {
|
||||
struct st_kdl_token_ref *type; /* NULL when not annotated */
|
||||
struct st_kdl_token_ref name;
|
||||
struct st_kdl_arg *args;
|
||||
struct st_kdl_prop *props;
|
||||
struct st_kdl_node *children;
|
||||
struct st_kdl_node *next;
|
||||
};
|
||||
|
||||
/* A document: zero or more top-level nodes in source order. */
|
||||
struct st_kdl_document {
|
||||
struct st_kdl_node *nodes;
|
||||
};
|
||||
|
||||
/* Parse `src` (a NUL-terminated buffer, borrowed) into an AST. On success
|
||||
* returns the document — possibly with zero nodes — and leaves `*err`
|
||||
* untouched. On failure returns NULL and, if `err != NULL`, sets `*err`
|
||||
* to an owned st_error of category ST_ERR_KDL_PARSE with a stable span
|
||||
* (see the header comment). `filename` is borrowed into every span. */
|
||||
struct st_kdl_document *st_kdl_parse(const char *src, const char *filename,
|
||||
struct st_error **err);
|
||||
|
||||
/* Release a document produced by st_kdl_parse. NULL is a safe no-op. */
|
||||
void st_kdl_document_free(struct st_kdl_document *doc);
|
||||
|
||||
#endif /* ST_KDL_AST_H */
|
||||
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
* parser.c - KDL 2.0.0 recursive-descent parser for stupidtools (todo 7).
|
||||
*
|
||||
* Consumes the lexer's flat token stream (src/kdl/lexer.h) and builds the
|
||||
* AST from src/kdl/ast.h. Roles are assigned BY POSITION, per the lexer's
|
||||
* contract: a string-ish token followed by ST_TOK_EQUALS (on the same
|
||||
* line) is a property key, otherwise a bare value is an argument.
|
||||
*
|
||||
* Grammar implemented (see the plan, todo 7):
|
||||
*
|
||||
* document := node* (separated by whitespace/`;`; comments and
|
||||
* `/-` are trivia, skipped)
|
||||
* node := [type] name (arg | prop)* children?
|
||||
* children := '{' node* '}' (`;`-separated, recursively)
|
||||
* arg := value token
|
||||
* prop := string-ish-token '=' value token
|
||||
*
|
||||
* Node termination: a node ends at `;`, `/-`, the end of its parent's
|
||||
* child block (`}`), EOF, or a token on a LATER source line than the last
|
||||
* consumed token (the lexer discards newlines, so the parser infers
|
||||
* newline termination from token spans). Entries and the children block
|
||||
* may span lines only via the lexer's line continuations (esclines), which
|
||||
* the lexer consumes invisibly.
|
||||
*
|
||||
* ERRORS AND SPANS
|
||||
* ----------------
|
||||
* Every parse failure returns a st_error (ST_ERR_KDL_PARSE) carrying a
|
||||
* span. Because st_error only holds a BORROWED span pointer (src/error.h
|
||||
* is frozen), and the lexer's err_span dies with the lexer, the parser
|
||||
* copies the span into storage that is heap-allocated IN THE SAME BLOCK
|
||||
* as the error struct itself — st_error_free() releases both, so the span
|
||||
* is never dangling and never leaked. Lexer errors are likewise copied
|
||||
* into an owned block before the lexer is freed.
|
||||
*
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include "kdl/ast.h"
|
||||
|
||||
#include "error.h"
|
||||
#include "kdl/lexer.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---- parser state ----------------------------------------------------- */
|
||||
|
||||
struct st_parser {
|
||||
struct st_lexer *lx;
|
||||
struct st_token tok; /* one-token lookahead */
|
||||
bool has_tok; /* is `tok` a valid peeked token? */
|
||||
};
|
||||
|
||||
/* ---- owned errors ----------------------------------------------------- */
|
||||
|
||||
/* Build an owned ST_ERR_KDL_PARSE error whose span lives in the same
|
||||
* allocation as the error struct (aligned right after it). Returns NULL
|
||||
* only on allocation failure. */
|
||||
static struct st_error *
|
||||
err_at_owned(struct st_span sp, const char *msg)
|
||||
{
|
||||
const size_t align = _Alignof(struct st_span);
|
||||
const size_t esize =
|
||||
(sizeof(struct st_error) + align - 1) & ~(align - 1);
|
||||
struct st_error *e;
|
||||
struct st_span *spc;
|
||||
size_t mlen;
|
||||
|
||||
if (msg == NULL) {
|
||||
msg = "";
|
||||
}
|
||||
e = malloc(esize + sizeof(struct st_span));
|
||||
if (e == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
mlen = strlen(msg);
|
||||
e->message = malloc(mlen + 1);
|
||||
if (e->message == NULL) {
|
||||
free(e);
|
||||
return NULL;
|
||||
}
|
||||
memcpy(e->message, msg, mlen + 1);
|
||||
e->category = ST_ERR_KDL_PARSE;
|
||||
spc = (struct st_span *)((unsigned char *)e + esize);
|
||||
*spc = sp;
|
||||
e->span = spc;
|
||||
return e;
|
||||
}
|
||||
|
||||
/* ---- token plumbing --------------------------------------------------- */
|
||||
|
||||
/* Pull the next token from the lexer into p->tok. On a lex error, copies
|
||||
* the error (message + span value) into an owned block, frees the
|
||||
* lexer's error, and returns ST_TOK_ERROR with *err set. */
|
||||
static enum st_token_kind
|
||||
p_fetch(struct st_parser *p, struct st_error **err)
|
||||
{
|
||||
struct st_error *lexerr = NULL;
|
||||
enum st_token_kind k;
|
||||
struct st_span sp;
|
||||
|
||||
k = st_lexer_next(p->lx, &p->tok, &lexerr);
|
||||
if (k != ST_TOK_ERROR) {
|
||||
return k;
|
||||
}
|
||||
/* Duplicate the message while `lexerr` is still alive (its span value
|
||||
* is copied by value; the message must be copied BEFORE the free). */
|
||||
if (lexerr != NULL) {
|
||||
sp = lexerr->span != NULL ? *lexerr->span : p->tok.span;
|
||||
*err = err_at_owned(sp, st_error_message(lexerr));
|
||||
st_error_free(lexerr);
|
||||
} else {
|
||||
*err = err_at_owned(p->tok.span, "lexer error");
|
||||
}
|
||||
return ST_TOK_ERROR;
|
||||
}
|
||||
|
||||
/* Peek the next token (leaving it in the lookahead). ST_TOK_ERROR on a
|
||||
* lex failure (*err set). */
|
||||
static enum st_token_kind
|
||||
p_peek(struct st_parser *p, struct st_error **err)
|
||||
{
|
||||
if (!p->has_tok) {
|
||||
enum st_token_kind k = p_fetch(p, err);
|
||||
|
||||
if (k == ST_TOK_ERROR) {
|
||||
return ST_TOK_ERROR;
|
||||
}
|
||||
p->has_tok = true;
|
||||
}
|
||||
return p->tok.kind;
|
||||
}
|
||||
|
||||
/* Discard the peeked token. */
|
||||
static void
|
||||
p_consume(struct st_parser *p)
|
||||
{
|
||||
p->has_tok = false;
|
||||
}
|
||||
|
||||
/* Skip separators/trivia at node-boundary level (document and children
|
||||
* block): line/block comments, slashdash, semicolons. Returns 0 on
|
||||
* success, -1 on lex error (*err set). */
|
||||
static int
|
||||
skip_trivia(struct st_parser *p, struct st_error **err)
|
||||
{
|
||||
for (;;) {
|
||||
enum st_token_kind k = p_peek(p, err);
|
||||
|
||||
if (k == ST_TOK_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
switch (k) {
|
||||
case ST_TOK_LINE_COMMENT:
|
||||
case ST_TOK_BLOCK_COMMENT:
|
||||
case ST_TOK_SLASHDASH:
|
||||
case ST_TOK_SEMICOLON:
|
||||
p_consume(p);
|
||||
continue;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Skip comments only (positions INSIDE a node, where `;` and `/-`
|
||||
* terminate the node instead of being trivia). Returns 0 on success,
|
||||
* -1 on lex error (*err set). */
|
||||
static int
|
||||
skip_comments(struct st_parser *p, struct st_error **err)
|
||||
{
|
||||
for (;;) {
|
||||
enum st_token_kind k = p_peek(p, err);
|
||||
|
||||
if (k == ST_TOK_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
if (k == ST_TOK_LINE_COMMENT || k == ST_TOK_BLOCK_COMMENT) {
|
||||
p_consume(p);
|
||||
continue;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- token classification --------------------------------------------- */
|
||||
|
||||
/* String-ish tokens: legal node names and property keys (the KDL grammar
|
||||
* "string": identifiers plus every quoted/raw/multi-line string form). */
|
||||
static bool
|
||||
tok_is_stringish(enum st_token_kind k)
|
||||
{
|
||||
return k == ST_TOK_IDENT || k == ST_TOK_STRING ||
|
||||
k == ST_TOK_MULTILINE_STRING || k == ST_TOK_RAW_STRING;
|
||||
}
|
||||
|
||||
/* Value tokens: anything usable as an argument or a property value. */
|
||||
static bool
|
||||
tok_is_value(enum st_token_kind k)
|
||||
{
|
||||
return tok_is_stringish(k) || k == ST_TOK_NUMBER || k == ST_TOK_INF ||
|
||||
k == ST_TOK_NEG_INF || k == ST_TOK_NAN || k == ST_TOK_TRUE ||
|
||||
k == ST_TOK_FALSE || k == ST_TOK_NULL;
|
||||
}
|
||||
|
||||
/* ---- AST construction ------------------------------------------------- */
|
||||
|
||||
static void
|
||||
ref_from_token(struct st_kdl_token_ref *ref, const struct st_token *t)
|
||||
{
|
||||
ref->kind = t->kind;
|
||||
ref->span = t->span;
|
||||
ref->text = t->text;
|
||||
ref->len = t->len;
|
||||
}
|
||||
|
||||
static struct st_kdl_arg *
|
||||
arg_new(const struct st_token *t)
|
||||
{
|
||||
struct st_kdl_arg *a = calloc(1, sizeof(*a));
|
||||
|
||||
if (a == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(&a->value, t);
|
||||
return a;
|
||||
}
|
||||
|
||||
static struct st_kdl_prop *
|
||||
prop_new(const struct st_token *key, const struct st_token *value)
|
||||
{
|
||||
struct st_kdl_prop *pr = calloc(1, sizeof(*pr));
|
||||
|
||||
if (pr == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(&pr->key, key);
|
||||
ref_from_token(&pr->value, value);
|
||||
return pr;
|
||||
}
|
||||
|
||||
static struct st_kdl_node *
|
||||
node_new(const struct st_token *name, const struct st_token *type,
|
||||
bool has_type)
|
||||
{
|
||||
struct st_kdl_node *n = calloc(1, sizeof(*n));
|
||||
|
||||
if (n == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(&n->name, name);
|
||||
if (has_type) {
|
||||
n->type = malloc(sizeof(*n->type));
|
||||
if (n->type == NULL) {
|
||||
free(n);
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(n->type, type);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static void
|
||||
nodes_free(struct st_kdl_node *head);
|
||||
|
||||
static void
|
||||
node_free(struct st_kdl_node *n)
|
||||
{
|
||||
struct st_kdl_arg *a;
|
||||
struct st_kdl_prop *pr;
|
||||
|
||||
if (n == NULL) {
|
||||
return;
|
||||
}
|
||||
free(n->type);
|
||||
for (a = n->args; a != NULL;) {
|
||||
struct st_kdl_arg *nx = a->next;
|
||||
free(a);
|
||||
a = nx;
|
||||
}
|
||||
for (pr = n->props; pr != NULL;) {
|
||||
struct st_kdl_prop *px = pr->next;
|
||||
free(pr);
|
||||
pr = px;
|
||||
}
|
||||
nodes_free(n->children);
|
||||
free(n);
|
||||
}
|
||||
|
||||
static void
|
||||
nodes_free(struct st_kdl_node *head)
|
||||
{
|
||||
while (head != NULL) {
|
||||
struct st_kdl_node *nx = head->next;
|
||||
node_free(head);
|
||||
head = nx;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- the grammar ------------------------------------------------------ */
|
||||
|
||||
static struct st_kdl_node *parse_node(struct st_parser *p,
|
||||
struct st_error **err);
|
||||
|
||||
/* Parse a children block. The opening `{` is already consumed; `lbrace`
|
||||
* is its span (used for the unterminated-block error). Returns the child
|
||||
* list head (NULL for an empty block), or NULL with *err set. */
|
||||
static struct st_kdl_node *
|
||||
parse_children(struct st_parser *p, struct st_span lbrace,
|
||||
struct st_error **err)
|
||||
{
|
||||
struct st_kdl_node *head = NULL;
|
||||
struct st_kdl_node *tail = NULL;
|
||||
|
||||
for (;;) {
|
||||
enum st_token_kind k;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
if (skip_trivia(p, err) < 0) {
|
||||
nodes_free(head);
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
nodes_free(head);
|
||||
return NULL;
|
||||
}
|
||||
if (k == ST_TOK_EOF) {
|
||||
nodes_free(head);
|
||||
*err = err_at_owned(lbrace,
|
||||
"unterminated children block (missing closing '}')");
|
||||
return NULL;
|
||||
}
|
||||
if (k == ST_TOK_RBRACE) {
|
||||
p_consume(p);
|
||||
return head;
|
||||
}
|
||||
n = parse_node(p, err);
|
||||
if (n == NULL) {
|
||||
nodes_free(head);
|
||||
return NULL;
|
||||
}
|
||||
if (tail == NULL) {
|
||||
head = tail = n;
|
||||
} else {
|
||||
tail->next = n;
|
||||
tail = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse one node. Returns NULL with *err set on failure. The caller is
|
||||
* responsible for freeing the node list when NULL comes back. */
|
||||
static struct st_kdl_node *
|
||||
parse_node(struct st_parser *p, struct st_error **err)
|
||||
{
|
||||
struct st_kdl_node *n = NULL;
|
||||
struct st_token name_tok;
|
||||
struct st_token type_tok;
|
||||
struct st_kdl_arg *args_tail = NULL;
|
||||
struct st_kdl_prop *props_tail = NULL;
|
||||
bool has_type = false;
|
||||
size_t last_line;
|
||||
enum st_token_kind k;
|
||||
|
||||
/* Optional type annotation. */
|
||||
if (skip_comments(p, err) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
return NULL;
|
||||
}
|
||||
if (k == ST_TOK_TYPE) {
|
||||
type_tok = p->tok;
|
||||
has_type = true;
|
||||
p_consume(p);
|
||||
if (skip_comments(p, err) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Name: any string-ish token. */
|
||||
if (!tok_is_stringish(k)) {
|
||||
*err = err_at_owned(p->tok.span, "expected a node name");
|
||||
return NULL;
|
||||
}
|
||||
name_tok = p->tok;
|
||||
p_consume(p);
|
||||
|
||||
n = node_new(&name_tok, &type_tok, has_type);
|
||||
if (n == NULL) {
|
||||
*err = err_at_owned(name_tok.span, "out of memory");
|
||||
return NULL;
|
||||
}
|
||||
last_line = name_tok.span.line;
|
||||
|
||||
/* Entries: interleaved arguments and properties. */
|
||||
for (;;) {
|
||||
if (skip_comments(p, err) < 0) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
switch (k) {
|
||||
case ST_TOK_EOF:
|
||||
case ST_TOK_RBRACE:
|
||||
case ST_TOK_SEMICOLON:
|
||||
case ST_TOK_SLASHDASH:
|
||||
/* Terminated: the peeked token belongs to the caller's level. */
|
||||
return n;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (p->tok.span.line > last_line) {
|
||||
/* Newline termination (the lexer discards newlines). */
|
||||
return n;
|
||||
}
|
||||
switch (k) {
|
||||
case ST_TOK_LBRACE: {
|
||||
struct st_span lbrace = p->tok.span;
|
||||
|
||||
p_consume(p);
|
||||
n->children = parse_children(p, lbrace, err);
|
||||
if (*err != NULL) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
case ST_TOK_EQUALS:
|
||||
/* The previous token was the node name or a non-key value. */
|
||||
*err = err_at_owned(p->tok.span, "unexpected '='");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
case ST_TOK_TYPE:
|
||||
*err = err_at_owned(p->tok.span,
|
||||
"unexpected type annotation (value annotations are "
|
||||
"not supported yet)");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
default:
|
||||
if (!tok_is_value(k)) {
|
||||
*err = err_at_owned(p->tok.span,
|
||||
"expected an argument or property");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* A value-ish entry: classify as property (key `=` value) or
|
||||
* argument. */
|
||||
{
|
||||
struct st_token val_tok = p->tok;
|
||||
|
||||
p_consume(p);
|
||||
if (tok_is_stringish(val_tok.kind)) {
|
||||
/* `=` lookahead, same source line only. */
|
||||
if (skip_comments(p, err) < 0) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
if (k == ST_TOK_EQUALS &&
|
||||
p->tok.span.line == val_tok.span.line) {
|
||||
struct st_kdl_prop *pr;
|
||||
|
||||
p_consume(p); /* '=' */
|
||||
if (skip_comments(p, err) < 0) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
k = p_peek(p, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
if (!tok_is_value(k)) {
|
||||
*err = err_at_owned(p->tok.span,
|
||||
"expected a value after '='");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
pr = prop_new(&val_tok, &p->tok);
|
||||
if (pr == NULL) {
|
||||
*err = err_at_owned(val_tok.span, "out of memory");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
last_line = p->tok.span.line;
|
||||
p_consume(p);
|
||||
if (props_tail == NULL) {
|
||||
n->props = props_tail = pr;
|
||||
} else {
|
||||
props_tail->next = pr;
|
||||
props_tail = pr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* Argument. */
|
||||
{
|
||||
struct st_kdl_arg *a = arg_new(&val_tok);
|
||||
|
||||
if (a == NULL) {
|
||||
*err = err_at_owned(val_tok.span, "out of memory");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
last_line = val_tok.span.line;
|
||||
if (args_tail == NULL) {
|
||||
n->args = args_tail = a;
|
||||
} else {
|
||||
args_tail->next = a;
|
||||
args_tail = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- public API ------------------------------------------------------- */
|
||||
|
||||
struct st_kdl_document *
|
||||
st_kdl_parse(const char *src, const char *filename, struct st_error **err)
|
||||
{
|
||||
struct st_parser parser;
|
||||
struct st_kdl_document *doc;
|
||||
struct st_kdl_node *head = NULL;
|
||||
struct st_kdl_node *tail = NULL;
|
||||
|
||||
if (err != NULL) {
|
||||
*err = NULL;
|
||||
}
|
||||
|
||||
parser.lx = st_lexer_new(src, filename);
|
||||
if (parser.lx == NULL) {
|
||||
if (err != NULL) {
|
||||
*err = err_at_owned((struct st_span){ filename, 0, 0 },
|
||||
"out of memory");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
parser.has_tok = false;
|
||||
|
||||
doc = calloc(1, sizeof(*doc));
|
||||
if (doc == NULL) {
|
||||
st_lexer_free(parser.lx);
|
||||
if (err != NULL) {
|
||||
*err = err_at_owned((struct st_span){ filename, 0, 0 },
|
||||
"out of memory");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
enum st_token_kind k;
|
||||
struct st_kdl_node *n;
|
||||
|
||||
if (skip_trivia(&parser, err) < 0) {
|
||||
goto fail;
|
||||
}
|
||||
k = p_peek(&parser, err);
|
||||
if (k == ST_TOK_ERROR) {
|
||||
goto fail;
|
||||
}
|
||||
if (k == ST_TOK_EOF) {
|
||||
break;
|
||||
}
|
||||
if (k == ST_TOK_RBRACE) {
|
||||
*err = err_at_owned(parser.tok.span, "unexpected '}'");
|
||||
goto fail;
|
||||
}
|
||||
n = parse_node(&parser, err);
|
||||
if (n == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
if (tail == NULL) {
|
||||
head = tail = n;
|
||||
} else {
|
||||
tail->next = n;
|
||||
tail = n;
|
||||
}
|
||||
}
|
||||
|
||||
st_lexer_free(parser.lx);
|
||||
doc->nodes = head;
|
||||
return doc;
|
||||
|
||||
fail:
|
||||
st_lexer_free(parser.lx);
|
||||
nodes_free(head);
|
||||
free(doc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
st_kdl_document_free(struct st_kdl_document *doc)
|
||||
{
|
||||
if (doc == NULL) {
|
||||
return;
|
||||
}
|
||||
nodes_free(doc->nodes);
|
||||
free(doc);
|
||||
}
|
||||
Reference in New Issue
Block a user