32 char name[PC_GQL_NAME_MAX];
40 char name[PC_GQL_NAME_MAX];
51 Node nodes[PC_GQL_MAX_NODES];
52 Arg args[PC_GQL_MAX_ARGS];
53 char strbuf[PC_GQL_STRBUF];
60 int scope[PC_GQL_MAX_ARGS];
62 pc_gql_resolver_fn resolver;
63 char path[PC_GQL_PATH_MAX];
69 if (s_gql.nnodes >= PC_GQL_MAX_NODES)
71 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
74 Node *n = &s_gql.nodes[s_gql.nnodes];
80 return s_gql.nnodes++;
95 if (c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' || c ==
',')
101 while (L.p < L.e && *L.p !=
'\n')
116 return L.p < L.e ? *L.p :
'\0';
120void gql_flag_parse_err()
122 if (s_gql.err == pc_gql_result::PC_GQL_OK)
124 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
128bool is_name_start(
char c)
130 return (c >=
'A' && c <=
'Z') || (c >=
'a' && c <=
'z') || c ==
'_';
134 return is_name_start(c) || (c >=
'0' && c <=
'9');
142bool parse_name(Lex &L,
char *out,
size_t cap)
145 if (L.p >= L.e || !is_name_start(*L.p))
149 const size_t limit = cap < (size_t)PC_GQL_NAME_MAX ? cap : (size_t)PC_GQL_NAME_MAX;
151 while (L.p < L.e && is_name(*L.p))
158 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
168const char *intern(
const char *s,
int len)
170 if (s_gql.str_len + len + 1 > PC_GQL_STRBUF)
172 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
175 char *dst = s_gql.strbuf + s_gql.str_len;
178 s_gql.str_len += len + 1;
182bool parse_value(Lex &L, pc_gql_value *v)
188 char tmp[PC_GQL_STRBUF];
190 while (L.p < L.e && *L.p !=
'"')
193 if (ch ==
'\\' && L.p < L.e)
221 if (n >= (
int)
sizeof(tmp) - 1)
223 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
230 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
234 const char *s = intern(tmp, n);
239 v->type = pc_gql_type::PC_GQL_STR;
243 if (c ==
'-' || (c >=
'0' && c <=
'9'))
254 bool is_float =
false;
255 unsigned long long ipart = 0;
257 while (L.p < L.e && *L.p >=
'0' && *L.p <=
'9')
259 ipart = ipart * 10ULL + (unsigned)(*L.p -
'0');
263 fval = (double)ipart;
264 if (L.p < L.e && *L.p ==
'.')
269 while (L.p < L.e && *L.p >=
'0' && *L.p <=
'9')
272 fval += (double)(*L.p -
'0') / scale;
277 if (L.p < L.e && (*L.p ==
'e' || *L.p ==
'E'))
282 if (L.p < L.e && (*L.p ==
'+' || *L.p ==
'-'))
284 eneg = (*L.p++ ==
'-');
287 while (L.p < L.e && *L.p >=
'0' && *L.p <=
'9')
290 ex = (ex < 400) ? ex * 10 + (*L.p -
'0') : ex;
294 for (
int k = 0; k < ex; k++)
298 fval = eneg ? fval / m : fval * m;
302 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
307 v->type = pc_gql_type::PC_GQL_FLOAT;
308 v->f = neg ? -fval : fval;
312 v->type = pc_gql_type::PC_GQL_INT;
315 v->i = neg ? -(
long long)ipart : (long long)ipart;
321 if (parse_name(L, kw,
sizeof(kw)))
323 if (strcmp(kw,
"true") == 0)
325 v->type = pc_gql_type::PC_GQL_BOOL;
329 if (strcmp(kw,
"false") == 0)
331 v->type = pc_gql_type::PC_GQL_BOOL;
335 if (strcmp(kw,
"null") == 0)
337 v->type = pc_gql_type::PC_GQL_NULL;
341 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
345int parse_selection(Lex &L,
int depth);
347int parse_field(Lex &L,
int depth)
349 int idx = new_node();
354 if (!parse_name(L, s_gql.nodes[idx].name,
sizeof(s_gql.nodes[idx].name)))
356 gql_flag_parse_err();
365 while (peek(L) !=
')')
367 if (s_gql.nargs >= PC_GQL_MAX_ARGS)
369 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
372 Arg *a = &s_gql.args[s_gql.nargs];
373 if (!parse_name(L, a->name,
sizeof(a->name)))
375 gql_flag_parse_err();
380 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
384 if (!parse_value(L, &a->val))
396 s_gql.nodes[idx].first_arg = first;
397 s_gql.nodes[idx].n_args = count;
402 s_gql.nodes[idx].first_child = parse_selection(L, depth + 1);
404 return s_gql.err != pc_gql_result::PC_GQL_OK ? -1 : idx;
407int parse_selection(Lex &L,
int depth)
409 if (depth > PC_GQL_MAX_DEPTH)
411 s_gql.err = pc_gql_result::PC_GQL_ERR_LIMIT;
416 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
422 while (peek(L) !=
'}')
426 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
429 int f = parse_field(L, depth);
440 s_gql.nodes[prev].next_sib = f;
448bool parse_document(Lex &L)
453 char kw[PC_GQL_NAME_MAX];
454 if (!parse_name(L, kw,
sizeof(kw)) || strcmp(kw,
"query") != 0)
456 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
461 char opname[PC_GQL_NAME_MAX];
462 if (!parse_name(L, opname,
sizeof(opname)))
464 gql_flag_parse_err();
469 s_gql.root = parse_selection(L, 1);
470 if (s_gql.err != pc_gql_result::PC_GQL_OK)
476 s_gql.err = pc_gql_result::PC_GQL_ERR_PARSE;
490void w_raw(Writer &w,
const char *s,
size_t len)
496 if (w.n + len > w.cap)
501 memcpy(w.o + w.n, s, len);
504void w_str(Writer &w,
const char *s)
506 w_raw(w, s, strnlen(s, w.cap + 1));
508void w_json_str(Writer &w,
const char *s)
511 for (
const char *p = s; *p; p++)
513 unsigned char ch = (
unsigned char)*p;
537 pc_sb sb_u = {u,
sizeof(u), 0,
true};
548 w_raw(w, (
const char *)&ch, 1);
553void w_scalar(Writer &w,
const pc_gql_value *v)
558 case pc_gql_type::PC_GQL_INT: {
559 pc_sb sb_b = {b,
sizeof(b), 0,
true};
568 case pc_gql_type::PC_GQL_FLOAT: {
569 pc_sb sb_b2 = {b,
sizeof(b), 0,
true};
570 pc_sb_g(&sb_b2, (
double)(v->f), 6);
578 case pc_gql_type::PC_GQL_BOOL:
579 w_str(w, v->b ?
"true" :
"false");
581 case pc_gql_type::PC_GQL_STR:
582 w_json_str(w, v->s ? v->s :
"");
590void emit_field(Writer &w,
int idx,
int path_len)
592 Node *node = &s_gql.nodes[idx];
598 if (plen + 1 >= PC_GQL_PATH_MAX)
603 s_gql.path[plen++] =
'.';
605 int nl = (int)strnlen(node->name, PC_GQL_PATH_MAX);
606 if (plen + nl >= PC_GQL_PATH_MAX)
611 memcpy(s_gql.path + plen, node->name, nl);
613 s_gql.path[plen] =
'\0';
617 for (
int a = 0; a < node->n_args; a++)
622 if (s_gql.scope_n < PC_GQL_MAX_ARGS)
624 s_gql.scope[s_gql.scope_n++] = node->first_arg + a;
629 w_json_str(w, node->name);
632 if (node->first_child >= 0)
636 for (
int c = node->first_child; c >= 0; c = s_gql.nodes[c].next_sib)
643 emit_field(w, c, plen);
650 v.type = pc_gql_type::PC_GQL_NULL;
651 pc_gql_args view = {s_gql.scope, s_gql.scope_n};
652 if (s_gql.resolver && s_gql.resolver(s_gql.path, &view, &v))
662 s_gql.scope_n -= pushed;
663 s_gql.path[path_len] =
'\0';
667bool pc_gql_arg_int(
const pc_gql_args *args,
const char *name,
long long *out)
673 for (
int k = 0; k < args->count; k++)
675 Arg *a = &s_gql.args[args->idx[k]];
676 if (strcmp(a->name, name) == 0 && a->val.type == pc_gql_type::PC_GQL_INT)
684bool pc_gql_arg_str(
const pc_gql_args *args,
const char *name,
const char **out)
690 for (
int k = 0; k < args->count; k++)
692 Arg *a = &s_gql.args[args->idx[k]];
693 if (strcmp(a->name, name) == 0 && a->val.type == pc_gql_type::PC_GQL_STR)
701bool pc_gql_arg_bool(
const pc_gql_args *args,
const char *name,
bool *out)
707 for (
int k = 0; k < args->count; k++)
709 Arg *a = &s_gql.args[args->idx[k]];
710 if (strcmp(a->name, name) == 0 && a->val.type == pc_gql_type::PC_GQL_BOOL)
719pc_gql_result pc_graphql_execute(
const char *query,
size_t len, pc_gql_resolver_fn resolver,
char *out,
size_t cap)
726 s_gql.err = pc_gql_result::PC_GQL_OK;
727 s_gql.resolver = resolver;
728 s_gql.path[0] =
'\0';
730 Lex L = {query, query + (query ? len : 0)};
731 if (!query || !out || cap == 0)
733 return pc_gql_result::PC_GQL_ERR_PARSE;
736 if (!parse_document(L))
739 (s_gql.err == pc_gql_result::PC_GQL_ERR_LIMIT) ?
"query exceeds a configured limit" :
"syntax error";
740 Writer w = {out, cap, 0,
false};
741 w_str(w,
"{\"errors\":[{\"message\":");
744 if (!w.ovf && w.n < cap)
750 return s_gql.err != pc_gql_result::PC_GQL_OK ? s_gql.err : pc_gql_result::PC_GQL_ERR_PARSE;
753 Writer w = {out, cap, 0,
false};
754 w_str(w,
"{\"data\":{");
756 for (
int c = s_gql.root; c >= 0; c = s_gql.nodes[c].next_sib)
766 if (w.ovf || w.n >= cap)
768 return pc_gql_result::PC_GQL_ERR_OVERFLOW;
771 return pc_gql_result::PC_GQL_OK;
Zero-heap GraphQL query subset - parser + executor (PC_ENABLE_GRAPHQL).
Bounded no-heap string builder that fails closed on overflow (one shared copy).
void pc_sb_g(pc_sb *b, double v, unsigned sig)
Append v with sig significant digits, choosing fixed or scientific form - the printf "%....
size_t pc_sb_finish(pc_sb *b)
NUL-terminate and return the built length, or 0 if the build overflowed.
void pc_sb_hex(pc_sb *b, uint64_t v, unsigned min_digits)
Append v as lowercase hex, zero-padded to at least min_digits (printf "%0Nx").
void pc_sb_i64(pc_sb *b, int64_t v)
Append v as signed decimal (64-bit), with a leading '-' when negative.
void pc_sb_put(pc_sb *b, const char *s)
Append NUL-terminated s; leaves the buffer untouched and clears ok if it would not fit.
Bump-append target; ok latches false once an append would overflow cap.