schemagen: support objects with more than 32 properties
Replace the 32-bit `uint32_t seen` bitmask with a `std::vector<uint64_t>` bitset sized to the actual field count. `requiredMask` is now a vector of the same word count, and required-field checks use per-word masking so that optional fields do not cause false rejections. Adds big.schema.json + test_big.cpp regression tests covering the issue reproducer (40 required properties, missing/duplicate at index 32), plus run_tests.sh to exercise both test_gen.cpp and test_big.cpp. Fixes #3
This commit is contained in:
@@ -690,19 +690,35 @@ namespace {ns} {{"""
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _bitmask(self, obj, pred):
|
||||
mask = 0
|
||||
for i, fld in enumerate(obj.fields):
|
||||
if pred(fld):
|
||||
mask |= 1 << i
|
||||
return mask
|
||||
def _fieldcount(self):
|
||||
lines = [" int fieldCount(Kind k) const {", " switch (k) {"]
|
||||
for name, obj in self.b.objects.items():
|
||||
lines.append(f" case Kind::{name}: return {len(obj.fields)};")
|
||||
lines.append(" default: return 0;")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _reqmask(self):
|
||||
lines = [" uint32_t requiredMask(Kind k) const {", " switch (k) {"]
|
||||
lines = [
|
||||
" const std::vector<uint64_t> &requiredMask(Kind k) const {",
|
||||
" switch (k) {",
|
||||
]
|
||||
for name, obj in self.b.objects.items():
|
||||
m = self._bitmask(obj, lambda f: f.required)
|
||||
lines.append(f" case Kind::{name}: return {hex(m)}u;")
|
||||
lines.append(" default: return 0u;")
|
||||
words = (len(obj.fields) + 63) // 64
|
||||
req = [0] * words
|
||||
for i, fld in enumerate(obj.fields):
|
||||
if fld.required:
|
||||
req[i >> 6] |= 1 << (i & 63)
|
||||
init = ", ".join(f"{hex(w)}u" for w in req) if words else ""
|
||||
lines.append(f" case Kind::{name}: {{")
|
||||
lines.append(f" static const std::vector<uint64_t> m = {{ {init} }};")
|
||||
lines.append(" return m;")
|
||||
lines.append(" }")
|
||||
lines.append(" default: {")
|
||||
lines.append(" static const std::vector<uint64_t> empty;")
|
||||
lines.append(" return empty;")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
@@ -783,6 +799,11 @@ namespace {ns} {{"""
|
||||
if root_kind is not None and root_cat == event_cat:
|
||||
lines.append(" if (stack_.empty()) {")
|
||||
lines.append(f" stack_.push_back(Frame{{{root_kind}, &result_}});")
|
||||
if event_cat == "Obj":
|
||||
lines.append(" {")
|
||||
lines.append(f" int n = fieldCount({root_kind});")
|
||||
lines.append(" stack_.back().seen.assign((n + 63) / 64, 0);")
|
||||
lines.append(" }")
|
||||
lines.append(" return;")
|
||||
lines.append(" }")
|
||||
else:
|
||||
@@ -798,6 +819,10 @@ namespace {ns} {{"""
|
||||
lines.append(f" if (si.cat != Cat::{event_cat}) {{ reject(); return; }}")
|
||||
lines.append(" void *p = engage(f, true);")
|
||||
lines.append(" stack_.push_back(Frame{si.child, p});")
|
||||
lines.append(" if (isObjectKind(si.child)) {")
|
||||
lines.append(" int n = fieldCount(si.child);")
|
||||
lines.append(" stack_.back().seen.assign((n + 63) / 64, 0);")
|
||||
lines.append(" }")
|
||||
return "\n".join(lines)
|
||||
|
||||
def _builder(self, order):
|
||||
@@ -849,8 +874,8 @@ private:
|
||||
struct Frame {{
|
||||
Kind kind;
|
||||
void *dest;
|
||||
int field = -1; // object: selected field (-1 want key, -2 skip)
|
||||
uint32_t seen = 0; // bitmask of populated fields
|
||||
int field = -1; // object: selected field (-1 want key, -2 skip)
|
||||
std::vector<uint64_t> seen; // populated field bitset (object frames)
|
||||
}};
|
||||
static constexpr int kWantKey = -1;
|
||||
static constexpr int kSkip = -2;
|
||||
@@ -885,7 +910,7 @@ private:
|
||||
scratch_.clear();
|
||||
{(" if (p.kind == Kind::RootScalar) { stack_.pop_back(); done_ = true; return; }" if root_kind is None else "")}
|
||||
if (isObjectKind(p.kind)) {{
|
||||
if (p.field >= 0) p.seen |= (1u << p.field);
|
||||
if (p.field >= 0) p.seen[p.field >> 6] |= (1ull << (p.field & 63));
|
||||
p.field = kWantKey;
|
||||
}}
|
||||
}}
|
||||
@@ -895,16 +920,19 @@ private:
|
||||
}}
|
||||
void cbEndObject() {{
|
||||
if (error_) return;
|
||||
Frame f = stack_.back();
|
||||
Frame &f = stack_.back();
|
||||
if (f.kind == Kind::Skip) {{
|
||||
stack_.pop_back();
|
||||
if (stack_.empty() || stack_.back().kind != Kind::Skip) valueComplete();
|
||||
return;
|
||||
}}
|
||||
if (isObjectKind(f.kind) &&
|
||||
(f.seen & requiredMask(f.kind)) != requiredMask(f.kind)) {{
|
||||
reject();
|
||||
return;
|
||||
if (isObjectKind(f.kind)) {{
|
||||
const auto &req = requiredMask(f.kind);
|
||||
bool missing = false;
|
||||
for (size_t i = 0; i < req.size(); ++i) {{
|
||||
if ((f.seen[i] & req[i]) != req[i]) {{ missing = true; break; }}
|
||||
}}
|
||||
if (missing) {{ reject(); return; }}
|
||||
}}
|
||||
stack_.pop_back();
|
||||
valueComplete();
|
||||
@@ -937,7 +965,9 @@ private:
|
||||
f.field = kSkip;
|
||||
return;
|
||||
}}
|
||||
if (f.seen & (1u << idx)) {{ reject(); return; }} // duplicate key
|
||||
if (f.seen[idx >> 6] & (1ull << (idx & 63))) {{
|
||||
reject(); return; // duplicate key
|
||||
}}
|
||||
f.field = idx;
|
||||
}}
|
||||
|
||||
@@ -1062,6 +1092,8 @@ private:
|
||||
|
||||
{self._matchkey()}
|
||||
|
||||
{self._fieldcount()}
|
||||
|
||||
{self._reqmask()}
|
||||
|
||||
{self._is_object_kind()}
|
||||
|
||||
Reference in New Issue
Block a user