Template
feat(kdl): add typed KDL value model
This commit is contained in:
+5
-1
@@ -56,14 +56,18 @@ struct st_kdl_token_ref {
|
||||
/* A positional argument, in source order (spec mandates order). */
|
||||
struct st_kdl_arg {
|
||||
struct st_kdl_token_ref value;
|
||||
struct st_kdl_token_ref *annotation; /* NULL when not annotated */
|
||||
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. */
|
||||
* token (identifier or any string form); the value is any value token.
|
||||
* `annotation` is the value's type annotation (ST_TOK_TYPE), e.g. the
|
||||
* `u8` in `key=(u8)42`; NULL when the value is not annotated. */
|
||||
struct st_kdl_prop {
|
||||
struct st_kdl_token_ref key;
|
||||
struct st_kdl_token_ref value;
|
||||
struct st_kdl_token_ref *annotation; /* NULL when not annotated */
|
||||
struct st_kdl_prop *next;
|
||||
};
|
||||
|
||||
|
||||
+92
-15
@@ -218,7 +218,7 @@ ref_from_token(struct st_kdl_token_ref *ref, const struct st_token *t)
|
||||
}
|
||||
|
||||
static struct st_kdl_arg *
|
||||
arg_new(const struct st_token *t)
|
||||
arg_new(const struct st_token *t, const struct st_token *ann, bool has_ann)
|
||||
{
|
||||
struct st_kdl_arg *a = calloc(1, sizeof(*a));
|
||||
|
||||
@@ -226,11 +226,20 @@ arg_new(const struct st_token *t)
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(&a->value, t);
|
||||
if (has_ann) {
|
||||
a->annotation = malloc(sizeof(*a->annotation));
|
||||
if (a->annotation == NULL) {
|
||||
free(a);
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(a->annotation, ann);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
static struct st_kdl_prop *
|
||||
prop_new(const struct st_token *key, const struct st_token *value)
|
||||
prop_new(const struct st_token *key, const struct st_token *value,
|
||||
const struct st_token *ann, bool has_ann)
|
||||
{
|
||||
struct st_kdl_prop *pr = calloc(1, sizeof(*pr));
|
||||
|
||||
@@ -239,6 +248,14 @@ prop_new(const struct st_token *key, const struct st_token *value)
|
||||
}
|
||||
ref_from_token(&pr->key, key);
|
||||
ref_from_token(&pr->value, value);
|
||||
if (has_ann) {
|
||||
pr->annotation = malloc(sizeof(*pr->annotation));
|
||||
if (pr->annotation == NULL) {
|
||||
free(pr);
|
||||
return NULL;
|
||||
}
|
||||
ref_from_token(pr->annotation, ann);
|
||||
}
|
||||
return pr;
|
||||
}
|
||||
|
||||
@@ -278,11 +295,13 @@ node_free(struct st_kdl_node *n)
|
||||
free(n->type);
|
||||
for (a = n->args; a != NULL;) {
|
||||
struct st_kdl_arg *nx = a->next;
|
||||
free(a->annotation);
|
||||
free(a);
|
||||
a = nx;
|
||||
}
|
||||
for (pr = n->props; pr != NULL;) {
|
||||
struct st_kdl_prop *px = pr->next;
|
||||
free(pr->annotation);
|
||||
free(pr);
|
||||
pr = px;
|
||||
}
|
||||
@@ -444,12 +463,45 @@ parse_node(struct st_parser *p, struct st_error **err)
|
||||
*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;
|
||||
case ST_TOK_TYPE: {
|
||||
/* Value annotation: capture it, then require an argument value.
|
||||
* (A type annotation in entry position prefixes a VALUE, so the
|
||||
* annotated token is always an argument, never a property key.) */
|
||||
struct st_token ann_tok = p->tok;
|
||||
struct st_kdl_arg *a;
|
||||
|
||||
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 type annotation");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
a = arg_new(&p->tok, &ann_tok, true);
|
||||
if (a == NULL) {
|
||||
*err = err_at_owned(p->tok.span, "out of memory");
|
||||
node_free(n);
|
||||
return NULL;
|
||||
}
|
||||
last_line = p->tok.span.line;
|
||||
p_consume(p);
|
||||
if (args_tail == NULL) {
|
||||
n->args = args_tail = a;
|
||||
} else {
|
||||
args_tail->next = a;
|
||||
args_tail = a;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
if (!tok_is_value(k)) {
|
||||
*err = err_at_owned(p->tok.span,
|
||||
@@ -491,13 +543,38 @@ parse_node(struct st_parser *p, struct st_error **err)
|
||||
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;
|
||||
{
|
||||
struct st_token vann_tok = { 0 };
|
||||
bool has_vann = false;
|
||||
|
||||
if (k == ST_TOK_TYPE) {
|
||||
vann_tok = p->tok;
|
||||
has_vann = true;
|
||||
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 type annotation");
|
||||
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, &vann_tok, has_vann);
|
||||
}
|
||||
pr = prop_new(&val_tok, &p->tok);
|
||||
if (pr == NULL) {
|
||||
*err = err_at_owned(val_tok.span, "out of memory");
|
||||
node_free(n);
|
||||
@@ -516,7 +593,7 @@ parse_node(struct st_parser *p, struct st_error **err)
|
||||
}
|
||||
/* Argument. */
|
||||
{
|
||||
struct st_kdl_arg *a = arg_new(&val_tok);
|
||||
struct st_kdl_arg *a = arg_new(&val_tok, NULL, false);
|
||||
|
||||
if (a == NULL) {
|
||||
*err = err_at_owned(val_tok.span, "out of memory");
|
||||
|
||||
+1053
File diff suppressed because it is too large
Load Diff
+126
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* value.h - typed KDL value model for stupidtools (todo 8).
|
||||
*
|
||||
* The parser (todo 7) produces RAW token references (src/kdl/ast.h); this
|
||||
* module INTERPRETS them into typed values. A KDL value is one of:
|
||||
*
|
||||
* - string (identifier, quoted, raw, or multi-line; escapes resolved)
|
||||
* - integer (i64) and unsigned (u64) — numbers without a decimal point
|
||||
* - float (f64) — numbers with a fraction/exponent, plus #inf/#-inf/#nan
|
||||
* - bool (#true / #false)
|
||||
* - null (#null)
|
||||
*
|
||||
* plus an optional type-annotation string (KDL §3.8), e.g. the `u8` in
|
||||
* `(u8)42`, captured into `annotation` (an owned NUL-terminated copy, or
|
||||
* NULL when the value was not annotated).
|
||||
*
|
||||
* SUBSET (documented deviation from KDL 2.0.0)
|
||||
* --------------------------------------------
|
||||
* KDL reserves the integer-width annotations i8/i16/i32/i64/isize and
|
||||
* u8/u16/u32/u64/usize (plus i128/u128) and the float annotations f32/f64.
|
||||
* This tool's value model supports at most 64-bit integers: an `i128` or
|
||||
* `u128` annotation yields a clear "unsupported width" error
|
||||
* (ST_ERR_KDL_PARSE) instead of a value. The `u*` annotations select the
|
||||
* unsigned (u64) interpretation; `i*` the signed (i64) one; `f32`/`f64`
|
||||
* the float interpretation. Any other annotation is captured verbatim and
|
||||
* leaves the literal's natural interpretation unchanged (the DSL schema,
|
||||
* todo 9, is responsible for rejecting application-invalid annotations).
|
||||
*
|
||||
* NUMBER INTERPRETATION (KDL §3.14)
|
||||
* ---------------------------------
|
||||
* Radix prefixes 0x/0o/0b (case-insensitive) and decimal; underscores
|
||||
* allowed between/after digits; optional leading +/-. A literal with a
|
||||
* decimal point or exponent is a float; otherwise an integer. Integers
|
||||
* overflow-check to i64 (default) or u64 (u*-annotated) and produce a
|
||||
* spanned error on overflow. #inf/#-inf/#nan map to INFINITY/-INFINITY/NAN
|
||||
* (math.h macros; no libm linkage is required).
|
||||
*
|
||||
* STRING UNESCAPING (KDL §3.9/3.11/3.12)
|
||||
* --------------------------------------
|
||||
* Quoted and multi-line strings resolve the KDL escapes (\n \r \t \\ \" \b
|
||||
* \f \s \u{...}) plus the lenient extras \' \a \v \xHH \U{...} (harmless
|
||||
* superset; the lexer already rejects non-KDL escapes, so these only matter
|
||||
* for directly-constructed token refs). A backslash followed by literal
|
||||
* whitespace/newline is a whitespace escape and vanishes. Unknown escapes,
|
||||
* a lone trailing backslash, and non-scalar-value unicode escapes are
|
||||
* spanned errors. Multi-line strings additionally remove the leading
|
||||
* newline and dedent by the closing line's whitespace (see value.c for the
|
||||
* exact rule and its documented simplifications). `\0` is REJECTED: KDL
|
||||
* forbids U+0000 and our value is a NUL-terminated C string, so a NUL byte
|
||||
* cannot be represented.
|
||||
*
|
||||
* OWNERSHIP
|
||||
* ---------
|
||||
* st_kdl_value_from_token() writes into a caller-provided `struct
|
||||
* st_kdl_value`; the string payload and the annotation are heap-allocated
|
||||
* by this module and released by st_kdl_value_free() (NULL is a safe no-op;
|
||||
* it also frees nothing for non-string kinds). The token ref's text slice
|
||||
* remains borrowed.
|
||||
*
|
||||
* ERRORS
|
||||
* ------
|
||||
* All failures return an owned st_error of category ST_ERR_KDL_PARSE whose
|
||||
* span is heap-allocated in the same block as the error (the parser.c
|
||||
* pattern), so err->span stays valid until st_error_free(err). On error the
|
||||
* output value is left zeroed (kind ST_KDL_VAL_NULL) and nothing leaks.
|
||||
*
|
||||
* Copyright (c) 2026 huntedbytheirs
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef ST_KDL_VALUE_H
|
||||
#define ST_KDL_VALUE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "ast.h" /* struct st_kdl_token_ref */
|
||||
|
||||
struct st_error;
|
||||
|
||||
/* The tagged kinds a KDL value can take. */
|
||||
enum st_kdl_value_kind {
|
||||
ST_KDL_VAL_STRING = 0, /* as.str (owned, NUL-terminated) */
|
||||
ST_KDL_VAL_INT, /* as.i (i64) */
|
||||
ST_KDL_VAL_UINT, /* as.u (u64) */
|
||||
ST_KDL_VAL_FLOAT, /* as.f (f64) */
|
||||
ST_KDL_VAL_BOOL, /* as.b */
|
||||
ST_KDL_VAL_NULL, /* no payload */
|
||||
};
|
||||
|
||||
/* A typed KDL value. `annotation` is an owned NUL-terminated copy of the
|
||||
* type-annotation identifier, or NULL when the value was not annotated. */
|
||||
struct st_kdl_value {
|
||||
enum st_kdl_value_kind kind;
|
||||
union {
|
||||
char *str; /* STRING: owned, NUL-terminated */
|
||||
long long i; /* INT */
|
||||
unsigned long long u; /* UINT */
|
||||
double f; /* FLOAT */
|
||||
bool b; /* BOOL */
|
||||
} as;
|
||||
char *annotation; /* owned; may be NULL */
|
||||
};
|
||||
|
||||
/* Interpret a raw value token ref into `out` (no type annotation). On
|
||||
* success returns NULL and `out` is fully populated; on failure returns an
|
||||
* owned error and `out` is left zeroed. */
|
||||
struct st_error *st_kdl_value_from_token(const struct st_kdl_token_ref *tok,
|
||||
struct st_kdl_value *out);
|
||||
|
||||
/* Same, but with an explicit type-annotation ref (ST_TOK_TYPE) captured
|
||||
* into `out->annotation`. `annotation` may be NULL. */
|
||||
struct st_error *st_kdl_value_from_token_annotated(
|
||||
const struct st_kdl_token_ref *tok,
|
||||
const struct st_kdl_token_ref *annotation,
|
||||
struct st_kdl_value *out);
|
||||
|
||||
/* Release the payloads owned by `v` (string + annotation). NULL is a safe
|
||||
* no-op. */
|
||||
void st_kdl_value_free(struct st_kdl_value *v);
|
||||
|
||||
/* Stable display name for a value kind, e.g. "string". Unknown kinds yield
|
||||
* "?" rather than indexing out of range. */
|
||||
const char *st_kdl_value_kind_name(enum st_kdl_value_kind kind);
|
||||
|
||||
#endif /* ST_KDL_VALUE_H */
|
||||
Reference in New Issue
Block a user