// Source: cppcc bench/scb_counts.cc @ cb25db7 -- curated 2026-08-06; // edits land in the source first. // bench/scb_counts.cc — CCS SCB walk demonstration (standalone). // // The CCS side of bench/main.cc, extracted so it builds against the // STANDALONE SCB C++ package alone (namespace ccsscb, libscbcpp.a) — // exactly what a b3u.dev delivery contains — plus the generated // jsonKeyWordDefinition.h from the cpp language bundle. // // Loads a .bfgr SCB instance (produced by the ccsjson frontend) once, // then runs the three walk strategies over it, timing each: // // Helper — recursive traversal through nested kwn/edp wrappers // Helper2 — same recursion, flat vectors, no per-call checks // Helper3 — grammar-aware: string/int/float counts in O(1) from // per-symbol totals; true/false/null from a targeted // scan of the four value positions. No tree walk. // // All three tally the same six counts (s/i/r/t/f/n = string, int, // float, true, false, null); the program asserts they agree and // exits nonzero if not — the walkers oracle each other. // // Build (paths point at your delivered SCB C++ package and the cpp // bundle directory holding jsonKeyWordDefinition.h): // // g++ -std=c++17 -O2 -Wall -fno-strict-aliasing // -I/include -I // scb_counts.cc /lib/libscbcpp.a -o scb_counts // (one command; wrapped here for readability) // // Usage: // ./scb_counts .bfgr [iterations] (default 100) #include #include #include #include #include #include "SyntaxControlledBinary.h" // The generated KeyWordDefinition header also serves frontend builds: // its tail declares a factory returning cppcc::com::KeyWordsContainer. // Linking the standalone SCB package (ccsscb) instead, that type is // absent — a forward declaration satisfies the (never-called) // declaration and keeps the delivered header untouched. namespace cppcc { namespace com { class KeyWordsContainer; } } #include "jsonKeyWordDefinition.h" namespace { using ccsscb::scb::SyntaxControlledBinary; namespace tag = ccsscb::scr::tag; namespace kw = cppcc::json; template double mean_us(int iters, F&& fn) { using clk = std::chrono::steady_clock; auto t0 = clk::now(); for (int i = 0; i < iters; i++) fn(); auto t1 = clk::now(); auto ns = std::chrono::duration_cast(t1 - t0).count(); return (ns / 1000.0) / iters; } struct Counts { unsigned long s_; // string values unsigned long i_; // integer numbers unsigned long r_; // real (float/double) numbers unsigned long t_; // true literals unsigned long f_; // false literals unsigned long n_; // null literals bool operator==(const Counts& o) const { return s_ == o.s_ && i_ == o.i_ && r_ == o.r_ && t_ == o.t_ && f_ == o.f_ && n_ == o.n_; } }; void print_counts(const char* name, double us, const Counts& c) { std::printf(" %-10s %10.2f us/walk (s=%lu i=%lu r=%lu t=%lu f=%lu n=%lu)\n", name, us, c.s_, c.i_, c.r_, c.t_, c.f_, c.n_); } // ---- Helper: recursive traversal of the SCB parse tree ------------ // // Each tag decomposes into (kkk, nnn) — kkk is the keyword id, nnn // the edp instance number. JSON leaf values short-circuit and bump // their counter; anything else fetches the edp and recurses into its // fixed and dynamic children. void scTraverse_(SyntaxControlledBinary::Helper& h, tag::Long t, Counts& c) { if (!t) return; tag::Long kkk; tag::Long nnn; tag::setlongedf(&t, kkk, nnn); // Leaf token kkks: the compiler flattens single-rule wrappers, so // str/num never appear and value-leaves show as the underlying // token kind. Their fixed[] holds raw bytes — never recurse in. switch (kkk) { case kw::KW_STRINGTOKEN: ++c.s_; return; case kw::KW_INTEGERTOKEN: ++c.i_; return; case kw::KW_FLOATTOKEN: ++c.r_; return; case kw::KW_IDENTIFIER: return; case kw::KW_TEXTTOKEN: return; case kw::KW_TERMINALTOKENOFRULE: return; case kw::KW_TERMTOKEN: // nnn names the specific terminal token; count true/false/null // and ignore structural punctuation. if (nnn == kw::KW_TERMINAL_TRUE) ++c.t_; else if (nnn == kw::KW_TERMINAL_FALSE) ++c.f_; else if (nnn == kw::KW_TERMINAL_NULL) ++c.n_; return; } if (kkk < 0 || kkk >= static_cast(h.kwns_.size())) return; auto* k = h.kwns_[kkk].k_; if (!k) return; if (nnn < 0 || nnn >= static_cast(h.kwns_[kkk].edps_.size())) return; auto* e = h.kwns_[kkk].edps_[nnn].e_; if (!e) return; tag::Long* uf = h.b_.fixed(e); tag::Long* ud = h.b_.dynamic(e); // pair = str(KEY) ':' value — skip the key slot so object keys do // not add to the string count (only VALUES are tallied). tag::Long fStart = (kkk == kw::KW_PAIR) ? 1 : 0; if (uf) { for (tag::Long i = fStart; i < e->fl; ++i) scTraverse_(h, uf[i], c); } if (ud) { for (tag::Long i = 0; i < e->dl; ++i) scTraverse_(h, ud[i], c); } } // ---- Helper2: same logic, flat vectors, no per-call checks -------- void scTraverse2_(SyntaxControlledBinary::Helper2& h, tag::Long t, Counts& c) { if (!t) return; tag::Long kkk; tag::Long nnn; tag::setlongedf(&t, kkk, nnn); switch (kkk) { case kw::KW_STRINGTOKEN: ++c.s_; return; case kw::KW_INTEGERTOKEN: ++c.i_; return; case kw::KW_FLOATTOKEN: ++c.r_; return; case kw::KW_IDENTIFIER: return; case kw::KW_TEXTTOKEN: return; case kw::KW_TERMINALTOKENOFRULE: return; case kw::KW_TERMTOKEN: if (nnn == kw::KW_TERMINAL_TRUE) ++c.t_; else if (nnn == kw::KW_TERMINAL_FALSE) ++c.f_; else if (nnn == kw::KW_TERMINAL_NULL) ++c.n_; return; } // Trust the binary — no bounds/null checks. SyntaxControlledBinary::edpirType* e = h.edps_[kkk][nnn]; tag::Long* uf = e->fl ? &h.b_.memory_[e->d] : nullptr; tag::Long* ud = e->dl ? &h.b_.memory_[e->d + e->fl] : nullptr; tag::Long fStart = (kkk == kw::KW_PAIR) ? 1 : 0; if (uf) { for (tag::Long i = fStart; i < e->fl; ++i) scTraverse2_(h, uf[i], c); } if (ud) { for (tag::Long i = 0; i < e->dl; ++i) scTraverse2_(h, ud[i], c); } } // ---- Helper3: grammar-aware, no tree walk ------------------------- // // * s/i/r: derived in O(1) from per-symbol totals (minus the pair // total for s, netting out object KEY strings — every pair holds // exactly one key string) // * t/f/n: scan only the value-position child of the four rules // that can hold a JSON value — 'true'/'false'/'null' can appear // nowhere else void populate_counts3(SyntaxControlledBinary::Helper3& h, Counts& c) { c = Counts{}; auto kwlen = [&](int kwid) -> unsigned long { auto* k = (kwid < (int)h.kwns_.size()) ? h.kwns_[kwid] : nullptr; return k ? static_cast(k->l) : 0; }; unsigned long stringTotal = kwlen(kw::KW_STRINGTOKEN); unsigned long pairTotal = kwlen(kw::KW_PAIR); c.s_ = (stringTotal >= pairTotal) ? (stringTotal - pairTotal) : 0; c.i_ = kwlen(kw::KW_INTEGERTOKEN); c.r_ = kwlen(kw::KW_FLOATTOKEN); auto checkValuePos = [&](int kkk, tag::Long pos) { if (kkk >= (int)h.edps_.size()) return; for (auto* e : h.edps_[kkk]) { if (!e || pos >= e->fl) continue; tag::Long t = h.b_.memory_[e->d + pos]; tag::Long kk2, nn2; tag::setlongedf(&t, kk2, nn2); if (kk2 != kw::KW_TERMTOKEN) continue; if (nn2 == kw::KW_TERMINAL_TRUE) ++c.t_; else if (nn2 == kw::KW_TERMINAL_FALSE) ++c.f_; else if (nn2 == kw::KW_TERMINAL_NULL) ++c.n_; } }; checkValuePos(kw::KW_JSONP, 0); // (jsonp ::= value) checkValuePos(kw::KW_PAIR, 2); // (pair ::= str ':' value) checkValuePos(kw::KW_ELEMENTS, 0); // (elements ::= value ...) checkValuePos(kw::KW_RESTVALUE, 1); // (restValue ::= ',' value) } } // namespace int main(int argc, char** argv) { if (argc < 2) { std::fprintf(stderr, "usage: scb_counts .bfgr [iterations]\n"); return 2; } std::string bfgr = argv[1]; int iters = argc > 2 ? std::atoi(argv[2]) : 100; if (iters <= 0) iters = 100; std::ifstream probe(bfgr); if (!probe) { std::fprintf(stderr, "FAIL: cannot open %s (run the ccsjson frontend " "on a JSON input first)\n", bfgr.c_str()); return 1; } probe.close(); // Load ONCE; only the walks are timed — "parse once, process many". SyntaxControlledBinary bin; bin.readBinaryRaw(bfgr); std::printf("loaded: %s (%zu longs), iterations: %d\n", bfgr.c_str(), bin.memory_.size(), iters); SyntaxControlledBinary::Helper h1(bin); SyntaxControlledBinary::Helper2 h2(bin); SyntaxControlledBinary::Helper3 h3(bin); Counts c1{}, c2{}, c3{}; double us1 = mean_us(iters, [&] { c1 = Counts{}; scTraverse_(h1, h1.h_.cntbeg, c1); }); double us2 = mean_us(iters, [&] { c2 = Counts{}; scTraverse2_(h2, h2.h_.cntbeg, c2); }); double us3 = mean_us(iters, [&] { populate_counts3(h3, c3); }); print_counts("Helper", us1, c1); print_counts("Helper2", us2, c2); print_counts("Helper3", us3, c3); if (!(c1 == c2 && c2 == c3)) { std::fprintf(stderr, "FAIL: walkers disagree\n"); return 1; } std::printf("OK: all three walkers agree\n"); return 0; }