/*
* c_runtime.c -- C-acceleration runtime for tarantool-protobuf.
*
* Phase 1 (bd-mq7): descriptor -> plan compiler. Walks a finalized
* Lua descriptor table and produces an opaque `pb_plan` userdata.
* Encode/decode entry points arrive with bd-3b/3c/etc.; this file
* only carries the plan compilation surface and enough introspection
* to make a smoke test possible.
*
* Conventions follow docs/specs/c_accel_strategy.md.
*/
/* strdup is POSIX, not C99; declare we want the POSIX surface from
* <string.h> before any system header pulls it in. */
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include <module.h>
#include <lauxlib.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#define PB_PLAN_MT "pb.plan"
#define PB_ABI_VERSION "1"
/* ---------------------------------------------------------------- *
* Kind / wire-type taxonomy. *
* *
* Mirrors runtime/pb/wire.lua's TYPE_INFO. The numbering is *
* internal — only the C runtime needs to agree with itself. *
* ---------------------------------------------------------------- */
enum {
PB_KIND_NONE = 0,
PB_KIND_INT32,
PB_KIND_INT64,
PB_KIND_UINT32,
PB_KIND_UINT64,
PB_KIND_SINT32,
PB_KIND_SINT64,
PB_KIND_FIXED32,
PB_KIND_FIXED64,
PB_KIND_SFIXED32,
PB_KIND_SFIXED64,
PB_KIND_FLOAT,
PB_KIND_DOUBLE,
PB_KIND_BOOL,
PB_KIND_STRING,
PB_KIND_BYTES,
PB_KIND_ENUM,
PB_KIND_MESSAGE,
PB_KIND_MAP,
};
/* Wire types per proto3 spec. */
enum {
PB_WIRE_VARINT = 0,
PB_WIRE_I64 = 1,
PB_WIRE_LEN = 2,
PB_WIRE_I32 = 5,
};
struct kind_info {
const char *proto_type;
uint8_t kind;
uint8_t wire_type;
};
/* Lookup table keyed by proto3 field type string. Linear scan is fine —
* a finalized descriptor calls this once per field at plan-compile time,
* not per encode/decode. */
static const struct kind_info kind_table[] = {
{"int32", PB_KIND_INT32, PB_WIRE_VARINT},
{"int64", PB_KIND_INT64, PB_WIRE_VARINT},
{"uint32", PB_KIND_UINT32, PB_WIRE_VARINT},
{"uint64", PB_KIND_UINT64, PB_WIRE_VARINT},
{"sint32", PB_KIND_SINT32, PB_WIRE_VARINT},
{"sint64", PB_KIND_SINT64, PB_WIRE_VARINT},
{"bool", PB_KIND_BOOL, PB_WIRE_VARINT},
{"fixed32", PB_KIND_FIXED32, PB_WIRE_I32},
{"sfixed32", PB_KIND_SFIXED32, PB_WIRE_I32},
{"float", PB_KIND_FLOAT, PB_WIRE_I32},
{"fixed64", PB_KIND_FIXED64, PB_WIRE_I64},
{"sfixed64", PB_KIND_SFIXED64, PB_WIRE_I64},
{"double", PB_KIND_DOUBLE, PB_WIRE_I64},
{"string", PB_KIND_STRING, PB_WIRE_LEN},
{"bytes", PB_KIND_BYTES, PB_WIRE_LEN},
};
static const struct kind_info *
lookup_kind(const char *proto_type)
{
if (proto_type == NULL)
return NULL;
for (size_t i = 0; i < sizeof(kind_table) / sizeof(kind_table[0]); i++) {
if (strcmp(kind_table[i].proto_type, proto_type) == 0)
return &kind_table[i];
}
return NULL;
}
/* ---------------------------------------------------------------- *
* Plan struct layout. *
* *
* Spec: docs/specs/c_accel_strategy.md § The plan userdata. *
* Simplifications from the spec for mq7: *
* - oneofs[] populated but member dispatch lives in 3i *
* - extension_range_* populated but dispatch lives in 3i *
* - sub_plan_idx points into `sub_plans_ref` table (1-based) *
* - Field name strings live in a Lua table keyed by 1..n; *
* lookup via `lua_rawgeti(L, names, i+1)` per spec. *
* ---------------------------------------------------------------- */
typedef struct pb_plan_field {
uint32_t field_number;
uint8_t wire_type;
uint8_t kind;
uint8_t packed;
uint8_t repeated;
uint8_t optional;
uint8_t tag_len;
uint8_t tag_bytes[5];
int sub_plan_idx; /* 1-based into sub_plans table; 0 if none */
uint8_t map_key_kind;
uint8_t map_value_kind;
int map_value_sub_plan_idx; /* 1-based; 0 if value is scalar */
int oneof_idx; /* 0-based into plan->oneofs; -1 if none */
int enum_ref; /* LUA_REGISTRYINDEX ref for enum desc; LUA_NOREF if none */
} pb_plan_field;
typedef struct pb_plan_oneof {
char *name; /* malloc'd */
int n_members;
int *member_indices; /* indices into plan->fields */
} pb_plan_oneof;
typedef struct pb_plan {
char *name; /* malloc'd descriptor name */
int n_fields;
pb_plan_field *fields;
int n_oneofs;
pb_plan_oneof *oneofs;
int extension_range_start;
int extension_range_end;
uint8_t has_override;
int override_encode_ref; /* LUA_NOREF if absent */
int override_decode_ref;
int field_names_ref; /* table { [1]=name1, ... } */
int sub_plans_ref; /* table { [1]=plan_userdata, ... } */
} pb_plan;
/* ---------------------------------------------------------------- *
* Tag encoding. *
* *
* Pre-encodes the (field_number << 3) | wire_type varint so the *
* hot encode path emits a fixed memcpy instead of recomputing. *
* Up to 5 bytes for any legal field number (2^29 - 1 max). *
* ---------------------------------------------------------------- */
static void
encode_tag(uint32_t field_number, uint8_t wire_type,
uint8_t *out, uint8_t *out_len)
{
uint64_t v = ((uint64_t)field_number << 3) | wire_type;
uint8_t i = 0;
while (v >= 0x80) {
out[i++] = (uint8_t)(v | 0x80);
v >>= 7;
}
out[i++] = (uint8_t)v;
*out_len = i;
}
/* ---------------------------------------------------------------- *
* Plan lifecycle: alloc / free. *
* ---------------------------------------------------------------- */
static void
plan_free(lua_State *L, pb_plan *p)
{
if (p->name != NULL)
free(p->name);
if (p->fields != NULL) {
for (int i = 0; i < p->n_fields; i++) {
if (p->fields[i].enum_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, p->fields[i].enum_ref);
}
free(p->fields);
}
if (p->oneofs != NULL) {
for (int i = 0; i < p->n_oneofs; i++) {
free(p->oneofs[i].name);
free(p->oneofs[i].member_indices);
}
free(p->oneofs);
}
if (p->override_encode_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, p->override_encode_ref);
if (p->override_decode_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, p->override_decode_ref);
if (p->field_names_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, p->field_names_ref);
if (p->sub_plans_ref != LUA_NOREF)
luaL_unref(L, LUA_REGISTRYINDEX, p->sub_plans_ref);
memset(p, 0, sizeof(*p));
p->override_encode_ref = LUA_NOREF;
p->override_decode_ref = LUA_NOREF;
p->field_names_ref = LUA_NOREF;
p->sub_plans_ref = LUA_NOREF;
}
static int
plan_gc(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
plan_free(L, p);
return 0;
}
static int
plan_tostring(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
lua_pushfstring(L, "pb.plan: %s (n_fields=%d)",
p->name != NULL ? p->name : "(unnamed)",
p->n_fields);
return 1;
}
/* ---------------------------------------------------------------- *
* Field compilation. *
* *
* Reads one descriptor-field table (at the top of the stack) and *
* fills the corresponding pb_plan_field. Recurses into sub-message *
* plans by calling compile_plan_impl. *
* *
* Stack expectations on entry: *
* -1: field-descriptor table (e.g. {name="foo", id=1, kind=...})*
* sub_plans_stack_idx: the sub-plans table being filled *
* *
* Stack on exit: same (we pop everything we push). *
* ---------------------------------------------------------------- */
static int compile_plan_impl(lua_State *L, int desc_idx);
/* Append a sub-plan userdata (at -1) to the sub-plans table at
* sub_plans_idx; return its 1-based index. Pops the userdata. */
static int
push_sub_plan(lua_State *L, int sub_plans_idx)
{
int n = (int)lua_objlen(L, sub_plans_idx);
lua_rawseti(L, sub_plans_idx, n + 1);
return n + 1;
}
/* Resolve a sub-message descriptor (at stack idx `sub_desc_idx`) into a
* 1-based index into the sub-plans table. Stack-neutral: pushes/pops the
* intermediate desc copy and plan userdata internally. */
static int
resolve_sub_plan(lua_State *L, int sub_desc_idx, int sub_plans_idx)
{
int saved_top = lua_gettop(L);
/* compile_plan_impl is idempotent — if desc.c_plan exists it just
* leaves the cached userdata on top. */
lua_pushvalue(L, sub_desc_idx);
int dup_idx = lua_gettop(L);
compile_plan_impl(L, dup_idx);
if (!lua_isuserdata(L, -1))
luaL_error(L, "compile_plan_impl did not return a userdata");
int idx = push_sub_plan(L, sub_plans_idx); /* pops plan userdata */
lua_settop(L, saved_top); /* drop sub_desc copy */
return idx;
}
/* Compile one field. `f_desc_idx` is the absolute stack index of the
* field descriptor table. `field` points at the pb_plan_field slot to
* fill. `field_names_idx`, `sub_plans_idx` are absolute indices of the
* field-names and sub-plans tables being populated. `field_idx_1based`
* is the 1-based position used for the field-names lookup table. */
static void
compile_field(lua_State *L, int f_desc_idx, pb_plan_field *field,
int field_names_idx, int sub_plans_idx, int field_idx_1based)
{
memset(field, 0, sizeof(*field));
field->enum_ref = LUA_NOREF;
field->oneof_idx = -1;
/* field number */
lua_getfield(L, f_desc_idx, "id");
if (!lua_isnumber(L, -1))
luaL_error(L, "field descriptor missing 'id'");
field->field_number = (uint32_t)lua_tointeger(L, -1);
lua_pop(L, 1);
/* name (interned in field_names_ref under field_idx_1based) */
lua_getfield(L, f_desc_idx, "name");
if (!lua_isstring(L, -1))
luaL_error(L, "field descriptor missing 'name'");
lua_pushvalue(L, -1); /* dup */
lua_rawseti(L, field_names_idx, field_idx_1based);
lua_pop(L, 1);
/* repeated / packed / optional */
lua_getfield(L, f_desc_idx, "repeated");
field->repeated = lua_toboolean(L, -1) ? 1 : 0;
lua_pop(L, 1);
lua_getfield(L, f_desc_idx, "packed");
field->packed = lua_toboolean(L, -1) ? 1 : 0;
lua_pop(L, 1);
lua_getfield(L, f_desc_idx, "optional");
field->optional = lua_toboolean(L, -1) ? 1 : 0;
lua_pop(L, 1);
/* kind dispatch on desc.kind */
lua_getfield(L, f_desc_idx, "kind");
const char *kind_str = lua_tostring(L, -1);
if (kind_str == NULL)
luaL_error(L, "field descriptor missing 'kind'");
uint8_t element_wire_type;
if (strcmp(kind_str, "scalar") == 0) {
lua_pop(L, 1); /* pop kind */
lua_getfield(L, f_desc_idx, "proto_type");
const char *pt = lua_tostring(L, -1);
const struct kind_info *ki = lookup_kind(pt);
if (ki == NULL)
luaL_error(L, "unknown scalar proto_type: %s",
pt != NULL ? pt : "(nil)");
field->kind = ki->kind;
element_wire_type = ki->wire_type;
lua_pop(L, 1);
} else if (strcmp(kind_str, "enum") == 0) {
lua_pop(L, 1);
field->kind = PB_KIND_ENUM;
element_wire_type = PB_WIRE_VARINT;
lua_getfield(L, f_desc_idx, "enum");
if (lua_istable(L, -1)) {
field->enum_ref = luaL_ref(L, LUA_REGISTRYINDEX);
} else {
lua_pop(L, 1);
}
} else if (strcmp(kind_str, "message") == 0) {
lua_pop(L, 1);
field->kind = PB_KIND_MESSAGE;
element_wire_type = PB_WIRE_LEN;
lua_getfield(L, f_desc_idx, "message");
if (!lua_istable(L, -1))
luaL_error(L, "message field '%s' missing 'message' descriptor",
"?");
int sub_desc = lua_gettop(L);
field->sub_plan_idx = resolve_sub_plan(L, sub_desc, sub_plans_idx);
lua_pop(L, 1); /* sub-desc table */
} else if (strcmp(kind_str, "map") == 0) {
lua_pop(L, 1);
field->kind = PB_KIND_MAP;
element_wire_type = PB_WIRE_LEN;
/* key */
lua_getfield(L, f_desc_idx, "key");
if (!lua_istable(L, -1))
luaL_error(L, "map field missing 'key' descriptor");
lua_getfield(L, -1, "proto_type");
const struct kind_info *ki = lookup_kind(lua_tostring(L, -1));
if (ki == NULL)
luaL_error(L, "map key has unknown proto_type");
field->map_key_kind = ki->kind;
lua_pop(L, 2); /* proto_type + key */
/* value */
lua_getfield(L, f_desc_idx, "value");
if (!lua_istable(L, -1))
luaL_error(L, "map field missing 'value' descriptor");
lua_getfield(L, -1, "kind");
const char *vk = lua_tostring(L, -1);
lua_pop(L, 1);
if (vk != NULL && strcmp(vk, "scalar") == 0) {
lua_getfield(L, -1, "proto_type");
const struct kind_info *vki = lookup_kind(lua_tostring(L, -1));
if (vki == NULL)
luaL_error(L, "map value has unknown scalar proto_type");
field->map_value_kind = vki->kind;
lua_pop(L, 1);
} else if (vk != NULL && strcmp(vk, "message") == 0) {
field->map_value_kind = PB_KIND_MESSAGE;
lua_getfield(L, -1, "message");
if (lua_istable(L, -1)) {
int sub_desc = lua_gettop(L);
field->map_value_sub_plan_idx =
resolve_sub_plan(L, sub_desc, sub_plans_idx);
}
lua_pop(L, 1);
} else if (vk != NULL && strcmp(vk, "enum") == 0) {
field->map_value_kind = PB_KIND_ENUM;
} else {
luaL_error(L, "map value has unknown kind: %s",
vk != NULL ? vk : "(nil)");
}
lua_pop(L, 1); /* value table */
} else {
luaL_error(L, "unknown field kind: %s", kind_str);
return; /* unreachable */
}
/* Wire type: repeated+packed → LEN regardless of element type;
* repeated unpacked → element type per tag; singular → element. */
if (field->repeated && field->packed) {
field->wire_type = PB_WIRE_LEN;
} else {
field->wire_type = element_wire_type;
}
encode_tag(field->field_number, field->wire_type,
field->tag_bytes, &field->tag_len);
}
/* ---------------------------------------------------------------- *
* Oneof compilation. *
* *
* desc.oneofs is the hash table {[name]=members}. We walk it and *
* build pb_plan_oneof[]. For each member field, set its oneof_idx *
* to point back at the plan's oneof entry. *
* ---------------------------------------------------------------- */
static int
find_field_by_name(pb_plan *p, lua_State *L, int field_names_idx,
const char *name)
{
for (int i = 0; i < p->n_fields; i++) {
lua_rawgeti(L, field_names_idx, i + 1);
const char *fn = lua_tostring(L, -1);
int match = (fn != NULL && strcmp(fn, name) == 0);
lua_pop(L, 1);
if (match) return i;
}
return -1;
}
static void
compile_oneofs(lua_State *L, pb_plan *p, int desc_idx, int field_names_idx)
{
lua_getfield(L, desc_idx, "oneofs");
if (!lua_istable(L, -1)) {
lua_pop(L, 1);
return;
}
/* First pass: count. */
int count = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
count++;
lua_pop(L, 1);
}
if (count == 0) {
lua_pop(L, 1);
return;
}
p->oneofs = (pb_plan_oneof *)calloc(count, sizeof(pb_plan_oneof));
p->n_oneofs = count;
int idx = 0;
lua_pushnil(L);
while (lua_next(L, -2) != 0) {
/* key at -2 (oneof name), value at -1 (members array) */
const char *oname = lua_tostring(L, -2);
p->oneofs[idx].name = strdup(oname != NULL ? oname : "");
int n_members = (int)lua_objlen(L, -1);
p->oneofs[idx].n_members = n_members;
p->oneofs[idx].member_indices = (int *)calloc(n_members, sizeof(int));
for (int j = 0; j < n_members; j++) {
lua_rawgeti(L, -1, j + 1);
const char *mname = lua_tostring(L, -1);
int fi = find_field_by_name(p, L, field_names_idx,
mname != NULL ? mname : "");
lua_pop(L, 1);
p->oneofs[idx].member_indices[j] = fi;
if (fi >= 0)
p->fields[fi].oneof_idx = idx;
}
lua_pop(L, 1); /* value */
idx++;
}
lua_pop(L, 1); /* oneofs table */
}
/* ---------------------------------------------------------------- *
* Main compile entry. *
* *
* Idempotent. If desc.c_plan exists and is a pb.plan userdata, *
* returns it. Otherwise allocates a new one, stashes it on *
* desc.c_plan BEFORE recursing into sub-plans (breaks cycles for *
* self-referencing messages like Person.friends → Person), then *
* walks fields and oneofs. *
* ---------------------------------------------------------------- */
/* Lua 5.1 lacks lua_absindex; LuaJIT's compatibility layer too. */
static inline int
abs_idx(lua_State *L, int idx)
{
if (idx < 0 && idx > LUA_REGISTRYINDEX)
return lua_gettop(L) + idx + 1;
return idx;
}
static int
compile_plan_impl(lua_State *L, int desc_idx)
{
desc_idx = abs_idx(L, desc_idx);
/* Idempotency check. */
lua_getfield(L, desc_idx, "c_plan");
if (lua_isuserdata(L, -1)) {
/* Already compiled; leave on top of stack and pop the original
* desc-table push pattern wasn't done — caller still owns. */
return 1;
}
lua_pop(L, 1);
/* Allocate the plan userdata and stash it immediately. */
pb_plan *p = (pb_plan *)lua_newuserdata(L, sizeof(pb_plan));
memset(p, 0, sizeof(*p));
p->override_encode_ref = LUA_NOREF;
p->override_decode_ref = LUA_NOREF;
p->field_names_ref = LUA_NOREF;
p->sub_plans_ref = LUA_NOREF;
luaL_getmetatable(L, PB_PLAN_MT);
lua_setmetatable(L, -2);
int plan_idx = lua_gettop(L);
/* Stash on desc.c_plan first to break sub-message cycles. */
lua_pushvalue(L, plan_idx);
lua_setfield(L, desc_idx, "c_plan");
/* desc.name */
lua_getfield(L, desc_idx, "name");
const char *dname = lua_tostring(L, -1);
p->name = strdup(dname != NULL ? dname : "");
lua_pop(L, 1);
/* WKT override pointers — desc.encode / desc.decode. */
lua_getfield(L, desc_idx, "encode");
if (lua_isfunction(L, -1)) {
p->has_override = 1;
p->override_encode_ref = luaL_ref(L, LUA_REGISTRYINDEX);
} else {
lua_pop(L, 1);
}
lua_getfield(L, desc_idx, "decode");
if (lua_isfunction(L, -1)) {
p->has_override = 1;
p->override_decode_ref = luaL_ref(L, LUA_REGISTRYINDEX);
} else {
lua_pop(L, 1);
}
/* Extension ranges (proto2). desc.extension_ranges = {{start, end}, ...} */
lua_getfield(L, desc_idx, "extension_ranges");
if (lua_istable(L, -1) && lua_objlen(L, -1) >= 1) {
lua_rawgeti(L, -1, 1);
if (lua_istable(L, -1)) {
lua_rawgeti(L, -1, 1);
p->extension_range_start = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
lua_rawgeti(L, -1, 2);
p->extension_range_end = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
}
lua_pop(L, 1);
}
lua_pop(L, 1);
/* If this descriptor has an override, we skip field-walk entirely —
* the override owns encode/decode and the field array is unused. */
if (p->has_override) {
return 1;
}
/* Create the field-names and sub-plans tables. */
lua_newtable(L);
int field_names_idx = lua_gettop(L);
lua_newtable(L);
int sub_plans_idx = lua_gettop(L);
/* Walk desc.fields. */
lua_getfield(L, desc_idx, "fields");
if (!lua_istable(L, -1))
luaL_error(L, "descriptor '%s' has no 'fields'",
p->name != NULL ? p->name : "?");
int n_fields = (int)lua_objlen(L, -1);
int fields_table_idx = lua_gettop(L);
p->n_fields = n_fields;
p->fields = (pb_plan_field *)calloc(
n_fields > 0 ? n_fields : 1, sizeof(pb_plan_field));
for (int i = 0; i < n_fields; i++) {
lua_rawgeti(L, fields_table_idx, i + 1);
int f_desc_idx = lua_gettop(L);
compile_field(L, f_desc_idx, &p->fields[i],
field_names_idx, sub_plans_idx, i + 1);
lua_pop(L, 1);
}
lua_pop(L, 1); /* fields table */
/* Compile oneofs after fields so oneof_idx back-pointers can be set. */
compile_oneofs(L, p, desc_idx, field_names_idx);
/* Stash the field-names + sub-plans tables in the registry. */
lua_pushvalue(L, sub_plans_idx);
p->sub_plans_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1); /* sub_plans_idx (now unreferenced from stack) */
lua_pushvalue(L, field_names_idx);
p->field_names_ref = luaL_ref(L, LUA_REGISTRYINDEX);
lua_pop(L, 1);
/* Stack now has just the plan userdata on top. */
return 1;
}
static int
compile_plan_lua(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 1);
return compile_plan_impl(L, 1);
}
/* ---------------------------------------------------------------- *
* Introspection (for the smoke test and future debugging). *
* *
* These are intentionally lean — enough to let a Lua test assert *
* the plan-build produced sensible values without a C-side test *
* harness. *
* ---------------------------------------------------------------- */
static int
plan_n_fields(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
lua_pushinteger(L, p->n_fields);
return 1;
}
static int
plan_name(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
lua_pushstring(L, p->name != NULL ? p->name : "");
return 1;
}
static int
plan_field_info(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
int i = luaL_checkint(L, 2);
if (i < 1 || i > p->n_fields)
return luaL_error(L, "field index %d out of range [1, %d]",
i, p->n_fields);
pb_plan_field *f = &p->fields[i - 1];
lua_createtable(L, 0, 9);
lua_pushinteger(L, f->field_number);
lua_setfield(L, -2, "field_number");
lua_pushinteger(L, f->wire_type);
lua_setfield(L, -2, "wire_type");
lua_pushinteger(L, f->kind);
lua_setfield(L, -2, "kind");
lua_pushboolean(L, f->repeated);
lua_setfield(L, -2, "repeated");
lua_pushboolean(L, f->packed);
lua_setfield(L, -2, "packed");
lua_pushboolean(L, f->optional);
lua_setfield(L, -2, "optional");
lua_pushinteger(L, f->sub_plan_idx);
lua_setfield(L, -2, "sub_plan_idx");
lua_pushinteger(L, f->oneof_idx);
lua_setfield(L, -2, "oneof_idx");
lua_pushlstring(L, (const char *)f->tag_bytes, f->tag_len);
lua_setfield(L, -2, "tag_bytes");
/* Field name from cached table. */
lua_rawgeti(L, LUA_REGISTRYINDEX, p->field_names_ref);
lua_rawgeti(L, -1, i);
lua_remove(L, -2);
lua_setfield(L, -2, "name");
/* Map kinds, if any. */
if (f->kind == PB_KIND_MAP) {
lua_pushinteger(L, f->map_key_kind);
lua_setfield(L, -2, "map_key_kind");
lua_pushinteger(L, f->map_value_kind);
lua_setfield(L, -2, "map_value_kind");
lua_pushinteger(L, f->map_value_sub_plan_idx);
lua_setfield(L, -2, "map_value_sub_plan_idx");
}
return 1;
}
static int
plan_n_oneofs(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
lua_pushinteger(L, p->n_oneofs);
return 1;
}
static int
plan_oneof_info(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
int i = luaL_checkint(L, 2);
if (i < 1 || i > p->n_oneofs)
return luaL_error(L, "oneof index %d out of range [1, %d]",
i, p->n_oneofs);
pb_plan_oneof *o = &p->oneofs[i - 1];
lua_createtable(L, 0, 2);
lua_pushstring(L, o->name != NULL ? o->name : "");
lua_setfield(L, -2, "name");
lua_createtable(L, o->n_members, 0);
for (int j = 0; j < o->n_members; j++) {
lua_pushinteger(L, o->member_indices[j]);
lua_rawseti(L, -2, j + 1);
}
lua_setfield(L, -2, "member_indices");
return 1;
}
static int
plan_has_override(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
lua_pushboolean(L, p->has_override);
return 1;
}
static int
plan_sub_plan(lua_State *L)
{
pb_plan *p = (pb_plan *)luaL_checkudata(L, 1, PB_PLAN_MT);
int i = luaL_checkint(L, 2);
if (p->sub_plans_ref == LUA_NOREF || i < 1) {
lua_pushnil(L);
return 1;
}
lua_rawgeti(L, LUA_REGISTRYINDEX, p->sub_plans_ref);
lua_rawgeti(L, -1, i);
lua_remove(L, -2);
return 1;
}
/* ---------------------------------------------------------------- *
* Module entry. *
* ---------------------------------------------------------------- */
static const struct luaL_Reg c_runtime_methods[] = {
{"compile_plan", compile_plan_lua},
{"plan_n_fields", plan_n_fields},
{"plan_name", plan_name},
{"plan_field_info", plan_field_info},
{"plan_n_oneofs", plan_n_oneofs},
{"plan_oneof_info", plan_oneof_info},
{"plan_has_override", plan_has_override},
{"plan_sub_plan", plan_sub_plan},
{NULL, NULL},
};
static const struct luaL_Reg plan_mt_methods[] = {
{"__gc", plan_gc},
{"__tostring", plan_tostring},
{NULL, NULL},
};
LUA_API int
luaopen_pb_c_runtime(lua_State *L)
{
/* Register the plan metatable. */
luaL_newmetatable(L, PB_PLAN_MT);
luaL_register(L, NULL, plan_mt_methods);
lua_pop(L, 1);
/* Build the module table. */
lua_newtable(L);
luaL_register(L, NULL, c_runtime_methods);
lua_pushliteral(L, PB_ABI_VERSION);
lua_setfield(L, -2, "_abi_version");
/* Kind constants — exported so Lua tests can compare without
* duplicating the enum. */
lua_createtable(L, 0, 19);
lua_pushinteger(L, PB_KIND_NONE); lua_setfield(L, -2, "NONE");
lua_pushinteger(L, PB_KIND_INT32); lua_setfield(L, -2, "INT32");
lua_pushinteger(L, PB_KIND_INT64); lua_setfield(L, -2, "INT64");
lua_pushinteger(L, PB_KIND_UINT32); lua_setfield(L, -2, "UINT32");
lua_pushinteger(L, PB_KIND_UINT64); lua_setfield(L, -2, "UINT64");
lua_pushinteger(L, PB_KIND_SINT32); lua_setfield(L, -2, "SINT32");
lua_pushinteger(L, PB_KIND_SINT64); lua_setfield(L, -2, "SINT64");
lua_pushinteger(L, PB_KIND_FIXED32); lua_setfield(L, -2, "FIXED32");
lua_pushinteger(L, PB_KIND_FIXED64); lua_setfield(L, -2, "FIXED64");
lua_pushinteger(L, PB_KIND_SFIXED32); lua_setfield(L, -2, "SFIXED32");
lua_pushinteger(L, PB_KIND_SFIXED64); lua_setfield(L, -2, "SFIXED64");
lua_pushinteger(L, PB_KIND_FLOAT); lua_setfield(L, -2, "FLOAT");
lua_pushinteger(L, PB_KIND_DOUBLE); lua_setfield(L, -2, "DOUBLE");
lua_pushinteger(L, PB_KIND_BOOL); lua_setfield(L, -2, "BOOL");
lua_pushinteger(L, PB_KIND_STRING); lua_setfield(L, -2, "STRING");
lua_pushinteger(L, PB_KIND_BYTES); lua_setfield(L, -2, "BYTES");
lua_pushinteger(L, PB_KIND_ENUM); lua_setfield(L, -2, "ENUM");
lua_pushinteger(L, PB_KIND_MESSAGE); lua_setfield(L, -2, "MESSAGE");
lua_pushinteger(L, PB_KIND_MAP); lua_setfield(L, -2, "MAP");
lua_setfield(L, -2, "KIND");
lua_createtable(L, 0, 4);
lua_pushinteger(L, PB_WIRE_VARINT); lua_setfield(L, -2, "VARINT");
lua_pushinteger(L, PB_WIRE_I64); lua_setfield(L, -2, "I64");
lua_pushinteger(L, PB_WIRE_LEN); lua_setfield(L, -2, "LEN");
lua_pushinteger(L, PB_WIRE_I32); lua_setfield(L, -2, "I32");
lua_setfield(L, -2, "WIRE");
return 1;
}