docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"efun" : {
"type" : "category" ,
"key" : "efun" ,
"label" : "Efuns" ,
"link" : {
"type" : "generated-index" ,
"title" : "Efuns" ,
"slug" : "/efun/" ,
"description" : "Built-in functions (efuns) the driver exposes to LPC code, grouped by topic and package."
} ,
"items" : [
{
"type" : "category" ,
"key" : "efun/arrays" ,
"label" : "Arrays" ,
"link" : {
"type" : "generated-index" ,
"title" : "Arrays" ,
"slug" : "/efun/arrays/" ,
"description" : "Create, allocate, filter, map, sort and search LPC arrays."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/arrays/allocate" ,
"key" : "efun/arrays/allocate" ,
"label" : "allocate"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/arrayp" ,
"key" : "efun/arrays/arrayp" ,
"label" : "arrayp"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/element_of" ,
"key" : "efun/arrays/element_of" ,
"label" : "element_of"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/filter_array" ,
"key" : "efun/arrays/filter_array" ,
"label" : "filter_array"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/map_array" ,
"key" : "efun/arrays/map_array" ,
"label" : "map_array"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/member_array" ,
"key" : "efun/arrays/member_array" ,
"label" : "member_array"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/pointerp" ,
"key" : "efun/arrays/pointerp" ,
"label" : "pointerp"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/shuffle" ,
"key" : "efun/arrays/shuffle" ,
"label" : "shuffle"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/sort_array" ,
"key" : "efun/arrays/sort_array" ,
"label" : "sort_array"
} ,
{
"type" : "doc" ,
"id" : "efun/arrays/unique_array" ,
"key" : "efun/arrays/unique_array" ,
"label" : "unique_array"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/async" ,
"label" : "Async I/O" ,
"link" : {
"type" : "generated-index" ,
"title" : "Async I/O" ,
"slug" : "/efun/async/" ,
"description" : "Non-blocking file and database I/O that returns results via callbacks."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/async/async_db_exec" ,
"key" : "efun/async/async_db_exec" ,
"label" : "async_db_exec"
} ,
{
"type" : "doc" ,
"id" : "efun/async/async_getdir" ,
"key" : "efun/async/async_getdir" ,
"label" : "async_getdir"
} ,
{
"type" : "doc" ,
"id" : "efun/async/async_read" ,
"key" : "efun/async/async_read" ,
"label" : "async_read"
} ,
{
"type" : "doc" ,
"id" : "efun/async/async_write" ,
"key" : "efun/async/async_write" ,
"label" : "async_write"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/buffers" ,
"label" : "Buffers" ,
"link" : {
"type" : "generated-index" ,
"title" : "Buffers" ,
"slug" : "/efun/buffers/" ,
"description" : "Allocate, read, write, transcode and checksum binary buffer data."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/buffers/allocate_buffer" ,
"key" : "efun/buffers/allocate_buffer" ,
"label" : "allocate_buffer"
} ,
{
"type" : "doc" ,
"id" : "efun/buffers/buffer_transcode" ,
"key" : "efun/buffers/buffer_transcode" ,
"label" : "buffer_transcode"
} ,
{
"type" : "doc" ,
"id" : "efun/buffers/bufferp" ,
"key" : "efun/buffers/bufferp" ,
"label" : "bufferp"
} ,
{
"type" : "doc" ,
"id" : "efun/buffers/crc32" ,
"key" : "efun/buffers/crc32" ,
"label" : "crc32"
} ,
{
"type" : "doc" ,
"id" : "efun/buffers/read_buffer" ,
"key" : "efun/buffers/read_buffer" ,
"label" : "read_buffer"
} ,
vm: string foreach/ref fixes, buffers as byte arrays (foreach, strict bytes, to_buffer), thorough ref tests; #1196 docs follow-up (#1250)
* docs/tests: fix constructs index regression from #1196; add ref page to sidebar; extend & ref tests
- Restore extension-less links and the text_blocks entry in
docs/lpc/constructs/index.md (the PR was recreated from a pre-Docusaurus
branch and reintroduced .html links, which fail the docs build under
onBrokenLinks: 'throw', and dropped text_blocks)
- Add lpc/constructs/ref to the hand-authored sidebar in docs/sidebars.ts
- Drop the stale VitePress 'layout: doc' frontmatter from ref.md
- Extend testsuite/single/tests/operators/ref.lpc: & in parameter
declarations, ref keyword in foreach, and bitwise &/&= non-regression
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Skf9CfHwAngWorDNDzPKWp
* vm: fix string foreach crash and off-by-one ref reads; support += / -= on string chars
Three related defects around the shared string-codepoint lvalue
(global_lvalue_codepoint), all reproduced on the unfixed binary:
- Nested foreach over strings SEGFAULTED: the iteration cursor lived in
the shared global, so the inner loop's F_EXIT_FOREACH reset the
iterator out from under the outer loop (null deref in
post_index_to_offset). The EGC cursor now lives in each loop's own
stack slot (the T_NUMBER slot under the loop variable); the ref case
re-arms the shared codepoint lvalue every iteration, and exit only
clears the global when it still points at this loop's string slot.
- foreach (int ref c in str) read the WRONG characters: the shared
index was advanced before the body ran, so every read through the ref
was off by one ('abc' summed to 197 instead of 294, and the final
iteration read one past the end). The global index now stays on the
current character for the whole body. Writes through the ref still go
to the loop's by-value stack copy and never reach the iterated
variable -- semantics pinned by tests/operators/foreach.lpc.
- s[i] += n / s[i] -= n threw "Bad Argument 1 to +=()": F_ADD_EQ and
f_sub_eq handled buffer byte lvalues (T_LVALUE_BYTE) but not string
codepoint lvalues (T_LVALUE_CODEPOINT), even though ++/--/= worked.
Both now route through a new codepoint_lvalue_add() helper and produce
the resulting character as the rvalue.
Regression tests: nested (plain / ref / multi-byte UTF-8) string foreach
and ref-read correctness in tests/operators/foreach.lpc; compound
assignment on string chars (incl. reverse index, rvalue result, and
non-number rhs error) in tests/operators/string_index.lpc. Verified on
the unfixed binary: the nested-foreach test segfaults, the others fail.
Full LPC suite passes 2x on clang ASan/UBSan Debug and 2x on
RelWithDebInfo.
Docs: correct the ref.md note on foreach-over-strings; document the
string-char lvalue rules in AGENTS.md (section 8 + audit checklist 9)
and the ref/& feature in README.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Skf9CfHwAngWorDNDzPKWp
* vm: foreach ref over strings shares the s[i] char-lvalue logic; thorough ref tests
Arming a string-char lvalue now goes through one shared helper,
aim_lvalue_codepoint(): it validates the target EGC (out-of-bounds and
multi-codepoint error cleanly, same messages as s[i]) and points the
shared codepoint state at it. Both s[i] lvalues (push_indexed_lvalue)
and foreach ref loop variables use it, and every write consumer already
funnels into assign_lvalue_codepoint() -- so ref loop chars follow
exactly the s[i] rules:
- single-codepoint characters (and EGCs up to 4 bytes, which index as
their first codepoint) keep working: reads deliver the character,
assignments through the loop variable succeed
- wider EGCs (flag emoji, ZWJ sequences) now raise the catchable
"Indexed character is multi-codepoint" error when the ref loop
reaches them, instead of silently reading as -1; the non-ref form
still iterates and delivers -1 for such clusters
tests/operators/ref.lpc is now a thorough pass-by-reference suite:
ref/& parameter declarations and call arguments, by-value contrast,
ref forwarding through call chains, call-site refs to array elements /
mapping values / string chars (forward and reverse index, write-back),
foreach ref over arrays / mapping values / strings (read correctness,
multi-byte codepoints, by-value write semantics, assignment error
parity, the multi-codepoint error, non-ref contrast), compile-time
rejections via generated sources (ref outside an argument list, ref to
a range), and bitwise &/&= non-regression -- 39 checks.
Full LPC suite passes 2x on RelWithDebInfo and 2x on a clean clang
ASan/UBSan Debug build.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Skf9CfHwAngWorDNDzPKWp
* tests: cover ref across all LPC types
Extend tests/operators/ref.lpc to 58 checks: ref parameters of every
value type (int, float, string, array, mapping, object, function,
buffer, class, mixed) verifying reassignment propagates; the by-value
contrast for reference-typed containers (member writes propagate,
reassignment doesn't); call-site refs to buffer bytes (forward and
reverse index) and class members (both . and -> spellings); foreach
ref over mapping values of mixed types; the compile-time rejection of
a ref mapping KEY in foreach; and the clean runtime error for foreach
over a non-iterable type (buffer).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Skf9CfHwAngWorDNDzPKWp
* buffers behave like byte arrays: foreach, strict 0..255 bytes, string/array promotion, to_buffer()
Buffers are now first-class byte containers:
- foreach iterates a buffer like an array, delivering each byte as an
unsigned int 0..255. A ref loop variable mutates the buffer in place;
each ref carries its OWN T_LVALUE_BYTE (in ref->sv), so nested buffer
ref loops and b[i] lvalues in the body can't alias each other.
T_LVALUE_BYTE consumers now read the lvalue's own pointer/subtype
instead of reaching for the shared global_lvalue_byte (which remains
only as the scratch instance b[i] arms).
- every LPC byte write path (=, ++, --, +=, -=) range-checks the result:
a value outside 0..255 raises "Buffer byte value out of range" and
leaves the byte unchanged, instead of silently truncating/wrapping.
+= / -= on bytes also yield the resulting value as their rvalue.
- strings and arrays of ints 0..255 PROMOTE to buffers: a new
to_buffer() efun (registered like to_int/to_float) is wrapped around
the rhs by do_promotions() / rule_expr_assign for 'buffer b = str',
'b += str', initializers, and 'b + str'; range assignment and the
runtime + / += paths convert unpromoted (mixed) values through the
same svalue_to_buffer_bytes() helper. A string contributes its raw
UTF-8 bytes; an array must hold only ints 0..255 (validated before
allocation) or the conversion errors with the target unchanged.
- fixed a pre-existing ref_t leak: a foreach ref loop variable reused by
a re-entered inner loop leaked one ref per outer iteration (flagged by
the debug memory checker as 'Found temporary block: make_ref').
tests/operators/buffer_bytes.lpc pins the byte-range semantics, + / +=
concatenation, all promotion forms, and the error paths (106 checks);
foreach.lpc and ref.lpc pin buffer iteration and ref-loop independence;
buffer_range_assign.lpc gains range-read pins. New docs for to_buffer
(sidebar regenerated) and a rewritten lpc/types/buffer page.
Full LPC suite passes 2x on RelWithDebInfo and 2x on a clean clang
ASan/UBSan Debug build (no ref-checker warnings); GTest suite 312/312.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Skf9CfHwAngWorDNDzPKWp
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-12 01:39:30 -04:00
{
"type" : "doc" ,
"id" : "efun/buffers/to_buffer" ,
"key" : "efun/buffers/to_buffer" ,
"label" : "to_buffer"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "efun/buffers/write_buffer" ,
"key" : "efun/buffers/write_buffer" ,
"label" : "write_buffer"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/calls" ,
"label" : "Function Calls & Call-Outs" ,
"link" : {
"type" : "generated-index" ,
"title" : "Function Calls & Call-Outs" ,
"slug" : "/efun/calls/" ,
"description" : "Call functions on other objects, schedule call-outs, and manage shadows and exceptions."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/calls/call_other" ,
"key" : "efun/calls/call_other" ,
"label" : "call_other"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/call_out" ,
"key" : "efun/calls/call_out" ,
"label" : "call_out"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/call_out_walltime" ,
"key" : "efun/calls/call_out_walltime" ,
"label" : "call_out_walltime"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/call_stack" ,
"key" : "efun/calls/call_stack" ,
"label" : "call_stack"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/catch" ,
"key" : "efun/calls/catch" ,
"label" : "catch"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/origin" ,
"key" : "efun/calls/origin" ,
"label" : "origin"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/previous_object" ,
"key" : "efun/calls/previous_object" ,
"label" : "previous_object"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/query_shadowing" ,
"key" : "efun/calls/query_shadowing" ,
"label" : "query_shadowing"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/remove_call_out" ,
"key" : "efun/calls/remove_call_out" ,
"label" : "remove_call_out"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/shadow" ,
"key" : "efun/calls/shadow" ,
"label" : "shadow"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/this_object" ,
"key" : "efun/calls/this_object" ,
"label" : "this_object"
} ,
{
"type" : "doc" ,
"id" : "efun/calls/throw" ,
"key" : "efun/calls/throw" ,
"label" : "throw"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/contrib" ,
"label" : "Contrib Package" ,
"link" : {
"type" : "generated-index" ,
"title" : "Contrib Package" ,
"slug" : "/efun/contrib/" ,
"description" : "Optional add-on efuns from the contrib package: classes, strings, livings, memory and misc utilities."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/contrib/abs" ,
"key" : "efun/contrib/abs" ,
"label" : "abs"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/add_a" ,
"key" : "efun/contrib/add_a" ,
"label" : "add_a"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/assemble_class" ,
"key" : "efun/contrib/assemble_class" ,
"label" : "assemble_class"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/base_name" ,
"key" : "efun/contrib/base_name" ,
"label" : "base_name"
} ,
reference loops: docs, runtime cycle efuns, orphan collector, copy() unwind fix (#1276)
A complete treatment of reference loops (cyclic data structures) in LPC:
the reference-counting VM has no cycle collector, so a value that reaches
itself leaks permanently -- and silently -- once the last outside
reference is dropped, and it cannot be saved, deep-copied, or fully
printed in the meantime. This change documents the problem, fixes a
driver memory-safety bug it exposes, and adds runtime tooling to detect,
locate, break, and (on debug builds) collect such loops.
Driver fixes:
- copy() on a cyclic structure always hits the MAX_SAVE_SVALUE_DEPTH
error(), and that unwind path leaked every partially-built container --
allocated with the _empty_ (uninitialized-svalue) allocators and holding
a borrowed, un-ref-counted pointer to the ORIGINAL value, which the
Debug memory checker then read after free (heap-use-after-free abort
under ASan, reproducible with just catch(copy(a)) on a[0] = a).
deep_copy_* now allocate zero-filled, hold the destination in a
unique_ptr whose deleter is the tag-symmetric free_*, and only write the
destination slot after the child copy fully succeeded (ffi.cc precedent,
AGENTS.md section 4).
- save_object/save_variable and copy()'s 'nested too deep' errors -- the
classic symptom of a loop -- now say so (the has_cycle() pointer is
gated on PACKAGE_CONTRIB so core never recommends an efun the build
lacks).
New efuns (contrib package, src/packages/contrib/cycles.cc):
- has_cycle(mixed): 1 if the value's reference graph contains a loop.
- find_cycles(mixed): one index-path string per loop-closing slot
("[3][\"peer\"].1" style).
- break_cycles(mixed): clears every loop in place and returns the number
of edges broken. Exactly the DFS back-edges are touched (a digraph is
acyclic iff its DFS has no back-edges): item/value slots are zeroed, a
loop closed in mapping-KEY position has its node deleted (hashed keys
cannot be overwritten), and a loop closing on the funptr->args edge
itself -- possible because bind() SHARES the args array between the old
and new funptr -- detaches the bound funptr's args list and replaces it
with a zero-filled one of the same size. DAG sharing is never touched;
one cut un-loops a whole ring; afterwards the value saves, copies,
prints, and frees normally.
All three share one ITERATIVE walk (explicit heap stack, white/grey/
black coloring): no C-stack recursion, no depth cap -- arbitrarily deep
acyclic values scan cleanly where save_variable() errors. Edges:
array/class items, mapping keys AND values, fp->hdr.args; objects are
deliberately leaves (loops through object variables are the
destruct()-managed kind: destruct2() zeroes the variable block).
break_cycles() records fixes during a mutation-free walk and applies
them in a post-pass that holds a reference on every touched container,
zeroes slots before deleting nodes (only node deletion can cascade
frees), and releases the holds last -- order-independent and safe
against shared/overlapping fixes.
Orphaned-loop collector (develop package, Debug/DEBUGMALLOC_EXTENSIONS):
- find_orphaned_cycles(int collect): finds -- and with any nonzero
argument reclaims -- data blocks that are unreachable because only a
reference loop keeps them alive: the case nothing LPC-level can reach
anymore. Detection is trial deletion (CPython-gc-style), implemented in
md_scan_orphaned_cycles (checkmemory.cc): count each array/class/
mapping/funptr's references held by OTHER data blocks; a block whose
real ref count exceeds that is externally held (object variables, VM
stack, call_out, any C++-side holder) and seeds liveness, which
propagates along data edges; the remainder is loop garbage. No root
enumeration to get wrong -- every legitimate holder shows up as an
external ref. Collection: hold a ref on every dead block, sever all
their child slots (releasing strings/objects/buffers/live values
normally), then release the holds -- each dead block deallocates with
nothing left to cascade into.
- check_all_blocks() runs the same scan (skippable via new flag bit 2,
value 4) and reports 'unreachable data block(s) kept alive only by
reference loop(s)', so the testsuite's per-file check_memory() gate
turns a dropped cycle into a hard, attributed failure. That immediately
caught a real pre-existing leak: tests/std/json.lpc's
test_encode_circular_references() dropped all four of its
deliberately-cyclic fixtures on every suite run since it was written.
Tests (testsuite/single/tests/):
- operators/reference_loop.lpc pins the driver contract around loops and
crashes the unfixed Debug/ASan driver (the copy() unwind UAF).
- efuns/has_cycle.lpc, find_cycles.lpc, break_cycles.lpc cover self/
mutual/ring loops across arrays, mappings (value and key position),
classes, funptr args (including the bind()-shared-args case, which was
unbreakable in an earlier revision of this change), DAG-sharing
preservation, save/copy working again after a break, idempotency, and a
5000-deep acyclic walk.
- efuns/find_orphaned_cycles.lpc pins baseline-relative detection of 6
orphans across three dropped loop shapes, idempotent detection, that
reachable loops are never classified as garbage, and that collection
reclaims everything while reachable data survives.
- Every cycle-building test has UNCONDITIONAL teardown (body in catch(),
find_orphaned_cycles(1) regardless, error re-raised) so a mid-test
regression stays one [ FAILED ] entry instead of cascading the
harness's LEAK gate into a suite-wide abort (AGENTS.md section 7).
Docs (Docusaurus, sidebar regenerated; full two-locale build verified):
- new concepts page docs/concepts/general/reference_loops.md: why loops
leak, what each recursive consumer does, the destruct() exception,
prevention patterns, the runtime tools, and the debug-build collector;
- efun pages for all four new efuns; check_memory.md documents the new
scan and flag bit.
Validated on Debug+ASan/UBSan (full LPC suite, randomized order, multiple
runs) and RelWithDebInfo (full suite), 313 GTest unit tests, plus an
8-angle adversarially-verified self-review.
Round-2 self-review (4 fresh angles, adversarially verified) additionally:
- break_cycles() post-pass releases its held container references via an
RAII guard: allocate_array() there can error() (set_config() can shrink
__MAX_ARRAY_SIZE__ at runtime below a shared args array's size), and the
old trailing release loop would have leaked every held ref on that
unwind (AGENTS.md section 4).
- documented the pre-existing map_delete()-class caveat: deleting a
key-closed loop's node while an outer unlocked foreach-ref variable is
aimed at it dangles that variable (not specific to this efun; noted in
code and doc).
- extended orphan-collector coverage from 6 to 10 blocks: class rings
(TAG_CLASS candidate/sever/free_class paths), mapping pairs closed in
KEY position (the collector's in-place key-zeroing sever path), and a
buffer payload riding an orphaned ring (sever must release it or the
Debug ref gate trips); added a destructed-object-in-walked-value test
(render_key + leaf handling).
- docs: refs.md and copy.md now link back to the cycle tooling; zh-CN
sidebar translation keys rescaffolded; AGENTS.md section 7 documents the
new hard gate and the catch + find_orphaned_cycles(1) teardown pattern.
- re-entrancy audit (foreach/MAP_LOCKED/locked_map_nodes/merge_arg_lists)
and LPC-test-semantics audit returned no code defects.
Claude-Session: https://claude.ai/code/session_01VaksxbPjc3hjzghoQPUHRo
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 00:06:37 -07:00
{
"type" : "doc" ,
"id" : "efun/contrib/break_cycles" ,
"key" : "efun/contrib/break_cycles" ,
"label" : "break_cycles"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "efun/contrib/classes" ,
"key" : "efun/contrib/classes" ,
"label" : "classes"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/compressedp" ,
"key" : "efun/contrib/compressedp" ,
"label" : "compressedp"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/copy" ,
"key" : "efun/contrib/copy" ,
"label" : "copy"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/debug_message" ,
"key" : "efun/contrib/debug_message" ,
"label" : "debug_message"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/disassemble_class" ,
"key" : "efun/contrib/disassemble_class" ,
"label" : "disassemble_class"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/event" ,
"key" : "efun/contrib/event" ,
"label" : "event"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/fetch_class_member" ,
"key" : "efun/contrib/fetch_class_member" ,
"label" : "fetch_class_member"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/fetch_variable" ,
"key" : "efun/contrib/fetch_variable" ,
"label" : "fetch_variable"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/file_length" ,
"key" : "efun/contrib/file_length" ,
"label" : "file_length"
} ,
reference loops: docs, runtime cycle efuns, orphan collector, copy() unwind fix (#1276)
A complete treatment of reference loops (cyclic data structures) in LPC:
the reference-counting VM has no cycle collector, so a value that reaches
itself leaks permanently -- and silently -- once the last outside
reference is dropped, and it cannot be saved, deep-copied, or fully
printed in the meantime. This change documents the problem, fixes a
driver memory-safety bug it exposes, and adds runtime tooling to detect,
locate, break, and (on debug builds) collect such loops.
Driver fixes:
- copy() on a cyclic structure always hits the MAX_SAVE_SVALUE_DEPTH
error(), and that unwind path leaked every partially-built container --
allocated with the _empty_ (uninitialized-svalue) allocators and holding
a borrowed, un-ref-counted pointer to the ORIGINAL value, which the
Debug memory checker then read after free (heap-use-after-free abort
under ASan, reproducible with just catch(copy(a)) on a[0] = a).
deep_copy_* now allocate zero-filled, hold the destination in a
unique_ptr whose deleter is the tag-symmetric free_*, and only write the
destination slot after the child copy fully succeeded (ffi.cc precedent,
AGENTS.md section 4).
- save_object/save_variable and copy()'s 'nested too deep' errors -- the
classic symptom of a loop -- now say so (the has_cycle() pointer is
gated on PACKAGE_CONTRIB so core never recommends an efun the build
lacks).
New efuns (contrib package, src/packages/contrib/cycles.cc):
- has_cycle(mixed): 1 if the value's reference graph contains a loop.
- find_cycles(mixed): one index-path string per loop-closing slot
("[3][\"peer\"].1" style).
- break_cycles(mixed): clears every loop in place and returns the number
of edges broken. Exactly the DFS back-edges are touched (a digraph is
acyclic iff its DFS has no back-edges): item/value slots are zeroed, a
loop closed in mapping-KEY position has its node deleted (hashed keys
cannot be overwritten), and a loop closing on the funptr->args edge
itself -- possible because bind() SHARES the args array between the old
and new funptr -- detaches the bound funptr's args list and replaces it
with a zero-filled one of the same size. DAG sharing is never touched;
one cut un-loops a whole ring; afterwards the value saves, copies,
prints, and frees normally.
All three share one ITERATIVE walk (explicit heap stack, white/grey/
black coloring): no C-stack recursion, no depth cap -- arbitrarily deep
acyclic values scan cleanly where save_variable() errors. Edges:
array/class items, mapping keys AND values, fp->hdr.args; objects are
deliberately leaves (loops through object variables are the
destruct()-managed kind: destruct2() zeroes the variable block).
break_cycles() records fixes during a mutation-free walk and applies
them in a post-pass that holds a reference on every touched container,
zeroes slots before deleting nodes (only node deletion can cascade
frees), and releases the holds last -- order-independent and safe
against shared/overlapping fixes.
Orphaned-loop collector (develop package, Debug/DEBUGMALLOC_EXTENSIONS):
- find_orphaned_cycles(int collect): finds -- and with any nonzero
argument reclaims -- data blocks that are unreachable because only a
reference loop keeps them alive: the case nothing LPC-level can reach
anymore. Detection is trial deletion (CPython-gc-style), implemented in
md_scan_orphaned_cycles (checkmemory.cc): count each array/class/
mapping/funptr's references held by OTHER data blocks; a block whose
real ref count exceeds that is externally held (object variables, VM
stack, call_out, any C++-side holder) and seeds liveness, which
propagates along data edges; the remainder is loop garbage. No root
enumeration to get wrong -- every legitimate holder shows up as an
external ref. Collection: hold a ref on every dead block, sever all
their child slots (releasing strings/objects/buffers/live values
normally), then release the holds -- each dead block deallocates with
nothing left to cascade into.
- check_all_blocks() runs the same scan (skippable via new flag bit 2,
value 4) and reports 'unreachable data block(s) kept alive only by
reference loop(s)', so the testsuite's per-file check_memory() gate
turns a dropped cycle into a hard, attributed failure. That immediately
caught a real pre-existing leak: tests/std/json.lpc's
test_encode_circular_references() dropped all four of its
deliberately-cyclic fixtures on every suite run since it was written.
Tests (testsuite/single/tests/):
- operators/reference_loop.lpc pins the driver contract around loops and
crashes the unfixed Debug/ASan driver (the copy() unwind UAF).
- efuns/has_cycle.lpc, find_cycles.lpc, break_cycles.lpc cover self/
mutual/ring loops across arrays, mappings (value and key position),
classes, funptr args (including the bind()-shared-args case, which was
unbreakable in an earlier revision of this change), DAG-sharing
preservation, save/copy working again after a break, idempotency, and a
5000-deep acyclic walk.
- efuns/find_orphaned_cycles.lpc pins baseline-relative detection of 6
orphans across three dropped loop shapes, idempotent detection, that
reachable loops are never classified as garbage, and that collection
reclaims everything while reachable data survives.
- Every cycle-building test has UNCONDITIONAL teardown (body in catch(),
find_orphaned_cycles(1) regardless, error re-raised) so a mid-test
regression stays one [ FAILED ] entry instead of cascading the
harness's LEAK gate into a suite-wide abort (AGENTS.md section 7).
Docs (Docusaurus, sidebar regenerated; full two-locale build verified):
- new concepts page docs/concepts/general/reference_loops.md: why loops
leak, what each recursive consumer does, the destruct() exception,
prevention patterns, the runtime tools, and the debug-build collector;
- efun pages for all four new efuns; check_memory.md documents the new
scan and flag bit.
Validated on Debug+ASan/UBSan (full LPC suite, randomized order, multiple
runs) and RelWithDebInfo (full suite), 313 GTest unit tests, plus an
8-angle adversarially-verified self-review.
Round-2 self-review (4 fresh angles, adversarially verified) additionally:
- break_cycles() post-pass releases its held container references via an
RAII guard: allocate_array() there can error() (set_config() can shrink
__MAX_ARRAY_SIZE__ at runtime below a shared args array's size), and the
old trailing release loop would have leaked every held ref on that
unwind (AGENTS.md section 4).
- documented the pre-existing map_delete()-class caveat: deleting a
key-closed loop's node while an outer unlocked foreach-ref variable is
aimed at it dangles that variable (not specific to this efun; noted in
code and doc).
- extended orphan-collector coverage from 6 to 10 blocks: class rings
(TAG_CLASS candidate/sever/free_class paths), mapping pairs closed in
KEY position (the collector's in-place key-zeroing sever path), and a
buffer payload riding an orphaned ring (sever must release it or the
Debug ref gate trips); added a destructed-object-in-walked-value test
(render_key + leaf handling).
- docs: refs.md and copy.md now link back to the cycle tooling; zh-CN
sidebar translation keys rescaffolded; AGENTS.md section 7 documents the
new hard gate and the catch + find_orphaned_cycles(1) teardown pattern.
- re-entrancy audit (foreach/MAP_LOCKED/locked_map_nodes/merge_arg_lists)
and LPC-test-semantics audit returned no code defects.
Claude-Session: https://claude.ai/code/session_01VaksxbPjc3hjzghoQPUHRo
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 00:06:37 -07:00
{
"type" : "doc" ,
"id" : "efun/contrib/find_cycles" ,
"key" : "efun/contrib/find_cycles" ,
"label" : "find_cycles"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "efun/contrib/function_owner" ,
"key" : "efun/contrib/function_owner" ,
"label" : "function_owner"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/functions" ,
"key" : "efun/contrib/functions" ,
"label" : "functions"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/get_garbage" ,
"key" : "efun/contrib/get_garbage" ,
"label" : "get_garbage"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/get_os_env" ,
"key" : "efun/contrib/get_os_env" ,
"label" : "get_os_env"
} ,
reference loops: docs, runtime cycle efuns, orphan collector, copy() unwind fix (#1276)
A complete treatment of reference loops (cyclic data structures) in LPC:
the reference-counting VM has no cycle collector, so a value that reaches
itself leaks permanently -- and silently -- once the last outside
reference is dropped, and it cannot be saved, deep-copied, or fully
printed in the meantime. This change documents the problem, fixes a
driver memory-safety bug it exposes, and adds runtime tooling to detect,
locate, break, and (on debug builds) collect such loops.
Driver fixes:
- copy() on a cyclic structure always hits the MAX_SAVE_SVALUE_DEPTH
error(), and that unwind path leaked every partially-built container --
allocated with the _empty_ (uninitialized-svalue) allocators and holding
a borrowed, un-ref-counted pointer to the ORIGINAL value, which the
Debug memory checker then read after free (heap-use-after-free abort
under ASan, reproducible with just catch(copy(a)) on a[0] = a).
deep_copy_* now allocate zero-filled, hold the destination in a
unique_ptr whose deleter is the tag-symmetric free_*, and only write the
destination slot after the child copy fully succeeded (ffi.cc precedent,
AGENTS.md section 4).
- save_object/save_variable and copy()'s 'nested too deep' errors -- the
classic symptom of a loop -- now say so (the has_cycle() pointer is
gated on PACKAGE_CONTRIB so core never recommends an efun the build
lacks).
New efuns (contrib package, src/packages/contrib/cycles.cc):
- has_cycle(mixed): 1 if the value's reference graph contains a loop.
- find_cycles(mixed): one index-path string per loop-closing slot
("[3][\"peer\"].1" style).
- break_cycles(mixed): clears every loop in place and returns the number
of edges broken. Exactly the DFS back-edges are touched (a digraph is
acyclic iff its DFS has no back-edges): item/value slots are zeroed, a
loop closed in mapping-KEY position has its node deleted (hashed keys
cannot be overwritten), and a loop closing on the funptr->args edge
itself -- possible because bind() SHARES the args array between the old
and new funptr -- detaches the bound funptr's args list and replaces it
with a zero-filled one of the same size. DAG sharing is never touched;
one cut un-loops a whole ring; afterwards the value saves, copies,
prints, and frees normally.
All three share one ITERATIVE walk (explicit heap stack, white/grey/
black coloring): no C-stack recursion, no depth cap -- arbitrarily deep
acyclic values scan cleanly where save_variable() errors. Edges:
array/class items, mapping keys AND values, fp->hdr.args; objects are
deliberately leaves (loops through object variables are the
destruct()-managed kind: destruct2() zeroes the variable block).
break_cycles() records fixes during a mutation-free walk and applies
them in a post-pass that holds a reference on every touched container,
zeroes slots before deleting nodes (only node deletion can cascade
frees), and releases the holds last -- order-independent and safe
against shared/overlapping fixes.
Orphaned-loop collector (develop package, Debug/DEBUGMALLOC_EXTENSIONS):
- find_orphaned_cycles(int collect): finds -- and with any nonzero
argument reclaims -- data blocks that are unreachable because only a
reference loop keeps them alive: the case nothing LPC-level can reach
anymore. Detection is trial deletion (CPython-gc-style), implemented in
md_scan_orphaned_cycles (checkmemory.cc): count each array/class/
mapping/funptr's references held by OTHER data blocks; a block whose
real ref count exceeds that is externally held (object variables, VM
stack, call_out, any C++-side holder) and seeds liveness, which
propagates along data edges; the remainder is loop garbage. No root
enumeration to get wrong -- every legitimate holder shows up as an
external ref. Collection: hold a ref on every dead block, sever all
their child slots (releasing strings/objects/buffers/live values
normally), then release the holds -- each dead block deallocates with
nothing left to cascade into.
- check_all_blocks() runs the same scan (skippable via new flag bit 2,
value 4) and reports 'unreachable data block(s) kept alive only by
reference loop(s)', so the testsuite's per-file check_memory() gate
turns a dropped cycle into a hard, attributed failure. That immediately
caught a real pre-existing leak: tests/std/json.lpc's
test_encode_circular_references() dropped all four of its
deliberately-cyclic fixtures on every suite run since it was written.
Tests (testsuite/single/tests/):
- operators/reference_loop.lpc pins the driver contract around loops and
crashes the unfixed Debug/ASan driver (the copy() unwind UAF).
- efuns/has_cycle.lpc, find_cycles.lpc, break_cycles.lpc cover self/
mutual/ring loops across arrays, mappings (value and key position),
classes, funptr args (including the bind()-shared-args case, which was
unbreakable in an earlier revision of this change), DAG-sharing
preservation, save/copy working again after a break, idempotency, and a
5000-deep acyclic walk.
- efuns/find_orphaned_cycles.lpc pins baseline-relative detection of 6
orphans across three dropped loop shapes, idempotent detection, that
reachable loops are never classified as garbage, and that collection
reclaims everything while reachable data survives.
- Every cycle-building test has UNCONDITIONAL teardown (body in catch(),
find_orphaned_cycles(1) regardless, error re-raised) so a mid-test
regression stays one [ FAILED ] entry instead of cascading the
harness's LEAK gate into a suite-wide abort (AGENTS.md section 7).
Docs (Docusaurus, sidebar regenerated; full two-locale build verified):
- new concepts page docs/concepts/general/reference_loops.md: why loops
leak, what each recursive consumer does, the destruct() exception,
prevention patterns, the runtime tools, and the debug-build collector;
- efun pages for all four new efuns; check_memory.md documents the new
scan and flag bit.
Validated on Debug+ASan/UBSan (full LPC suite, randomized order, multiple
runs) and RelWithDebInfo (full suite), 313 GTest unit tests, plus an
8-angle adversarially-verified self-review.
Round-2 self-review (4 fresh angles, adversarially verified) additionally:
- break_cycles() post-pass releases its held container references via an
RAII guard: allocate_array() there can error() (set_config() can shrink
__MAX_ARRAY_SIZE__ at runtime below a shared args array's size), and the
old trailing release loop would have leaked every held ref on that
unwind (AGENTS.md section 4).
- documented the pre-existing map_delete()-class caveat: deleting a
key-closed loop's node while an outer unlocked foreach-ref variable is
aimed at it dangles that variable (not specific to this efun; noted in
code and doc).
- extended orphan-collector coverage from 6 to 10 blocks: class rings
(TAG_CLASS candidate/sever/free_class paths), mapping pairs closed in
KEY position (the collector's in-place key-zeroing sever path), and a
buffer payload riding an orphaned ring (sever must release it or the
Debug ref gate trips); added a destructed-object-in-walked-value test
(render_key + leaf handling).
- docs: refs.md and copy.md now link back to the cycle tooling; zh-CN
sidebar translation keys rescaffolded; AGENTS.md section 7 documents the
new hard gate and the catch + find_orphaned_cycles(1) teardown pattern.
- re-entrancy audit (foreach/MAP_LOCKED/locked_map_nodes/merge_arg_lists)
and LPC-test-semantics audit returned no code defects.
Claude-Session: https://claude.ai/code/session_01VaksxbPjc3hjzghoQPUHRo
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 00:06:37 -07:00
{
"type" : "doc" ,
"id" : "efun/contrib/has_cycle" ,
"key" : "efun/contrib/has_cycle" ,
"label" : "has_cycle"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "efun/contrib/heart_beats" ,
"key" : "efun/contrib/heart_beats" ,
"label" : "heart_beats"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/is_daylight_savings_time" ,
"key" : "efun/contrib/is_daylight_savings_time" ,
"label" : "is_daylight_savings_time"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/max" ,
"key" : "efun/contrib/max" ,
"label" : "max"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/memory_summary" ,
"key" : "efun/contrib/memory_summary" ,
"label" : "memory_summary"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/min" ,
"key" : "efun/contrib/min" ,
"label" : "min"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/named_livings" ,
"key" : "efun/contrib/named_livings" ,
"label" : "named_livings"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/network_stats" ,
"key" : "efun/contrib/network_stats" ,
"label" : "network_stats"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/num_classes" ,
"key" : "efun/contrib/num_classes" ,
"label" : "num_classes"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/pluralize" ,
"key" : "efun/contrib/pluralize" ,
"label" : "pluralize"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/program_info" ,
"key" : "efun/contrib/program_info" ,
"label" : "program_info"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_charmode" ,
"key" : "efun/contrib/query_charmode" ,
"label" : "query_charmode"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_ip_port" ,
"key" : "efun/contrib/query_ip_port" ,
"label" : "query_ip_port"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_multiple_short" ,
"key" : "efun/contrib/query_multiple_short" ,
"label" : "query_multiple_short"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_notify_fail" ,
"key" : "efun/contrib/query_notify_fail" ,
"label" : "query_notify_fail"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_num" ,
"key" : "efun/contrib/query_num" ,
"label" : "query_num"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/query_replaced_program" ,
"key" : "efun/contrib/query_replaced_program" ,
"label" : "query_replaced_program"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/real_time" ,
"key" : "efun/contrib/real_time" ,
"label" : "real_time"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/reference_allowed" ,
"key" : "efun/contrib/reference_allowed" ,
"label" : "reference_allowed"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/remove_charmode" ,
"key" : "efun/contrib/remove_charmode" ,
"label" : "remove_charmode"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/remove_get_char" ,
"key" : "efun/contrib/remove_get_char" ,
"label" : "remove_get_char"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/remove_interactive" ,
"key" : "efun/contrib/remove_interactive" ,
"label" : "remove_interactive"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/remove_shadow" ,
"key" : "efun/contrib/remove_shadow" ,
"label" : "remove_shadow"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/repeat_string" ,
"key" : "efun/contrib/repeat_string" ,
"label" : "repeat_string"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replace" ,
"key" : "efun/contrib/replace" ,
"label" : "replace"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replace_dollars" ,
"key" : "efun/contrib/replace_dollars" ,
"label" : "replace_dollars"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replace_html" ,
"key" : "efun/contrib/replace_html" ,
"label" : "replace_html"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replace_mxp" ,
"key" : "efun/contrib/replace_mxp" ,
"label" : "replace_mxp"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replace_objects" ,
"key" : "efun/contrib/replace_objects" ,
"label" : "replace_objects"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/replaceable" ,
"key" : "efun/contrib/replaceable" ,
"label" : "replaceable"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/restore_from_string" ,
"key" : "efun/contrib/restore_from_string" ,
"label" : "restore_from_string"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/roll_MdN" ,
"key" : "efun/contrib/roll_MdN" ,
"label" : "roll_MdN"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/roulette_wheel" ,
"key" : "efun/contrib/roulette_wheel" ,
"label" : "roulette_wheel"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/send_nullbyte" ,
"key" : "efun/contrib/send_nullbyte" ,
"label" : "send_nullbyte"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/set_os_env" ,
"key" : "efun/contrib/set_os_env" ,
"label" : "set_os_env"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/set_prompt" ,
"key" : "efun/contrib/set_prompt" ,
"label" : "set_prompt"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/store_class_member" ,
"key" : "efun/contrib/store_class_member" ,
"label" : "store_class_member"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/store_variable" ,
"key" : "efun/contrib/store_variable" ,
"label" : "store_variable"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/string_difference" ,
"key" : "efun/contrib/string_difference" ,
"label" : "string_difference"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/terminal_colour" ,
"key" : "efun/contrib/terminal_colour" ,
"label" : "terminal_colour"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/test_load" ,
"key" : "efun/contrib/test_load" ,
"label" : "test_load"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/upper_case" ,
"key" : "efun/contrib/upper_case" ,
"label" : "upper_case"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/variables" ,
"key" : "efun/contrib/variables" ,
"label" : "variables"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/vowel" ,
"key" : "efun/contrib/vowel" ,
"label" : "vowel"
} ,
{
"type" : "doc" ,
"id" : "efun/contrib/zonetime" ,
"key" : "efun/contrib/zonetime" ,
"label" : "zonetime"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/crypto" ,
"label" : "Cryptography" ,
"link" : {
"type" : "generated-index" ,
"title" : "Cryptography" ,
"slug" : "/efun/crypto/" ,
"description" : "Cryptographic hashing helpers."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/crypto/hash" ,
"key" : "efun/crypto/hash" ,
"label" : "hash"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/db" ,
"label" : "Database" ,
"link" : {
"type" : "generated-index" ,
"title" : "Database" ,
"slug" : "/efun/db/" ,
"description" : "Connect to SQL databases and run queries, commits and rollbacks."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/db/db_close" ,
"key" : "efun/db/db_close" ,
"label" : "db_close"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_commit" ,
"key" : "efun/db/db_commit" ,
"label" : "db_commit"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_connect" ,
"key" : "efun/db/db_connect" ,
"label" : "db_connect"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_exec" ,
"key" : "efun/db/db_exec" ,
"label" : "db_exec"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_fetch" ,
"key" : "efun/db/db_fetch" ,
"label" : "db_fetch"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_rollback" ,
"key" : "efun/db/db_rollback" ,
"label" : "db_rollback"
} ,
{
"type" : "doc" ,
"id" : "efun/db/db_status" ,
"key" : "efun/db/db_status" ,
"label" : "db_status"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/ed" ,
"label" : "Line Editor (ed)" ,
"link" : {
"type" : "generated-index" ,
"title" : "Line Editor (ed)" ,
"slug" : "/efun/ed/" ,
"description" : "Drive the built-in ed line editor from LPC."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/ed/ed_cmd" ,
"key" : "efun/ed/ed_cmd" ,
"label" : "ed_cmd"
} ,
{
"type" : "doc" ,
"id" : "efun/ed/ed_start" ,
"key" : "efun/ed/ed_start" ,
"label" : "ed_start"
} ,
{
"type" : "doc" ,
"id" : "efun/ed/query_ed_mode" ,
"key" : "efun/ed/query_ed_mode" ,
"label" : "query_ed_mode"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/external" ,
"label" : "External Processes" ,
"link" : {
"type" : "generated-index" ,
"title" : "External Processes" ,
"slug" : "/efun/external/" ,
"description" : "Launch and communicate with external programs."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/external/external_start" ,
"key" : "efun/external/external_start" ,
"label" : "external_start"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/ffi" ,
"label" : "Foreign Function Interface" ,
"link" : {
"type" : "generated-index" ,
"title" : "Foreign Function Interface" ,
"slug" : "/efun/ffi/" ,
"description" : "Load native libraries and call C functions, allocate memory and read/write structs."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_address" ,
"key" : "efun/ffi/ffi_address" ,
"label" : "ffi_address"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_alloc" ,
"key" : "efun/ffi/ffi_alloc" ,
"label" : "ffi_alloc"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_call" ,
"key" : "efun/ffi/ffi_call" ,
"label" : "ffi_call"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_callback" ,
"key" : "efun/ffi/ffi_callback" ,
"label" : "ffi_callback"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_callback_addr" ,
"key" : "efun/ffi/ffi_callback_addr" ,
"label" : "ffi_callback_addr"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_callback_free" ,
"key" : "efun/ffi/ffi_callback_free" ,
"label" : "ffi_callback_free"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_error" ,
"key" : "efun/ffi/ffi_error" ,
"label" : "ffi_error"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_free" ,
"key" : "efun/ffi/ffi_free" ,
"label" : "ffi_free"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_load" ,
"key" : "efun/ffi/ffi_load" ,
"label" : "ffi_load"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_peek" ,
"key" : "efun/ffi/ffi_peek" ,
"label" : "ffi_peek"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_prepare" ,
"key" : "efun/ffi/ffi_prepare" ,
"label" : "ffi_prepare"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_read" ,
"key" : "efun/ffi/ffi_read" ,
"label" : "ffi_read"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_sizeof" ,
"key" : "efun/ffi/ffi_sizeof" ,
"label" : "ffi_sizeof"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_status" ,
"key" : "efun/ffi/ffi_status" ,
"label" : "ffi_status"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_struct_layout" ,
"key" : "efun/ffi/ffi_struct_layout" ,
"label" : "ffi_struct_layout"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_symbol" ,
"key" : "efun/ffi/ffi_symbol" ,
"label" : "ffi_symbol"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_unload" ,
"key" : "efun/ffi/ffi_unload" ,
"label" : "ffi_unload"
} ,
{
"type" : "doc" ,
"id" : "efun/ffi/ffi_write" ,
"key" : "efun/ffi/ffi_write" ,
"label" : "ffi_write"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/filesystem" ,
"label" : "Filesystem" ,
"link" : {
"type" : "generated-index" ,
"title" : "Filesystem" ,
"slug" : "/efun/filesystem/" ,
"description" : "Read, write, copy, move and inspect files and directories."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/filesystem/cp" ,
"key" : "efun/filesystem/cp" ,
"label" : "cp"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/file_size" ,
"key" : "efun/filesystem/file_size" ,
"label" : "file_size"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/get_dir" ,
"key" : "efun/filesystem/get_dir" ,
"label" : "get_dir"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/link" ,
"key" : "efun/filesystem/link" ,
"label" : "link"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/mkdir" ,
"key" : "efun/filesystem/mkdir" ,
"label" : "mkdir"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/read_bytes" ,
"key" : "efun/filesystem/read_bytes" ,
"label" : "read_bytes"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/read_file" ,
"key" : "efun/filesystem/read_file" ,
"label" : "read_file"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/rename" ,
"key" : "efun/filesystem/rename" ,
"label" : "rename"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/rm" ,
"key" : "efun/filesystem/rm" ,
"label" : "rm"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/rmdir" ,
"key" : "efun/filesystem/rmdir" ,
"label" : "rmdir"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/stat" ,
"key" : "efun/filesystem/stat" ,
"label" : "stat"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/write_bytes" ,
"key" : "efun/filesystem/write_bytes" ,
"label" : "write_bytes"
} ,
{
"type" : "doc" ,
"id" : "efun/filesystem/write_file" ,
"key" : "efun/filesystem/write_file" ,
"label" : "write_file"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/floats" ,
"label" : "Floating-Point Math" ,
"link" : {
"type" : "generated-index" ,
"title" : "Floating-Point Math" ,
"slug" : "/efun/floats/" ,
"description" : "Trigonometry, logarithms, powers, rounding and float conversions."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/floats/acos" ,
"key" : "efun/floats/acos" ,
"label" : "acos"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/asin" ,
"key" : "efun/floats/asin" ,
"label" : "asin"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/atan" ,
"key" : "efun/floats/atan" ,
"label" : "atan"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/ceil" ,
"key" : "efun/floats/ceil" ,
"label" : "ceil"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/cos" ,
"key" : "efun/floats/cos" ,
"label" : "cos"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/exp" ,
"key" : "efun/floats/exp" ,
"label" : "exp"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/floatp" ,
"key" : "efun/floats/floatp" ,
"label" : "floatp"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/floor" ,
"key" : "efun/floats/floor" ,
"label" : "floor"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/log" ,
"key" : "efun/floats/log" ,
"label" : "log"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/pow" ,
"key" : "efun/floats/pow" ,
"label" : "pow"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/round" ,
"key" : "efun/floats/round" ,
"label" : "round"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/sin" ,
"key" : "efun/floats/sin" ,
"label" : "sin"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/sqrt" ,
"key" : "efun/floats/sqrt" ,
"label" : "sqrt"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/tan" ,
"key" : "efun/floats/tan" ,
"label" : "tan"
} ,
{
"type" : "doc" ,
"id" : "efun/floats/to_int" ,
"key" : "efun/floats/to_int" ,
"label" : "to_int"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/functions" ,
"label" : "Function Pointers" ,
"link" : {
"type" : "generated-index" ,
"title" : "Function Pointers" ,
"slug" : "/efun/functions/" ,
"description" : "Create, bind, defer and evaluate function pointers."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/functions/bind" ,
"key" : "efun/functions/bind" ,
"label" : "bind"
} ,
{
"type" : "doc" ,
"id" : "efun/functions/defer" ,
"key" : "efun/functions/defer" ,
"label" : "defer"
} ,
{
"type" : "doc" ,
"id" : "efun/functions/evaluate" ,
"key" : "efun/functions/evaluate" ,
"label" : "evaluate"
} ,
{
"type" : "doc" ,
"id" : "efun/functions/functionp" ,
"key" : "efun/functions/functionp" ,
"label" : "functionp"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/general" ,
"label" : "General" ,
"link" : {
"type" : "generated-index" ,
"title" : "General" ,
"slug" : "/efun/general/" ,
"description" : "Miscellaneous efuns: vector and matrix math, compression, type predicates, save/restore and generic collection helpers."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/general/angle" ,
"key" : "efun/general/angle" ,
"label" : "angle"
} ,
{
"type" : "doc" ,
"id" : "efun/general/classp" ,
"key" : "efun/general/classp" ,
"label" : "classp"
} ,
{
"type" : "doc" ,
"id" : "efun/general/compress" ,
"key" : "efun/general/compress" ,
"label" : "compress"
} ,
{
"type" : "doc" ,
"id" : "efun/general/compress_file" ,
"key" : "efun/general/compress_file" ,
"label" : "compress_file"
} ,
{
"type" : "doc" ,
"id" : "efun/general/distance" ,
"key" : "efun/general/distance" ,
"label" : "distance"
} ,
{
"type" : "doc" ,
"id" : "efun/general/dotprod" ,
"key" : "efun/general/dotprod" ,
"label" : "dotprod"
} ,
{
"type" : "doc" ,
"id" : "efun/general/explode_reversible" ,
"key" : "efun/general/explode_reversible" ,
"label" : "explode_reversible"
} ,
{
"type" : "doc" ,
"id" : "efun/general/filter" ,
"key" : "efun/general/filter" ,
"label" : "filter"
} ,
{
"type" : "doc" ,
"id" : "efun/general/id_matrix" ,
"key" : "efun/general/id_matrix" ,
"label" : "id_matrix"
} ,
{
"type" : "doc" ,
"id" : "efun/general/log10" ,
"key" : "efun/general/log10" ,
"label" : "log10"
} ,
{
"type" : "doc" ,
"id" : "efun/general/log2" ,
"key" : "efun/general/log2" ,
"label" : "log2"
} ,
{
"type" : "doc" ,
"id" : "efun/general/lookat_rotate" ,
"key" : "efun/general/lookat_rotate" ,
"label" : "lookat_rotate"
} ,
{
"type" : "doc" ,
"id" : "efun/general/lookat_rotate2" ,
"key" : "efun/general/lookat_rotate2" ,
"label" : "lookat_rotate2"
} ,
{
"type" : "doc" ,
"id" : "efun/general/map" ,
"key" : "efun/general/map" ,
"label" : "map"
} ,
{
"type" : "doc" ,
"id" : "efun/general/next_bit" ,
"key" : "efun/general/next_bit" ,
"label" : "next_bit"
} ,
{
"type" : "doc" ,
"id" : "efun/general/norm" ,
"key" : "efun/general/norm" ,
"label" : "norm"
} ,
{
"type" : "doc" ,
"id" : "efun/general/nullp" ,
"key" : "efun/general/nullp" ,
"label" : "nullp"
} ,
{
"type" : "doc" ,
"id" : "efun/general/restore_variable" ,
"key" : "efun/general/restore_variable" ,
"label" : "restore_variable"
} ,
{
"type" : "doc" ,
"id" : "efun/general/rotate_x" ,
"key" : "efun/general/rotate_x" ,
"label" : "rotate_x"
} ,
{
"type" : "doc" ,
"id" : "efun/general/rotate_y" ,
"key" : "efun/general/rotate_y" ,
"label" : "rotate_y"
} ,
{
"type" : "doc" ,
"id" : "efun/general/rotate_z" ,
"key" : "efun/general/rotate_z" ,
"label" : "rotate_z"
} ,
{
"type" : "doc" ,
"id" : "efun/general/save_variable" ,
"key" : "efun/general/save_variable" ,
"label" : "save_variable"
} ,
{
"type" : "doc" ,
"id" : "efun/general/scale" ,
"key" : "efun/general/scale" ,
"label" : "scale"
} ,
{
"type" : "doc" ,
"id" : "efun/general/sizeof" ,
"key" : "efun/general/sizeof" ,
"label" : "sizeof"
} ,
{
"type" : "doc" ,
"id" : "efun/general/translate" ,
"key" : "efun/general/translate" ,
"label" : "translate"
} ,
{
"type" : "doc" ,
"id" : "efun/general/typeof" ,
"key" : "efun/general/typeof" ,
"label" : "typeof"
} ,
{
"type" : "doc" ,
"id" : "efun/general/uncompress" ,
"key" : "efun/general/uncompress" ,
"label" : "uncompress"
} ,
{
"type" : "doc" ,
"id" : "efun/general/uncompress_file" ,
"key" : "efun/general/uncompress_file" ,
"label" : "uncompress_file"
} ,
{
"type" : "doc" ,
"id" : "efun/general/undefinedp" ,
"key" : "efun/general/undefinedp" ,
"label" : "undefinedp"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/interactive" ,
"label" : "Interactive & Networking" ,
"link" : {
"type" : "generated-index" ,
"title" : "Interactive & Networking" ,
"slug" : "/efun/interactive/" ,
"description" : "Player connections, input/output, snooping, telnet negotiation and terminal protocols (GMCP, MSDP, MXP, MSP, ZMP)."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/interactive/act_mxp" ,
"key" : "efun/interactive/act_mxp" ,
"label" : "act_mxp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/add_action" ,
"key" : "efun/interactive/add_action" ,
"label" : "add_action"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/command" ,
"key" : "efun/interactive/command" ,
"label" : "command"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/commands" ,
"key" : "efun/interactive/commands" ,
"label" : "commands"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/disable_commands" ,
"key" : "efun/interactive/disable_commands" ,
"label" : "disable_commands"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/disable_wizard" ,
"key" : "efun/interactive/disable_wizard" ,
"label" : "disable_wizard"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/ed" ,
"key" : "efun/interactive/ed" ,
"label" : "ed"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/enable_commands" ,
"key" : "efun/interactive/enable_commands" ,
"label" : "enable_commands"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/exec" ,
"key" : "efun/interactive/exec" ,
"label" : "exec"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/find_player" ,
"key" : "efun/interactive/find_player" ,
"label" : "find_player"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/get_char" ,
"key" : "efun/interactive/get_char" ,
"label" : "get_char"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/has_gmcp" ,
"key" : "efun/interactive/has_gmcp" ,
"label" : "has_gmcp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/has_msdp" ,
"key" : "efun/interactive/has_msdp" ,
"label" : "has_msdp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/has_msp" ,
"key" : "efun/interactive/has_msp" ,
"label" : "has_msp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/has_mxp" ,
"key" : "efun/interactive/has_mxp" ,
"label" : "has_mxp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/has_zmp" ,
"key" : "efun/interactive/has_zmp" ,
"label" : "has_zmp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/in_edit" ,
"key" : "efun/interactive/in_edit" ,
"label" : "in_edit"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/in_input" ,
"key" : "efun/interactive/in_input" ,
"label" : "in_input"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/input_to" ,
"key" : "efun/interactive/input_to" ,
"label" : "input_to"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/interactive" ,
"key" : "efun/interactive/interactive" ,
"label" : "interactive"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/message" ,
"key" : "efun/interactive/message" ,
"label" : "message"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/notify_fail" ,
"key" : "efun/interactive/notify_fail" ,
"label" : "notify_fail"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/printf" ,
"key" : "efun/interactive/printf" ,
"label" : "printf"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_encoding" ,
"key" : "efun/interactive/query_encoding" ,
"label" : "query_encoding"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_host_name" ,
"key" : "efun/interactive/query_host_name" ,
"label" : "query_host_name"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_idle" ,
"key" : "efun/interactive/query_idle" ,
"label" : "query_idle"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_ip_name" ,
"key" : "efun/interactive/query_ip_name" ,
"label" : "query_ip_name"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_ip_number" ,
"key" : "efun/interactive/query_ip_number" ,
"label" : "query_ip_number"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_snoop" ,
"key" : "efun/interactive/query_snoop" ,
"label" : "query_snoop"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/query_snooping" ,
"key" : "efun/interactive/query_snooping" ,
"label" : "query_snooping"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/receive" ,
"key" : "efun/interactive/receive" ,
"label" : "receive"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/remove_action" ,
"key" : "efun/interactive/remove_action" ,
"label" : "remove_action"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/request_term_size" ,
"key" : "efun/interactive/request_term_size" ,
"label" : "request_term_size"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/request_term_type" ,
"key" : "efun/interactive/request_term_type" ,
"label" : "request_term_type"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/resolve" ,
"key" : "efun/interactive/resolve" ,
"label" : "resolve"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/say" ,
"key" : "efun/interactive/say" ,
"label" : "say"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/send_gmcp" ,
"key" : "efun/interactive/send_gmcp" ,
"label" : "send_gmcp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/send_msdp_variable" ,
"key" : "efun/interactive/send_msdp_variable" ,
"label" : "send_msdp_variable"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/send_zmp" ,
"key" : "efun/interactive/send_zmp" ,
"label" : "send_zmp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/set_encoding" ,
"key" : "efun/interactive/set_encoding" ,
"label" : "set_encoding"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/set_this_player" ,
"key" : "efun/interactive/set_this_player" ,
"label" : "set_this_player"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/set_this_user" ,
"key" : "efun/interactive/set_this_user" ,
"label" : "set_this_user"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/shout" ,
"key" : "efun/interactive/shout" ,
"label" : "shout"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/snoop" ,
"key" : "efun/interactive/snoop" ,
"label" : "snoop"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/start_request_term_type" ,
"key" : "efun/interactive/start_request_term_type" ,
"label" : "start_request_term_type"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/telnet_ga" ,
"key" : "efun/interactive/telnet_ga" ,
"label" : "telnet_ga"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/telnet_msp_oob" ,
"key" : "efun/interactive/telnet_msp_oob" ,
"label" : "telnet_msp_oob"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/telnet_nop" ,
"key" : "efun/interactive/telnet_nop" ,
"label" : "telnet_nop"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/this_interactive" ,
"key" : "efun/interactive/this_interactive" ,
"label" : "this_interactive"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/this_player" ,
"key" : "efun/interactive/this_player" ,
"label" : "this_player"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/this_user" ,
"key" : "efun/interactive/this_user" ,
"label" : "this_user"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/userp" ,
"key" : "efun/interactive/userp" ,
"label" : "userp"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/users" ,
"key" : "efun/interactive/users" ,
"label" : "users"
} ,
{
"type" : "doc" ,
"id" : "efun/interactive/write" ,
"key" : "efun/interactive/write" ,
"label" : "write"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/internals" ,
"label" : "Driver Internals" ,
"link" : {
"type" : "generated-index" ,
"title" : "Driver Internals" ,
"slug" : "/efun/internals/" ,
"description" : "Driver introspection, debugging, memory, tracing and runtime configuration."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/internals/cache_stats" ,
"key" : "efun/internals/cache_stats" ,
"label" : "cache_stats"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/check_memory" ,
"key" : "efun/internals/check_memory" ,
"label" : "check_memory"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/clear_debug_level" ,
"key" : "efun/internals/clear_debug_level" ,
"label" : "clear_debug_level"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/debug_info" ,
"key" : "efun/internals/debug_info" ,
"label" : "debug_info"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/debug_levels" ,
"key" : "efun/internals/debug_levels" ,
"label" : "debug_levels"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/debugmalloc" ,
"key" : "efun/internals/debugmalloc" ,
"label" : "debugmalloc"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/destructed_objects" ,
"key" : "efun/internals/destructed_objects" ,
"label" : "destructed_objects"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dump_file_descriptors" ,
"key" : "efun/internals/dump_file_descriptors" ,
"label" : "dump_file_descriptors"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dump_jemalloc" ,
"key" : "efun/internals/dump_jemalloc" ,
"label" : "dump_jemalloc"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dump_prog" ,
"key" : "efun/internals/dump_prog" ,
"label" : "dump_prog"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dump_stralloc" ,
"key" : "efun/internals/dump_stralloc" ,
"label" : "dump_stralloc"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dump_trace" ,
"key" : "efun/internals/dump_trace" ,
"label" : "dump_trace"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/dumpallobj" ,
"key" : "efun/internals/dumpallobj" ,
"label" : "dumpallobj"
} ,
reference loops: docs, runtime cycle efuns, orphan collector, copy() unwind fix (#1276)
A complete treatment of reference loops (cyclic data structures) in LPC:
the reference-counting VM has no cycle collector, so a value that reaches
itself leaks permanently -- and silently -- once the last outside
reference is dropped, and it cannot be saved, deep-copied, or fully
printed in the meantime. This change documents the problem, fixes a
driver memory-safety bug it exposes, and adds runtime tooling to detect,
locate, break, and (on debug builds) collect such loops.
Driver fixes:
- copy() on a cyclic structure always hits the MAX_SAVE_SVALUE_DEPTH
error(), and that unwind path leaked every partially-built container --
allocated with the _empty_ (uninitialized-svalue) allocators and holding
a borrowed, un-ref-counted pointer to the ORIGINAL value, which the
Debug memory checker then read after free (heap-use-after-free abort
under ASan, reproducible with just catch(copy(a)) on a[0] = a).
deep_copy_* now allocate zero-filled, hold the destination in a
unique_ptr whose deleter is the tag-symmetric free_*, and only write the
destination slot after the child copy fully succeeded (ffi.cc precedent,
AGENTS.md section 4).
- save_object/save_variable and copy()'s 'nested too deep' errors -- the
classic symptom of a loop -- now say so (the has_cycle() pointer is
gated on PACKAGE_CONTRIB so core never recommends an efun the build
lacks).
New efuns (contrib package, src/packages/contrib/cycles.cc):
- has_cycle(mixed): 1 if the value's reference graph contains a loop.
- find_cycles(mixed): one index-path string per loop-closing slot
("[3][\"peer\"].1" style).
- break_cycles(mixed): clears every loop in place and returns the number
of edges broken. Exactly the DFS back-edges are touched (a digraph is
acyclic iff its DFS has no back-edges): item/value slots are zeroed, a
loop closed in mapping-KEY position has its node deleted (hashed keys
cannot be overwritten), and a loop closing on the funptr->args edge
itself -- possible because bind() SHARES the args array between the old
and new funptr -- detaches the bound funptr's args list and replaces it
with a zero-filled one of the same size. DAG sharing is never touched;
one cut un-loops a whole ring; afterwards the value saves, copies,
prints, and frees normally.
All three share one ITERATIVE walk (explicit heap stack, white/grey/
black coloring): no C-stack recursion, no depth cap -- arbitrarily deep
acyclic values scan cleanly where save_variable() errors. Edges:
array/class items, mapping keys AND values, fp->hdr.args; objects are
deliberately leaves (loops through object variables are the
destruct()-managed kind: destruct2() zeroes the variable block).
break_cycles() records fixes during a mutation-free walk and applies
them in a post-pass that holds a reference on every touched container,
zeroes slots before deleting nodes (only node deletion can cascade
frees), and releases the holds last -- order-independent and safe
against shared/overlapping fixes.
Orphaned-loop collector (develop package, Debug/DEBUGMALLOC_EXTENSIONS):
- find_orphaned_cycles(int collect): finds -- and with any nonzero
argument reclaims -- data blocks that are unreachable because only a
reference loop keeps them alive: the case nothing LPC-level can reach
anymore. Detection is trial deletion (CPython-gc-style), implemented in
md_scan_orphaned_cycles (checkmemory.cc): count each array/class/
mapping/funptr's references held by OTHER data blocks; a block whose
real ref count exceeds that is externally held (object variables, VM
stack, call_out, any C++-side holder) and seeds liveness, which
propagates along data edges; the remainder is loop garbage. No root
enumeration to get wrong -- every legitimate holder shows up as an
external ref. Collection: hold a ref on every dead block, sever all
their child slots (releasing strings/objects/buffers/live values
normally), then release the holds -- each dead block deallocates with
nothing left to cascade into.
- check_all_blocks() runs the same scan (skippable via new flag bit 2,
value 4) and reports 'unreachable data block(s) kept alive only by
reference loop(s)', so the testsuite's per-file check_memory() gate
turns a dropped cycle into a hard, attributed failure. That immediately
caught a real pre-existing leak: tests/std/json.lpc's
test_encode_circular_references() dropped all four of its
deliberately-cyclic fixtures on every suite run since it was written.
Tests (testsuite/single/tests/):
- operators/reference_loop.lpc pins the driver contract around loops and
crashes the unfixed Debug/ASan driver (the copy() unwind UAF).
- efuns/has_cycle.lpc, find_cycles.lpc, break_cycles.lpc cover self/
mutual/ring loops across arrays, mappings (value and key position),
classes, funptr args (including the bind()-shared-args case, which was
unbreakable in an earlier revision of this change), DAG-sharing
preservation, save/copy working again after a break, idempotency, and a
5000-deep acyclic walk.
- efuns/find_orphaned_cycles.lpc pins baseline-relative detection of 6
orphans across three dropped loop shapes, idempotent detection, that
reachable loops are never classified as garbage, and that collection
reclaims everything while reachable data survives.
- Every cycle-building test has UNCONDITIONAL teardown (body in catch(),
find_orphaned_cycles(1) regardless, error re-raised) so a mid-test
regression stays one [ FAILED ] entry instead of cascading the
harness's LEAK gate into a suite-wide abort (AGENTS.md section 7).
Docs (Docusaurus, sidebar regenerated; full two-locale build verified):
- new concepts page docs/concepts/general/reference_loops.md: why loops
leak, what each recursive consumer does, the destruct() exception,
prevention patterns, the runtime tools, and the debug-build collector;
- efun pages for all four new efuns; check_memory.md documents the new
scan and flag bit.
Validated on Debug+ASan/UBSan (full LPC suite, randomized order, multiple
runs) and RelWithDebInfo (full suite), 313 GTest unit tests, plus an
8-angle adversarially-verified self-review.
Round-2 self-review (4 fresh angles, adversarially verified) additionally:
- break_cycles() post-pass releases its held container references via an
RAII guard: allocate_array() there can error() (set_config() can shrink
__MAX_ARRAY_SIZE__ at runtime below a shared args array's size), and the
old trailing release loop would have leaked every held ref on that
unwind (AGENTS.md section 4).
- documented the pre-existing map_delete()-class caveat: deleting a
key-closed loop's node while an outer unlocked foreach-ref variable is
aimed at it dangles that variable (not specific to this efun; noted in
code and doc).
- extended orphan-collector coverage from 6 to 10 blocks: class rings
(TAG_CLASS candidate/sever/free_class paths), mapping pairs closed in
KEY position (the collector's in-place key-zeroing sever path), and a
buffer payload riding an orphaned ring (sever must release it or the
Debug ref gate trips); added a destructed-object-in-walked-value test
(render_key + leaf handling).
- docs: refs.md and copy.md now link back to the cycle tooling; zh-CN
sidebar translation keys rescaffolded; AGENTS.md section 7 documents the
new hard gate and the catch + find_orphaned_cycles(1) teardown pattern.
- re-entrancy audit (foreach/MAP_LOCKED/locked_map_nodes/merge_arg_lists)
and LPC-test-semantics audit returned no code defects.
Claude-Session: https://claude.ai/code/session_01VaksxbPjc3hjzghoQPUHRo
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 00:06:37 -07:00
{
"type" : "doc" ,
"id" : "efun/internals/find_orphaned_cycles" ,
"key" : "efun/internals/find_orphaned_cycles" ,
"label" : "find_orphaned_cycles"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "efun/internals/get_config" ,
"key" : "efun/internals/get_config" ,
"label" : "get_config"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/malloc_status" ,
"key" : "efun/internals/malloc_status" ,
"label" : "malloc_status"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/memory_info" ,
"key" : "efun/internals/memory_info" ,
"label" : "memory_info"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/moncontrol" ,
"key" : "efun/internals/moncontrol" ,
"label" : "moncontrol"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/mud_status" ,
"key" : "efun/internals/mud_status" ,
"label" : "mud_status"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/query_load_average" ,
"key" : "efun/internals/query_load_average" ,
"label" : "query_load_average"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/refs" ,
"key" : "efun/internals/refs" ,
"label" : "refs"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/rusage" ,
"key" : "efun/internals/rusage" ,
"label" : "rusage"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/set_config" ,
"key" : "efun/internals/set_config" ,
"label" : "set_config"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/set_debug_level" ,
"key" : "efun/internals/set_debug_level" ,
"label" : "set_debug_level"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/set_malloc_mask" ,
"key" : "efun/internals/set_malloc_mask" ,
"label" : "set_malloc_mask"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/time_expression" ,
"key" : "efun/internals/time_expression" ,
"label" : "time_expression"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/trace" ,
"key" : "efun/internals/trace" ,
"label" : "trace"
} ,
{
"type" : "doc" ,
"id" : "efun/internals/traceprefix" ,
"key" : "efun/internals/traceprefix" ,
"label" : "traceprefix"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/jsbridge" ,
"label" : "JavaScript Bridge" ,
"link" : {
"type" : "generated-index" ,
"title" : "JavaScript Bridge" ,
"slug" : "/efun/jsbridge/" ,
"description" : "Call into JavaScript from LPC on the WebAssembly build of the driver."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/jsbridge/js_call" ,
"key" : "efun/jsbridge/js_call" ,
"label" : "js_call"
} ,
{
"type" : "doc" ,
"id" : "efun/jsbridge/js_eval" ,
"key" : "efun/jsbridge/js_eval" ,
"label" : "js_eval"
} ,
{
"type" : "doc" ,
"id" : "efun/jsbridge/js_export" ,
"key" : "efun/jsbridge/js_export" ,
"label" : "js_export"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/mappings" ,
"label" : "Mappings" ,
"link" : {
"type" : "generated-index" ,
"title" : "Mappings" ,
"slug" : "/efun/mappings/" ,
"description" : "Create, filter, iterate and transform LPC mappings (associative arrays)."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/mappings/allocate_mapping" ,
"key" : "efun/mappings/allocate_mapping" ,
"label" : "allocate_mapping"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/filter_mapping" ,
"key" : "efun/mappings/filter_mapping" ,
"label" : "filter_mapping"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/keys" ,
"key" : "efun/mappings/keys" ,
"label" : "keys"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/map_delete" ,
"key" : "efun/mappings/map_delete" ,
"label" : "map_delete"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/map_mapping" ,
"key" : "efun/mappings/map_mapping" ,
"label" : "map_mapping"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/mapp" ,
"key" : "efun/mappings/mapp" ,
"label" : "mapp"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/match_path" ,
"key" : "efun/mappings/match_path" ,
"label" : "match_path"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/unique_mapping" ,
"key" : "efun/mappings/unique_mapping" ,
"label" : "unique_mapping"
} ,
{
"type" : "doc" ,
"id" : "efun/mappings/values" ,
"key" : "efun/mappings/values" ,
"label" : "values"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/mudlib" ,
"label" : "Mudlib Support" ,
"link" : {
"type" : "generated-index" ,
"title" : "Mudlib Support" ,
"slug" : "/efun/mudlib/" ,
"description" : "User/euid identities, privileges, livings and author/domain statistics."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/mudlib/author_stats" ,
"key" : "efun/mudlib/author_stats" ,
"label" : "author_stats"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/domain_stats" ,
"key" : "efun/mudlib/domain_stats" ,
"label" : "domain_stats"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/enable_wizard" ,
"key" : "efun/mudlib/enable_wizard" ,
"label" : "enable_wizard"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/export_uid" ,
"key" : "efun/mudlib/export_uid" ,
"label" : "export_uid"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/find_living" ,
"key" : "efun/mudlib/find_living" ,
"label" : "find_living"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/geteuid" ,
"key" : "efun/mudlib/geteuid" ,
"label" : "geteuid"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/getuid" ,
"key" : "efun/mudlib/getuid" ,
"label" : "getuid"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/living" ,
"key" : "efun/mudlib/living" ,
"label" : "living"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/livings" ,
"key" : "efun/mudlib/livings" ,
"label" : "livings"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/query_privs" ,
"key" : "efun/mudlib/query_privs" ,
"label" : "query_privs"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/set_author" ,
"key" : "efun/mudlib/set_author" ,
"label" : "set_author"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/set_light" ,
"key" : "efun/mudlib/set_light" ,
"label" : "set_light"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/set_living_name" ,
"key" : "efun/mudlib/set_living_name" ,
"label" : "set_living_name"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/set_privs" ,
"key" : "efun/mudlib/set_privs" ,
"label" : "set_privs"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/seteuid" ,
"key" : "efun/mudlib/seteuid" ,
"label" : "seteuid"
} ,
{
"type" : "doc" ,
"id" : "efun/mudlib/wizardp" ,
"key" : "efun/mudlib/wizardp" ,
"label" : "wizardp"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/numbers" ,
"label" : "Numbers" ,
"link" : {
"type" : "generated-index" ,
"title" : "Numbers" ,
"slug" : "/efun/numbers/" ,
"description" : "Integer predicates, random-number generation and numeric conversion."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/numbers/intp" ,
"key" : "efun/numbers/intp" ,
"label" : "intp"
} ,
{
"type" : "doc" ,
"id" : "efun/numbers/random" ,
"key" : "efun/numbers/random" ,
"label" : "random"
} ,
{
"type" : "doc" ,
"id" : "efun/numbers/secure_random" ,
"key" : "efun/numbers/secure_random" ,
"label" : "secure_random"
} ,
{
"type" : "doc" ,
"id" : "efun/numbers/to_float" ,
"key" : "efun/numbers/to_float" ,
"label" : "to_float"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/objects" ,
"label" : "Objects" ,
"link" : {
"type" : "generated-index" ,
"title" : "Objects" ,
"slug" : "/efun/objects/" ,
"description" : "Clone, load, move, find and destruct objects and manage their inventory and state."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/objects/all_inventory" ,
"key" : "efun/objects/all_inventory" ,
"label" : "all_inventory"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/children" ,
"key" : "efun/objects/children" ,
"label" : "children"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/clone_object" ,
"key" : "efun/objects/clone_object" ,
"label" : "clone_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/clonep" ,
"key" : "efun/objects/clonep" ,
"label" : "clonep"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/deep_inventory" ,
"key" : "efun/objects/deep_inventory" ,
"label" : "deep_inventory"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/destruct" ,
"key" : "efun/objects/destruct" ,
"label" : "destruct"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/environment" ,
"key" : "efun/objects/environment" ,
"label" : "environment"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/file_name" ,
"key" : "efun/objects/file_name" ,
"label" : "file_name"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/find_object" ,
"key" : "efun/objects/find_object" ,
"label" : "find_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/first_inventory" ,
"key" : "efun/objects/first_inventory" ,
"label" : "first_inventory"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/load_object" ,
"key" : "efun/objects/load_object" ,
"label" : "load_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/master" ,
"key" : "efun/objects/master" ,
"label" : "master"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/move_object" ,
"key" : "efun/objects/move_object" ,
"label" : "move_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/new" ,
"key" : "efun/objects/new" ,
"label" : "new"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/next_inventory" ,
"key" : "efun/objects/next_inventory" ,
"label" : "next_inventory"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/objectp" ,
"key" : "efun/objects/objectp" ,
"label" : "objectp"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/objects" ,
"key" : "efun/objects/objects" ,
"label" : "objects"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/present" ,
"key" : "efun/objects/present" ,
"label" : "present"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/query_heart_beat" ,
"key" : "efun/objects/query_heart_beat" ,
"label" : "query_heart_beat"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/query_notify_destruct" ,
"key" : "efun/objects/query_notify_destruct" ,
"label" : "query_notify_destruct"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/recompile_object" ,
"key" : "efun/objects/recompile_object" ,
"label" : "recompile_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/reload_object" ,
"key" : "efun/objects/reload_object" ,
"label" : "reload_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/restore_object" ,
"key" : "efun/objects/restore_object" ,
"label" : "restore_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/save_object" ,
"key" : "efun/objects/save_object" ,
"label" : "save_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/set_heart_beat" ,
"key" : "efun/objects/set_heart_beat" ,
"label" : "set_heart_beat"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/set_hide" ,
"key" : "efun/objects/set_hide" ,
"label" : "set_hide"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/set_notify_destruct" ,
"key" : "efun/objects/set_notify_destruct" ,
"label" : "set_notify_destruct"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/tell_object" ,
"key" : "efun/objects/tell_object" ,
"label" : "tell_object"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/tell_room" ,
"key" : "efun/objects/tell_room" ,
"label" : "tell_room"
} ,
{
"type" : "doc" ,
"id" : "efun/objects/virtualp" ,
"key" : "efun/objects/virtualp" ,
"label" : "virtualp"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/parsing" ,
"label" : "Command Parsing" ,
"link" : {
"type" : "generated-index" ,
"title" : "Command Parsing" ,
"slug" : "/efun/parsing/" ,
"description" : "The parse_command natural-language sentence, verb and rule parser."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/parsing/parse_add_rule" ,
"key" : "efun/parsing/parse_add_rule" ,
"label" : "parse_add_rule"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_add_synonym" ,
"key" : "efun/parsing/parse_add_synonym" ,
"label" : "parse_add_synonym"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_command" ,
"key" : "efun/parsing/parse_command" ,
"label" : "parse_command"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_dump" ,
"key" : "efun/parsing/parse_dump" ,
"label" : "parse_dump"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_init" ,
"key" : "efun/parsing/parse_init" ,
"label" : "parse_init"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_my_rules" ,
"key" : "efun/parsing/parse_my_rules" ,
"label" : "parse_my_rules"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_refresh" ,
"key" : "efun/parsing/parse_refresh" ,
"label" : "parse_refresh"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_remove" ,
"key" : "efun/parsing/parse_remove" ,
"label" : "parse_remove"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/parse_sentence" ,
"key" : "efun/parsing/parse_sentence" ,
"label" : "parse_sentence"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/process_string" ,
"key" : "efun/parsing/process_string" ,
"label" : "process_string"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/process_value" ,
"key" : "efun/parsing/process_value" ,
"label" : "process_value"
} ,
{
"type" : "doc" ,
"id" : "efun/parsing/query_verb" ,
"key" : "efun/parsing/query_verb" ,
"label" : "query_verb"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/pcre" ,
"label" : "Regular Expressions (PCRE)" ,
"link" : {
"type" : "generated-index" ,
"title" : "Regular Expressions (PCRE)" ,
"slug" : "/efun/pcre/" ,
"description" : "Perl-compatible regular expression matching, extraction and replacement."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_assoc" ,
"key" : "efun/pcre/pcre_assoc" ,
"label" : "pcre_assoc"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_cache" ,
"key" : "efun/pcre/pcre_cache" ,
"label" : "pcre_cache"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_extract" ,
"key" : "efun/pcre/pcre_extract" ,
"label" : "pcre_extract"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_match" ,
"key" : "efun/pcre/pcre_match" ,
"label" : "pcre_match"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_match_all" ,
"key" : "efun/pcre/pcre_match_all" ,
"label" : "pcre_match_all"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_replace" ,
"key" : "efun/pcre/pcre_replace" ,
"label" : "pcre_replace"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_replace_callback" ,
"key" : "efun/pcre/pcre_replace_callback" ,
"label" : "pcre_replace_callback"
} ,
{
"type" : "doc" ,
"id" : "efun/pcre/pcre_version" ,
"key" : "efun/pcre/pcre_version" ,
"label" : "pcre_version"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/sockets" ,
"label" : "Sockets" ,
"link" : {
"type" : "generated-index" ,
"title" : "Sockets" ,
"slug" : "/efun/sockets/" ,
"description" : "Low-level TCP/UDP socket networking from the sockets package."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/sockets/socket_accept" ,
"key" : "efun/sockets/socket_accept" ,
"label" : "socket_accept"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_acquire" ,
"key" : "efun/sockets/socket_acquire" ,
"label" : "socket_acquire"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_address" ,
"key" : "efun/sockets/socket_address" ,
"label" : "socket_address"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_bind" ,
"key" : "efun/sockets/socket_bind" ,
"label" : "socket_bind"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_close" ,
"key" : "efun/sockets/socket_close" ,
"label" : "socket_close"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_connect" ,
"key" : "efun/sockets/socket_connect" ,
"label" : "socket_connect"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_create" ,
"key" : "efun/sockets/socket_create" ,
"label" : "socket_create"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_error" ,
"key" : "efun/sockets/socket_error" ,
"label" : "socket_error"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_get_option" ,
"key" : "efun/sockets/socket_get_option" ,
"label" : "socket_get_option"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_listen" ,
"key" : "efun/sockets/socket_listen" ,
"label" : "socket_listen"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_release" ,
"key" : "efun/sockets/socket_release" ,
"label" : "socket_release"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_set_option" ,
"key" : "efun/sockets/socket_set_option" ,
"label" : "socket_set_option"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_status" ,
"key" : "efun/sockets/socket_status" ,
"label" : "socket_status"
} ,
{
"type" : "doc" ,
"id" : "efun/sockets/socket_write" ,
"key" : "efun/sockets/socket_write" ,
"label" : "socket_write"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/strings" ,
"label" : "Strings" ,
"link" : {
"type" : "generated-index" ,
"title" : "Strings" ,
"slug" : "/efun/strings/" ,
"description" : "Manipulate, format, search, trim, encode and hash strings, plus bit-string operations."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/strings/capitalize" ,
"key" : "efun/strings/capitalize" ,
"label" : "capitalize"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/clear_bit" ,
"key" : "efun/strings/clear_bit" ,
"label" : "clear_bit"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/crypt" ,
"key" : "efun/strings/crypt" ,
"label" : "crypt"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/explode" ,
"key" : "efun/strings/explode" ,
"label" : "explode"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/hash" ,
"key" : "efun/strings/hash" ,
"label" : "hash"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/implode" ,
"key" : "efun/strings/implode" ,
"label" : "implode"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/lower_case" ,
"key" : "efun/strings/lower_case" ,
"label" : "lower_case"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/ltrim" ,
"key" : "efun/strings/ltrim" ,
"label" : "ltrim"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/oldcrypt" ,
"key" : "efun/strings/oldcrypt" ,
"label" : "oldcrypt"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/reg_assoc" ,
"key" : "efun/strings/reg_assoc" ,
"label" : "reg_assoc"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/regexp" ,
"key" : "efun/strings/regexp" ,
"label" : "regexp"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/replace_string" ,
"key" : "efun/strings/replace_string" ,
"label" : "replace_string"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/rtrim" ,
"key" : "efun/strings/rtrim" ,
"label" : "rtrim"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/set_bit" ,
"key" : "efun/strings/set_bit" ,
"label" : "set_bit"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/sha1" ,
"key" : "efun/strings/sha1" ,
"label" : "sha1"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/sprintf" ,
"key" : "efun/strings/sprintf" ,
"label" : "sprintf"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/sscanf" ,
"key" : "efun/strings/sscanf" ,
"label" : "sscanf"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/strcmp" ,
"key" : "efun/strings/strcmp" ,
"label" : "strcmp"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/string_decode" ,
"key" : "efun/strings/string_decode" ,
"label" : "string_decode"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/string_encode" ,
"key" : "efun/strings/string_encode" ,
"label" : "string_encode"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/stringp" ,
"key" : "efun/strings/stringp" ,
"label" : "stringp"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/strlen" ,
"key" : "efun/strings/strlen" ,
"label" : "strlen"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/strsrch" ,
"key" : "efun/strings/strsrch" ,
"label" : "strsrch"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/strwidth" ,
"key" : "efun/strings/strwidth" ,
"label" : "strwidth"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/test_bit" ,
"key" : "efun/strings/test_bit" ,
"label" : "test_bit"
} ,
{
"type" : "doc" ,
"id" : "efun/strings/trim" ,
"key" : "efun/strings/trim" ,
"label" : "trim"
}
]
} ,
{
"type" : "category" ,
"key" : "efun/system" ,
"label" : "System" ,
"link" : {
"type" : "generated-index" ,
"title" : "System" ,
"slug" : "/efun/system/" ,
"description" : "Driver runtime services: time, eval-cost limits, inheritance introspection, call-out info and shutdown."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "efun/system/all_previous_objects" ,
"key" : "efun/system/all_previous_objects" ,
"label" : "all_previous_objects"
} ,
{
"type" : "doc" ,
"id" : "efun/system/call_out_info" ,
"key" : "efun/system/call_out_info" ,
"label" : "call_out_info"
} ,
{
"type" : "doc" ,
"id" : "efun/system/ctime" ,
"key" : "efun/system/ctime" ,
"label" : "ctime"
} ,
{
"type" : "doc" ,
"id" : "efun/system/deep_inherit_list" ,
"key" : "efun/system/deep_inherit_list" ,
"label" : "deep_inherit_list"
} ,
{
"type" : "doc" ,
"id" : "efun/system/error" ,
"key" : "efun/system/error" ,
"label" : "error"
} ,
{
"type" : "doc" ,
"id" : "efun/system/eval_cost" ,
"key" : "efun/system/eval_cost" ,
"label" : "eval_cost"
} ,
{
"type" : "doc" ,
"id" : "efun/system/find_call_out" ,
"key" : "efun/system/find_call_out" ,
"label" : "find_call_out"
} ,
{
"type" : "doc" ,
"id" : "efun/system/flush_messages" ,
"key" : "efun/system/flush_messages" ,
"label" : "flush_messages"
} ,
{
"type" : "doc" ,
"id" : "efun/system/function_exists" ,
"key" : "efun/system/function_exists" ,
"label" : "function_exists"
} ,
{
"type" : "doc" ,
"id" : "efun/system/function_profile" ,
"key" : "efun/system/function_profile" ,
"label" : "function_profile"
} ,
{
"type" : "doc" ,
"id" : "efun/system/inherit_list" ,
"key" : "efun/system/inherit_list" ,
"label" : "inherit_list"
} ,
{
"type" : "doc" ,
"id" : "efun/system/inherits" ,
"key" : "efun/system/inherits" ,
"label" : "inherits"
} ,
{
"type" : "doc" ,
"id" : "efun/system/localtime" ,
"key" : "efun/system/localtime" ,
"label" : "localtime"
} ,
{
"type" : "doc" ,
"id" : "efun/system/max_eval_cost" ,
"key" : "efun/system/max_eval_cost" ,
"label" : "max_eval_cost"
} ,
{
"type" : "doc" ,
"id" : "efun/system/perf_counter_ns" ,
"key" : "efun/system/perf_counter_ns" ,
"label" : "perf_counter_ns"
} ,
{
"type" : "doc" ,
"id" : "efun/system/reclaim_objects" ,
"key" : "efun/system/reclaim_objects" ,
"label" : "reclaim_objects"
} ,
{
"type" : "doc" ,
"id" : "efun/system/replace_program" ,
"key" : "efun/system/replace_program" ,
"label" : "replace_program"
} ,
{
"type" : "doc" ,
"id" : "efun/system/request_clean_up" ,
"key" : "efun/system/request_clean_up" ,
"label" : "request_clean_up"
} ,
{
"type" : "doc" ,
"id" : "efun/system/reset_eval_cost" ,
"key" : "efun/system/reset_eval_cost" ,
"label" : "reset_eval_cost"
} ,
{
"type" : "doc" ,
"id" : "efun/system/set_clean_up" ,
"key" : "efun/system/set_clean_up" ,
"label" : "set_clean_up"
} ,
{
"type" : "doc" ,
"id" : "efun/system/set_eval_limit" ,
"key" : "efun/system/set_eval_limit" ,
"label" : "set_eval_limit"
} ,
{
"type" : "doc" ,
"id" : "efun/system/set_reset" ,
"key" : "efun/system/set_reset" ,
"label" : "set_reset"
} ,
{
"type" : "doc" ,
"id" : "efun/system/shallow_inherit_list" ,
"key" : "efun/system/shallow_inherit_list" ,
"label" : "shallow_inherit_list"
} ,
{
"type" : "doc" ,
"id" : "efun/system/shutdown" ,
"key" : "efun/system/shutdown" ,
"label" : "shutdown"
} ,
{
"type" : "doc" ,
"id" : "efun/system/strftime" ,
"key" : "efun/system/strftime" ,
"label" : "strftime"
} ,
{
"type" : "doc" ,
"id" : "efun/system/strptime" ,
"key" : "efun/system/strptime" ,
"label" : "strptime"
} ,
{
"type" : "doc" ,
"id" : "efun/system/sys_network_ports" ,
"key" : "efun/system/sys_network_ports" ,
"label" : "sys_network_ports"
} ,
{
"type" : "doc" ,
"id" : "efun/system/sys_reload_tls" ,
"key" : "efun/system/sys_reload_tls" ,
"label" : "sys_reload_tls"
} ,
{
"type" : "doc" ,
"id" : "efun/system/time" ,
"key" : "efun/system/time" ,
"label" : "time"
} ,
{
"type" : "doc" ,
"id" : "efun/system/time_ns" ,
"key" : "efun/system/time_ns" ,
"label" : "time_ns"
} ,
{
"type" : "doc" ,
"id" : "efun/system/trace_end" ,
"key" : "efun/system/trace_end" ,
"label" : "trace_end"
} ,
{
"type" : "doc" ,
"id" : "efun/system/trace_start" ,
"key" : "efun/system/trace_start" ,
"label" : "trace_start"
} ,
{
"type" : "doc" ,
"id" : "efun/system/uptime" ,
"key" : "efun/system/uptime" ,
"label" : "uptime"
}
]
}
]
} ,
"apply" : {
"type" : "category" ,
"key" : "apply" ,
"label" : "Applies" ,
"link" : {
"type" : "generated-index" ,
"title" : "Applies" ,
"slug" : "/apply/" ,
"description" : "Driver-to-LPC callbacks (applies) that the FluffOS driver invokes on your objects, grouped by the interactive, master, and object roles they serve."
} ,
"items" : [
{
"type" : "category" ,
"key" : "apply/object" ,
"label" : "Object Applies" ,
"link" : {
"type" : "generated-index" ,
"title" : "Object Applies" ,
"slug" : "/apply/object/" ,
"description" : "Lifecycle applies the driver calls on ordinary objects, such as create, reset, init, heart_beat, and clean_up."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "apply/object/__INIT" ,
"key" : "apply/object/__INIT" ,
"label" : "__INIT"
} ,
{
"type" : "doc" ,
"id" : "apply/object/clean_up" ,
"key" : "apply/object/clean_up" ,
"label" : "clean_up"
} ,
{
"type" : "doc" ,
"id" : "apply/object/create" ,
"key" : "apply/object/create" ,
"label" : "create"
} ,
{
"type" : "doc" ,
"id" : "apply/object/heart_beat" ,
"key" : "apply/object/heart_beat" ,
"label" : "heart_beat"
} ,
{
"type" : "doc" ,
"id" : "apply/object/id" ,
"key" : "apply/object/id" ,
"label" : "id"
} ,
{
"type" : "doc" ,
"id" : "apply/object/init" ,
"key" : "apply/object/init" ,
"label" : "init"
} ,
{
"type" : "doc" ,
"id" : "apply/object/is_living" ,
"key" : "apply/object/is_living" ,
"label" : "is_living"
} ,
{
"type" : "doc" ,
"id" : "apply/object/move_or_destruct" ,
"key" : "apply/object/move_or_destruct" ,
"label" : "move_or_destruct"
} ,
{
"type" : "doc" ,
"id" : "apply/object/on_destruct" ,
"key" : "apply/object/on_destruct" ,
"label" : "on_destruct"
} ,
{
"type" : "doc" ,
"id" : "apply/object/reset" ,
"key" : "apply/object/reset" ,
"label" : "reset"
} ,
{
"type" : "doc" ,
"id" : "apply/object/virtual_start" ,
"key" : "apply/object/virtual_start" ,
"label" : "virtual_start"
}
]
} ,
{
"type" : "category" ,
"key" : "apply/master" ,
"label" : "Master Applies" ,
"link" : {
"type" : "generated-index" ,
"title" : "Master Applies" ,
"slug" : "/apply/master/" ,
"description" : "Applies the driver calls on the master object to control security, permissions, UIDs, object loading, error handling, and mud-wide policy."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "apply/master/author_file" ,
"key" : "apply/master/author_file" ,
"label" : "author_file"
} ,
{
"type" : "doc" ,
"id" : "apply/master/compile_object" ,
"key" : "apply/master/compile_object" ,
"label" : "compile_object"
} ,
{
"type" : "doc" ,
"id" : "apply/master/connect" ,
"key" : "apply/master/connect" ,
"label" : "connect"
} ,
{
"type" : "doc" ,
"id" : "apply/master/crash" ,
"key" : "apply/master/crash" ,
"label" : "crash"
} ,
{
"type" : "doc" ,
"id" : "apply/master/creator_file" ,
"key" : "apply/master/creator_file" ,
"label" : "creator_file"
} ,
{
"type" : "doc" ,
"id" : "apply/master/domain_file" ,
"key" : "apply/master/domain_file" ,
"label" : "domain_file"
} ,
{
"type" : "doc" ,
"id" : "apply/master/epilog" ,
"key" : "apply/master/epilog" ,
"label" : "epilog"
} ,
{
"type" : "doc" ,
"id" : "apply/master/error_handler" ,
"key" : "apply/master/error_handler" ,
"label" : "error_handler"
} ,
{
"type" : "doc" ,
"id" : "apply/master/flag" ,
"key" : "apply/master/flag" ,
"label" : "flag"
} ,
{
"type" : "doc" ,
"id" : "apply/master/get_bb_uid" ,
"key" : "apply/master/get_bb_uid" ,
"label" : "get_bb_uid"
} ,
{
"type" : "doc" ,
"id" : "apply/master/get_include_path" ,
"key" : "apply/master/get_include_path" ,
"label" : "get_include_path"
} ,
{
"type" : "doc" ,
"id" : "apply/master/get_mud_stats" ,
"key" : "apply/master/get_mud_stats" ,
"label" : "get_mud_stats"
} ,
{
"type" : "doc" ,
"id" : "apply/master/get_root_uid" ,
"key" : "apply/master/get_root_uid" ,
"label" : "get_root_uid"
} ,
{
"type" : "doc" ,
"id" : "apply/master/get_save_file_name" ,
"key" : "apply/master/get_save_file_name" ,
"label" : "get_save_file_name"
} ,
{
"type" : "doc" ,
"id" : "apply/master/include_file" ,
"key" : "apply/master/include_file" ,
"label" : "include_file"
} ,
{
"type" : "doc" ,
"id" : "apply/master/inherit_program" ,
"key" : "apply/master/inherit_program" ,
"label" : "inherit_program"
} ,
{
"type" : "doc" ,
"id" : "apply/master/log_error" ,
"key" : "apply/master/log_error" ,
"label" : "log_error"
} ,
{
"type" : "doc" ,
"id" : "apply/master/make_path_absolute" ,
"key" : "apply/master/make_path_absolute" ,
"label" : "make_path_absolute"
} ,
{
"type" : "doc" ,
"id" : "apply/master/object_name" ,
"key" : "apply/master/object_name" ,
"label" : "object_name"
} ,
{
"type" : "doc" ,
"id" : "apply/master/parser_error_message" ,
"key" : "apply/master/parser_error_message" ,
"label" : "parser_error_message"
} ,
{
"type" : "doc" ,
"id" : "apply/master/preload" ,
"key" : "apply/master/preload" ,
"label" : "preload"
} ,
{
"type" : "doc" ,
"id" : "apply/master/privs_file" ,
"key" : "apply/master/privs_file" ,
"label" : "privs_file"
} ,
{
"type" : "doc" ,
"id" : "apply/master/retrieve_ed_setup" ,
"key" : "apply/master/retrieve_ed_setup" ,
"label" : "retrieve_ed_setup"
} ,
{
"type" : "doc" ,
"id" : "apply/master/save_ed_setup" ,
"key" : "apply/master/save_ed_setup" ,
"label" : "save_ed_setup"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_bind" ,
"key" : "apply/master/valid_bind" ,
"label" : "valid_bind"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_database" ,
"key" : "apply/master/valid_database" ,
"label" : "valid_database"
} ,
docs: validate "See Also" references, drop dead ones, document valid_ffi (#1251)
* empty
* docs: validate "See Also" references, drop dead ones, document valid_ffi
Reviewed the "See Also" section of every doc and dropped man-page
cross-references whose target page does not exist (verified against the
driver source, not just the docs tree):
- errorp, each, opcprof, dump_socket_status, extract, shadowp,
destruct_env_of, move, inventory_visible, inventory_accessible
These name efuns/applies that no longer exist in FluffOS. keys/values now
point at the `for` construct in place of the defunct `each` efun.
valid_ffi was the one "referenced but undocumented" case: it is a real
master apply (APPLY_VALID_FFI) gating every ffi_load/symbol/prepare/
callback, so it gets a proper apply page rather than having its references
removed. Sidebar regenerated to include it.
The 13 modern markdown-link "See Also" sections were already clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 01:09:17 -04:00
{
"type" : "doc" ,
"id" : "apply/master/valid_ffi" ,
"key" : "apply/master/valid_ffi" ,
"label" : "valid_ffi"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "apply/master/valid_hide" ,
"key" : "apply/master/valid_hide" ,
"label" : "valid_hide"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_link" ,
"key" : "apply/master/valid_link" ,
"label" : "valid_link"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_object" ,
"key" : "apply/master/valid_object" ,
"label" : "valid_object"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_override" ,
"key" : "apply/master/valid_override" ,
"label" : "valid_override"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_read" ,
"key" : "apply/master/valid_read" ,
"label" : "valid_read"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_save_binary" ,
"key" : "apply/master/valid_save_binary" ,
"label" : "valid_save_binary"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_seteuid" ,
"key" : "apply/master/valid_seteuid" ,
"label" : "valid_seteuid"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_shadow" ,
"key" : "apply/master/valid_shadow" ,
"label" : "valid_shadow"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_socket" ,
"key" : "apply/master/valid_socket" ,
"label" : "valid_socket"
} ,
{
"type" : "doc" ,
"id" : "apply/master/valid_write" ,
"key" : "apply/master/valid_write" ,
"label" : "valid_write"
}
]
} ,
{
"type" : "category" ,
"key" : "apply/interactive" ,
"label" : "Interactive Applies" ,
"link" : {
"type" : "generated-index" ,
"title" : "Interactive Applies" ,
"slug" : "/apply/interactive/" ,
"description" : "Applies the driver calls on interactive (player-connected) objects to handle input, output, prompts, and telnet/GMCP/MSDP/MXP protocol negotiation."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "apply/interactive/catch_tell" ,
"key" : "apply/interactive/catch_tell" ,
"label" : "catch_tell"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/gmcp" ,
"key" : "apply/interactive/gmcp" ,
"label" : "gmcp"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/gmcp_enable" ,
"key" : "apply/interactive/gmcp_enable" ,
"label" : "gmcp_enable"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/logon" ,
"key" : "apply/interactive/logon" ,
"label" : "logon"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/msdp" ,
"key" : "apply/interactive/msdp" ,
"label" : "msdp"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/msdp_enable" ,
"key" : "apply/interactive/msdp_enable" ,
"label" : "msdp_enable"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/msp_enable" ,
"key" : "apply/interactive/msp_enable" ,
"label" : "msp_enable"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/mxp_enable" ,
"key" : "apply/interactive/mxp_enable" ,
"label" : "mxp_enable"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/mxp_tag" ,
"key" : "apply/interactive/mxp_tag" ,
"label" : "mxp_tag"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/net_dead" ,
"key" : "apply/interactive/net_dead" ,
"label" : "net_dead"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/process_input" ,
"key" : "apply/interactive/process_input" ,
"label" : "process_input"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/receive_ed" ,
"key" : "apply/interactive/receive_ed" ,
"label" : "receive_ed"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/receive_environ" ,
"key" : "apply/interactive/receive_environ" ,
"label" : "receive_environ"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/receive_message" ,
"key" : "apply/interactive/receive_message" ,
"label" : "receive_message"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/receive_snoop" ,
"key" : "apply/interactive/receive_snoop" ,
"label" : "receive_snoop"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/telnet_suboption" ,
"key" : "apply/interactive/telnet_suboption" ,
"label" : "telnet_suboption"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/terminal_colour_replace" ,
"key" : "apply/interactive/terminal_colour_replace" ,
"label" : "terminal_colour_replace"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/terminal_type" ,
"key" : "apply/interactive/terminal_type" ,
"label" : "terminal_type"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/window_size" ,
"key" : "apply/interactive/window_size" ,
"label" : "window_size"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/write_prompt" ,
"key" : "apply/interactive/write_prompt" ,
"label" : "write_prompt"
} ,
{
"type" : "doc" ,
"id" : "apply/interactive/zmp" ,
"key" : "apply/interactive/zmp" ,
"label" : "zmp"
}
]
}
]
} ,
"stdlib" : {
"type" : "category" ,
"key" : "stdlib" ,
"label" : "Standard Library" ,
"link" : {
"type" : "generated-index" ,
"title" : "Standard Library" ,
"slug" : "/stdlib/" ,
"description" : "Pure-LPC helper functions and libraries shipped with the FluffOS testsuite mudlib (not driver efuns), organized by data type and domain."
} ,
"items" : [
{
"type" : "category" ,
"key" : "stdlib/arrays" ,
"label" : "Arrays" ,
"link" : {
"type" : "generated-index" ,
"title" : "Arrays" ,
"slug" : "/stdlib/arrays/" ,
"description" : "LPC helpers for transforming and aggregating arrays, such as reduce."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/arrays/reduce" ,
"key" : "stdlib/arrays/reduce" ,
"label" : "reduce"
}
]
} ,
{
"type" : "category" ,
"key" : "stdlib/db" ,
"label" : "Database" ,
"link" : {
"type" : "generated-index" ,
"title" : "Database" ,
"slug" : "/stdlib/db/" ,
"description" : "A fluent LPC query builder for working with FluffOS-supported databases across SQLite, MySQL, and other backends."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/db/database" ,
"key" : "stdlib/db/database" ,
"label" : "database"
}
]
} ,
{
"type" : "category" ,
"key" : "stdlib/mappings" ,
"label" : "Mappings" ,
"link" : {
"type" : "generated-index" ,
"title" : "Mappings" ,
"slug" : "/stdlib/mappings/" ,
"description" : "LPC helpers for working with mappings, such as weighted random selection of keys."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/mappings/element_of_weighted" ,
"key" : "stdlib/mappings/element_of_weighted" ,
"label" : "element_of_weighted"
}
]
} ,
{
"type" : "category" ,
"key" : "stdlib/numbers" ,
"label" : "Numbers" ,
"link" : {
"type" : "generated-index" ,
"title" : "Numbers" ,
"slug" : "/stdlib/numbers/" ,
"description" : "LPC helpers for numeric aggregation and percentage math over ints and floats."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/numbers/array_sum" ,
"key" : "stdlib/numbers/array_sum" ,
"label" : "array_sum"
} ,
{
"type" : "doc" ,
"id" : "stdlib/numbers/percent" ,
"key" : "stdlib/numbers/percent" ,
"label" : "percent"
} ,
{
"type" : "doc" ,
"id" : "stdlib/numbers/percent_of" ,
"key" : "stdlib/numbers/percent_of" ,
"label" : "percent_of"
} ,
{
"type" : "doc" ,
"id" : "stdlib/numbers/sum" ,
"key" : "stdlib/numbers/sum" ,
"label" : "sum"
}
]
} ,
{
"type" : "category" ,
"key" : "stdlib/objects" ,
"label" : "Objects" ,
"link" : {
"type" : "generated-index" ,
"title" : "Objects" ,
"slug" : "/stdlib/objects/" ,
"description" : "LPC helpers for locating and inspecting objects in inventories and environments."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/objects/all_environment" ,
"key" : "stdlib/objects/all_environment" ,
"label" : "all_environment"
} ,
{
"type" : "doc" ,
"id" : "stdlib/objects/present_clone" ,
"key" : "stdlib/objects/present_clone" ,
"label" : "present_clone"
}
]
} ,
{
"type" : "category" ,
"key" : "stdlib/string" ,
"label" : "Strings" ,
"link" : {
"type" : "generated-index" ,
"title" : "Strings" ,
"slug" : "/stdlib/string/" ,
"description" : "LPC helpers for string manipulation, including base64 encoding, word wrapping, and bitmap-font rendering."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "stdlib/string/base64decode" ,
"key" : "stdlib/string/base64decode" ,
"label" : "base64decode"
} ,
{
"type" : "doc" ,
"id" : "stdlib/string/base64encode" ,
"key" : "stdlib/string/base64encode" ,
"label" : "base64encode"
} ,
{
"type" : "doc" ,
"id" : "stdlib/string/bitmap_font" ,
"key" : "stdlib/string/bitmap_font" ,
"label" : "bitmap_font"
} ,
{
"type" : "doc" ,
"id" : "stdlib/string/break_string" ,
"key" : "stdlib/string/break_string" ,
"label" : "break_string"
}
]
} ,
docs: rewrite FFI reference around buffer features, add worked libc examples (#1275)
* docs: rewrite FFI reference around buffer features, add worked libc examples
Update docs/driver/ffi-plan.md now that buffers carry the full byte
toolkit (string/int-array promotion, byte lvalues, range assignment,
foreach, concatenation):
- New "The buffer type does the heavy lifting" section mapping each
buffer language feature to its FFI use.
- The C-string idiom is now the two-line promotion form
(buffer b = s; b += ({ 0 })) instead of chained string_encode()
calls; string_encode stays documented for non-UTF-8 encodings, and
the doc notes that promotion never reaches inside ffi_call's args
array (the byte boundary stays explicit).
- New "Worked examples -- calling libc" section: scalars (sqrt/pow/abs),
strings in (strlen, incl. range assignment into an allocation),
strings out (getenv + ffi_peek(addr, -1) + string_decode with a NULL
check), out-parameters (frexp), structs (time/localtime via
ffi_struct_layout), callbacks (qsort with an LPC comparator), and
foreach over peeked bytes.
- Sync the reference with the implementation: add ffi_address() (was
missing from the efun listing), correct ffi_free() (zeroes bytes; GC
reclaims), ffi_peek(-1) (NUL-terminated read capped at the max
buffer size config), FFI_POINTER args accepting int addresses
(0 = NULL), pointer returns always being int addresses, the
FFI_INT/FFI_LONG aliases, ffi_load("") semantics, the valid_ffi
operation names with a sample master implementation, and the real
build default (PACKAGE_FFI ships ON; runtime denies without a
valid_ffi apply; WASM forces it off).
Every snippet is pinned verbatim by a new testsuite file,
testsuite/single/tests/efuns/ffi_doc_examples.lpc (guarded by
__PACKAGE_FFI__ and the ffi_probe availability fixture like the other
ffi tests). Verified: RelWithDebInfo build, ffi glob (21 files) and the
full LPC suite (600 files) pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* docs: add a local build quickstart to AGENTS.md
Record the verified Debian/Ubuntu setup so future agents don't
rediscover it one configure failure at a time: the full apt package
list (CI's packages: lines assume a GitHub runner image that
preinstalls cmake/ninja/libicu-dev), the configure/build commands, the
build/src/driver binary path, -DPACKAGE_DB=OFF as the escape hatch
when no MySQL client dev package is available, that libevent is
vendored and GTest optional, and how to run the LPC suite without a
pipe masking the driver's exit status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* docs: rename driver/ffi-plan.md to driver/ffi.md
The page stopped being a plan and is the package reference; name it
accordingly (site URL /driver/ffi/, sidebar label "FFI Package").
Update every reference: source comments (ffi.spec, ffi.cc, both
include/ffi.h copies), tools/ffi (README, generate.py's module
docstring and the two comment lines it emits into generated bindings,
with the committed ffi_genmath.lpc fixture updated to match), the
testsuite doc-examples pin, sidebar_meta.json + regenerated
sidebars.generated.json, and the zh-CN sidebar-label key.
Note: the old /driver/ffi-plan/ URL is not redirected (the site has no
client-redirects plugin configured).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* ffi: gate ffi_peek through valid_ffi("peek")
ffi_peek(address, nbytes) copies bytes from an arbitrary native
address, which makes it a process-memory disclosure primitive on its
own (and a driver crash on an unmapped address) -- yet it was the one
dangerous efun in the package not gated by valid_ffi, so any object
could call it whenever PACKAGE_FFI was compiled in, even under a
deny-all master. The other ungated efuns operate only on LPC-owned
buffers and handles (ffi_address reveals a buffer's own address, inert
without a peek/call grant) and stay ungated.
f_ffi_peek now calls check_valid_ffi("peek", address) before touching
any memory, exactly like load/symbol/prepare/callback. The testsuite
master denies a -0xDEAD sentinel address so the denial path is
testable without dereferencing anything; ffi_peek.lpc pins the exact
error. Docs updated: valid_ffi(4), ffi_peek(3), and the op list +
sample master in docs/driver/ffi.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* stdlib: add /std/ffi_util, FFI-boundary helpers for package_ffi callers
A pure-LPC library packaging the recurring idioms at the C boundary so
binding code stops re-deriving them:
- cstr(s) / cstr_enc(s, enc): NUL-terminated C strings (buffer
promotion for UTF-8, string_encode for other byte encodings)
- c_string(addr) / c_string_enc(addr, enc): read a returned char*,
with the NULL -> 0 convention folded in
- c_out(type_code): a zeroed out-parameter block for T*
- c_argv(strings): a NULL-terminated char*[] plus the kept-alive
per-string buffers (the lifetime footgun a library should own)
- c_field / c_field_set: struct-field access over ffi_struct_layout()
layouts
Deliberately consumed via `inherit "/std/ffi_util"` rather than the
simul_efun object: c_string() calls ffi_peek(), gated by
valid_ffi("peek", addr, caller), and inheriting keeps the consuming
object as the security principal the master sees. The docs page
(docs/stdlib/ffi_util, "FFI Utilities" in the sidebar) spells this out.
Tests: testsuite/single/tests/std/ffi_util.lpc exercises every helper
against libc (strlen with both encodings, getenv through c_string,
frexp through c_out, strtol's char **endptr through
c_out(FFI_POINTER), and structural checks on c_argv's pointer array).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-15 22:09:25 -07:00
{
"type" : "doc" ,
"id" : "stdlib/ffi_util" ,
"key" : "stdlib/ffi_util" ,
"label" : "FFI Utilities"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "stdlib/tui" ,
"key" : "stdlib/tui" ,
"label" : "TUI Toolkit"
}
]
} ,
"concepts" : {
"type" : "category" ,
"key" : "concepts" ,
"label" : "Concepts" ,
"link" : {
"type" : "generated-index" ,
"title" : "Concepts" ,
"slug" : "/concepts/" ,
"description" : "Conceptual guides explaining how LPC, objects, and the FluffOS driver work together, for readers learning the platform rather than a specific function."
} ,
"items" : [
{
"type" : "category" ,
"key" : "concepts/general" ,
"label" : "General Concepts" ,
"link" : {
"type" : "generated-index" ,
"title" : "General Concepts" ,
"slug" : "/concepts/general/" ,
"description" : "Foundational explanations of the LPC language, object model, the FluffOS driver, and mud-wide mechanisms like simul_efuns, hot reload, and networking."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "concepts/general/lpc" ,
"key" : "concepts/general/lpc" ,
"label" : "The LPC Language"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/objects" ,
"key" : "concepts/general/objects" ,
"label" : "Objects"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/oop" ,
"key" : "concepts/general/oop" ,
"label" : "Object-Oriented Programming"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/preprocessor" ,
"key" : "concepts/general/preprocessor" ,
"label" : "Preprocessor"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/global_include_file" ,
"key" : "concepts/general/global_include_file" ,
"label" : "Global Include File"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/fluffos_driver" ,
"key" : "concepts/general/fluffos_driver" ,
"label" : "The FluffOS Driver"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/simul_efun" ,
"key" : "concepts/general/simul_efun" ,
"label" : "Simulated Efuns"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/message_doc" ,
"key" : "concepts/general/message_doc" ,
"label" : "The message() System"
} ,
reference loops: docs, runtime cycle efuns, orphan collector, copy() unwind fix (#1276)
A complete treatment of reference loops (cyclic data structures) in LPC:
the reference-counting VM has no cycle collector, so a value that reaches
itself leaks permanently -- and silently -- once the last outside
reference is dropped, and it cannot be saved, deep-copied, or fully
printed in the meantime. This change documents the problem, fixes a
driver memory-safety bug it exposes, and adds runtime tooling to detect,
locate, break, and (on debug builds) collect such loops.
Driver fixes:
- copy() on a cyclic structure always hits the MAX_SAVE_SVALUE_DEPTH
error(), and that unwind path leaked every partially-built container --
allocated with the _empty_ (uninitialized-svalue) allocators and holding
a borrowed, un-ref-counted pointer to the ORIGINAL value, which the
Debug memory checker then read after free (heap-use-after-free abort
under ASan, reproducible with just catch(copy(a)) on a[0] = a).
deep_copy_* now allocate zero-filled, hold the destination in a
unique_ptr whose deleter is the tag-symmetric free_*, and only write the
destination slot after the child copy fully succeeded (ffi.cc precedent,
AGENTS.md section 4).
- save_object/save_variable and copy()'s 'nested too deep' errors -- the
classic symptom of a loop -- now say so (the has_cycle() pointer is
gated on PACKAGE_CONTRIB so core never recommends an efun the build
lacks).
New efuns (contrib package, src/packages/contrib/cycles.cc):
- has_cycle(mixed): 1 if the value's reference graph contains a loop.
- find_cycles(mixed): one index-path string per loop-closing slot
("[3][\"peer\"].1" style).
- break_cycles(mixed): clears every loop in place and returns the number
of edges broken. Exactly the DFS back-edges are touched (a digraph is
acyclic iff its DFS has no back-edges): item/value slots are zeroed, a
loop closed in mapping-KEY position has its node deleted (hashed keys
cannot be overwritten), and a loop closing on the funptr->args edge
itself -- possible because bind() SHARES the args array between the old
and new funptr -- detaches the bound funptr's args list and replaces it
with a zero-filled one of the same size. DAG sharing is never touched;
one cut un-loops a whole ring; afterwards the value saves, copies,
prints, and frees normally.
All three share one ITERATIVE walk (explicit heap stack, white/grey/
black coloring): no C-stack recursion, no depth cap -- arbitrarily deep
acyclic values scan cleanly where save_variable() errors. Edges:
array/class items, mapping keys AND values, fp->hdr.args; objects are
deliberately leaves (loops through object variables are the
destruct()-managed kind: destruct2() zeroes the variable block).
break_cycles() records fixes during a mutation-free walk and applies
them in a post-pass that holds a reference on every touched container,
zeroes slots before deleting nodes (only node deletion can cascade
frees), and releases the holds last -- order-independent and safe
against shared/overlapping fixes.
Orphaned-loop collector (develop package, Debug/DEBUGMALLOC_EXTENSIONS):
- find_orphaned_cycles(int collect): finds -- and with any nonzero
argument reclaims -- data blocks that are unreachable because only a
reference loop keeps them alive: the case nothing LPC-level can reach
anymore. Detection is trial deletion (CPython-gc-style), implemented in
md_scan_orphaned_cycles (checkmemory.cc): count each array/class/
mapping/funptr's references held by OTHER data blocks; a block whose
real ref count exceeds that is externally held (object variables, VM
stack, call_out, any C++-side holder) and seeds liveness, which
propagates along data edges; the remainder is loop garbage. No root
enumeration to get wrong -- every legitimate holder shows up as an
external ref. Collection: hold a ref on every dead block, sever all
their child slots (releasing strings/objects/buffers/live values
normally), then release the holds -- each dead block deallocates with
nothing left to cascade into.
- check_all_blocks() runs the same scan (skippable via new flag bit 2,
value 4) and reports 'unreachable data block(s) kept alive only by
reference loop(s)', so the testsuite's per-file check_memory() gate
turns a dropped cycle into a hard, attributed failure. That immediately
caught a real pre-existing leak: tests/std/json.lpc's
test_encode_circular_references() dropped all four of its
deliberately-cyclic fixtures on every suite run since it was written.
Tests (testsuite/single/tests/):
- operators/reference_loop.lpc pins the driver contract around loops and
crashes the unfixed Debug/ASan driver (the copy() unwind UAF).
- efuns/has_cycle.lpc, find_cycles.lpc, break_cycles.lpc cover self/
mutual/ring loops across arrays, mappings (value and key position),
classes, funptr args (including the bind()-shared-args case, which was
unbreakable in an earlier revision of this change), DAG-sharing
preservation, save/copy working again after a break, idempotency, and a
5000-deep acyclic walk.
- efuns/find_orphaned_cycles.lpc pins baseline-relative detection of 6
orphans across three dropped loop shapes, idempotent detection, that
reachable loops are never classified as garbage, and that collection
reclaims everything while reachable data survives.
- Every cycle-building test has UNCONDITIONAL teardown (body in catch(),
find_orphaned_cycles(1) regardless, error re-raised) so a mid-test
regression stays one [ FAILED ] entry instead of cascading the
harness's LEAK gate into a suite-wide abort (AGENTS.md section 7).
Docs (Docusaurus, sidebar regenerated; full two-locale build verified):
- new concepts page docs/concepts/general/reference_loops.md: why loops
leak, what each recursive consumer does, the destruct() exception,
prevention patterns, the runtime tools, and the debug-build collector;
- efun pages for all four new efuns; check_memory.md documents the new
scan and flag bit.
Validated on Debug+ASan/UBSan (full LPC suite, randomized order, multiple
runs) and RelWithDebInfo (full suite), 313 GTest unit tests, plus an
8-angle adversarially-verified self-review.
Round-2 self-review (4 fresh angles, adversarially verified) additionally:
- break_cycles() post-pass releases its held container references via an
RAII guard: allocate_array() there can error() (set_config() can shrink
__MAX_ARRAY_SIZE__ at runtime below a shared args array's size), and the
old trailing release loop would have leaked every held ref on that
unwind (AGENTS.md section 4).
- documented the pre-existing map_delete()-class caveat: deleting a
key-closed loop's node while an outer unlocked foreach-ref variable is
aimed at it dangles that variable (not specific to this efun; noted in
code and doc).
- extended orphan-collector coverage from 6 to 10 blocks: class rings
(TAG_CLASS candidate/sever/free_class paths), mapping pairs closed in
KEY position (the collector's in-place key-zeroing sever path), and a
buffer payload riding an orphaned ring (sever must release it or the
Debug ref gate trips); added a destructed-object-in-walked-value test
(render_key + leaf handling).
- docs: refs.md and copy.md now link back to the cycle tooling; zh-CN
sidebar translation keys rescaffolded; AGENTS.md section 7 documents the
new hard gate and the catch + find_orphaned_cycles(1) teardown pattern.
- re-entrancy audit (foreach/MAP_LOCKED/locked_map_nodes/merge_arg_lists)
and LPC-test-semantics audit returned no code defects.
Claude-Session: https://claude.ai/code/session_01VaksxbPjc3hjzghoQPUHRo
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-16 00:06:37 -07:00
{
"type" : "doc" ,
"id" : "concepts/general/reference_loops" ,
"key" : "concepts/general/reference_loops" ,
"label" : "Reference Loops"
} ,
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
{
"type" : "doc" ,
"id" : "concepts/general/hot_reload" ,
"key" : "concepts/general/hot_reload" ,
"label" : "Hot Reload"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/socket_efuns" ,
"key" : "concepts/general/socket_efuns" ,
"label" : "Socket Efuns"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/tls" ,
"key" : "concepts/general/tls" ,
"label" : "TLS"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/websocket" ,
"key" : "concepts/general/websocket" ,
"label" : "WebSocket"
} ,
{
"type" : "doc" ,
"id" : "concepts/general/tracing" ,
"key" : "concepts/general/tracing" ,
"label" : "Tracing"
}
]
}
]
} ,
"driver" : {
"type" : "category" ,
"key" : "driver" ,
"label" : "Driver Internals" ,
"link" : {
"type" : "generated-index" ,
"title" : "Driver Internals" ,
"slug" : "/driver/" ,
"description" : "Configuration reference and implementation internals of the FluffOS driver, from runtime config flags to the VM, memory, and native extension layers."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "driver/config" ,
"key" : "driver/config" ,
"label" : "Runtime Configuration"
} ,
{
"type" : "doc" ,
"id" : "driver/wasm" ,
"key" : "driver/wasm" ,
"label" : "WASM Driver Cookbook"
} ,
{
"type" : "doc" ,
"id" : "driver/adding_efuns" ,
"key" : "driver/adding_efuns" ,
"label" : "Adding Efuns"
} ,
{
"type" : "doc" ,
docs: rewrite FFI reference around buffer features, add worked libc examples (#1275)
* docs: rewrite FFI reference around buffer features, add worked libc examples
Update docs/driver/ffi-plan.md now that buffers carry the full byte
toolkit (string/int-array promotion, byte lvalues, range assignment,
foreach, concatenation):
- New "The buffer type does the heavy lifting" section mapping each
buffer language feature to its FFI use.
- The C-string idiom is now the two-line promotion form
(buffer b = s; b += ({ 0 })) instead of chained string_encode()
calls; string_encode stays documented for non-UTF-8 encodings, and
the doc notes that promotion never reaches inside ffi_call's args
array (the byte boundary stays explicit).
- New "Worked examples -- calling libc" section: scalars (sqrt/pow/abs),
strings in (strlen, incl. range assignment into an allocation),
strings out (getenv + ffi_peek(addr, -1) + string_decode with a NULL
check), out-parameters (frexp), structs (time/localtime via
ffi_struct_layout), callbacks (qsort with an LPC comparator), and
foreach over peeked bytes.
- Sync the reference with the implementation: add ffi_address() (was
missing from the efun listing), correct ffi_free() (zeroes bytes; GC
reclaims), ffi_peek(-1) (NUL-terminated read capped at the max
buffer size config), FFI_POINTER args accepting int addresses
(0 = NULL), pointer returns always being int addresses, the
FFI_INT/FFI_LONG aliases, ffi_load("") semantics, the valid_ffi
operation names with a sample master implementation, and the real
build default (PACKAGE_FFI ships ON; runtime denies without a
valid_ffi apply; WASM forces it off).
Every snippet is pinned verbatim by a new testsuite file,
testsuite/single/tests/efuns/ffi_doc_examples.lpc (guarded by
__PACKAGE_FFI__ and the ffi_probe availability fixture like the other
ffi tests). Verified: RelWithDebInfo build, ffi glob (21 files) and the
full LPC suite (600 files) pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* docs: add a local build quickstart to AGENTS.md
Record the verified Debian/Ubuntu setup so future agents don't
rediscover it one configure failure at a time: the full apt package
list (CI's packages: lines assume a GitHub runner image that
preinstalls cmake/ninja/libicu-dev), the configure/build commands, the
build/src/driver binary path, -DPACKAGE_DB=OFF as the escape hatch
when no MySQL client dev package is available, that libevent is
vendored and GTest optional, and how to run the LPC suite without a
pipe masking the driver's exit status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* docs: rename driver/ffi-plan.md to driver/ffi.md
The page stopped being a plan and is the package reference; name it
accordingly (site URL /driver/ffi/, sidebar label "FFI Package").
Update every reference: source comments (ffi.spec, ffi.cc, both
include/ffi.h copies), tools/ffi (README, generate.py's module
docstring and the two comment lines it emits into generated bindings,
with the committed ffi_genmath.lpc fixture updated to match), the
testsuite doc-examples pin, sidebar_meta.json + regenerated
sidebars.generated.json, and the zh-CN sidebar-label key.
Note: the old /driver/ffi-plan/ URL is not redirected (the site has no
client-redirects plugin configured).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* ffi: gate ffi_peek through valid_ffi("peek")
ffi_peek(address, nbytes) copies bytes from an arbitrary native
address, which makes it a process-memory disclosure primitive on its
own (and a driver crash on an unmapped address) -- yet it was the one
dangerous efun in the package not gated by valid_ffi, so any object
could call it whenever PACKAGE_FFI was compiled in, even under a
deny-all master. The other ungated efuns operate only on LPC-owned
buffers and handles (ffi_address reveals a buffer's own address, inert
without a peek/call grant) and stay ungated.
f_ffi_peek now calls check_valid_ffi("peek", address) before touching
any memory, exactly like load/symbol/prepare/callback. The testsuite
master denies a -0xDEAD sentinel address so the denial path is
testable without dereferencing anything; ffi_peek.lpc pins the exact
error. Docs updated: valid_ffi(4), ffi_peek(3), and the op list +
sample master in docs/driver/ffi.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
* stdlib: add /std/ffi_util, FFI-boundary helpers for package_ffi callers
A pure-LPC library packaging the recurring idioms at the C boundary so
binding code stops re-deriving them:
- cstr(s) / cstr_enc(s, enc): NUL-terminated C strings (buffer
promotion for UTF-8, string_encode for other byte encodings)
- c_string(addr) / c_string_enc(addr, enc): read a returned char*,
with the NULL -> 0 convention folded in
- c_out(type_code): a zeroed out-parameter block for T*
- c_argv(strings): a NULL-terminated char*[] plus the kept-alive
per-string buffers (the lifetime footgun a library should own)
- c_field / c_field_set: struct-field access over ffi_struct_layout()
layouts
Deliberately consumed via `inherit "/std/ffi_util"` rather than the
simul_efun object: c_string() calls ffi_peek(), gated by
valid_ffi("peek", addr, caller), and inheriting keeps the consuming
object as the security principal the master sees. The docs page
(docs/stdlib/ffi_util, "FFI Utilities" in the sidebar) spells this out.
Tests: testsuite/single/tests/std/ffi_util.lpc exercises every helper
against libc (strlen with both encodings, getenv through c_string,
frexp through c_out, strtol's char **endptr through
c_out(FFI_POINTER), and structural checks on c_argv's pointer array).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SooGWcJbSWszCZ67k4iVQT
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-15 22:09:25 -07:00
"id" : "driver/ffi" ,
"key" : "driver/ffi" ,
"label" : "FFI Package"
docs: fully-expandable generated sidebar + Chinese docs via Docusaurus i18n (#1246)
* docs: fully-expandable generated sidebar, replacing index.md link pages
Rework docs navigation so the sidebar expands to every page of every
reference tree, instead of terminating at generated index.md link lists:
- New docs/gen_sidebar.py (replaces gen_index.py + update_index.sh):
walks efun/, apply/, stdlib/, concepts/, driver/, cli/ and zh-CN/ and
emits sidebars.generated.json — a full Docusaurus category tree per
directory. Category landing pages are now `generated-index` card pages
(title/description/slug), so all generated index.md files are deleted.
--check mode verifies freshness; new .github/workflows/docs-sidebar.yml
runs it in CI.
- New docs/sidebar_meta.json holds curated presentation: category labels,
one-line descriptions (shown on the landing cards), explicit ordering
(driver/cli/concepts read top-down from user-facing to internals) and
per-page label overrides.
- sidebars.ts becomes a hand-authored skeleton (Getting Started, lpc/,
Historical) that splices in the generated trees.
Content reorganization (from a docs-wide review):
- Move misplaced efun pages out of efun/general: terminal/protocol efuns
(act_mxp, send_zmp, request_term_*) to interactive/, debugging efuns
(check_memory, dump_*, clear_debug_level, destructed_objects) to
internals/, shallow_inherit_list to system/.
- Delete stub duplicates superseded by complete pages elsewhere:
general/parse_{add_synonym,dump,my_rules,remove}, contrib/{shuffle,
element_of}.
Modernize key pages with MDX:
- index.mdx: landing page with a card grid linking each doc section.
- build.mdx: per-platform <Tabs> (Ubuntu/macOS/Windows/Alpine+Docker),
admonitions, VitePress [[toc]] leftover removed, stale per-platform CI
workflow links updated to the unified ci.yml.
- ffi-plan.md: GitHub-style [!CAUTION] alert converted to an admonition.
`npm run build` passes clean (onBrokenLinks: throw, no warnings).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
* docs: serve the Chinese corpus through Docusaurus i18n
Move the zh-CN/ directory out of the default docs tree and into a proper
Docusaurus locale (i18n/zh-CN/docusaurus-plugin-content-docs/current/):
- The flat zh-CN/efun/ directory (333 pages) is re-homed to mirror the
categorized English layout (name-matched 1:1; `hash` maps to strings/
per its own frontmatter). apply/ pages map 1:1; the stray English-text
zh-CN/apply/master/view_errors.md documents a MudOS-era apply that no
longer exists in the driver and is dropped; stdlib/db/database_zh.md
becomes the i18n translation of stdlib/db/database.md; the Chinese
build guide becomes the translation of build.mdx.
- Untranslated pages automatically fall back to English content under
/zh-CN/, so the whole site is navigable in either locale from the new
navbar locale dropdown.
- Both locales share one sidebar. Generated sidebar items now carry
stable `key` fields (the directory/doc path) so translation keys are
unique (both efun/ and stdlib/ have an "Arrays" category, crypto and
strings both document `hash`). Category labels, generated-index
titles/descriptions, navbar and footer are translated in
i18n/zh-CN/...; theme UI strings come from Docusaurus' bundled
zh-Hans translations. Translated landing page at /zh-CN/.
- The "中文文档" sidebar section, the zh-CN tree in gen_sidebar.py /
sidebar_meta.json, and its slice of sidebars.generated.json are gone.
- Relative .md-file links on pages that render in both locales break
the localized build (the file->permalink map points at the localized
copy), so concepts/, the two socket_*_option pages and the config.md
generator now emit extension-less route links instead.
- zh interactive.md/objects.md get explicit slugs like their English
counterparts (a doc named after its parent directory is otherwise a
Docusaurus category-index doc, colliding with the generated-index
route).
`npm run build` builds both locales clean (onBrokenLinks: throw).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174GM2azvAHBmvyESwxm5om
---------
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-11 17:20:25 -04:00
} ,
{
"type" : "doc" ,
"id" : "driver/call_into_vm" ,
"key" : "driver/call_into_vm" ,
"label" : "Calling Into the VM"
} ,
{
"type" : "doc" ,
"id" : "driver/stackmachine" ,
"key" : "driver/stackmachine" ,
"label" : "Stack Machine"
} ,
{
"type" : "doc" ,
"id" : "driver/parse_tree" ,
"key" : "driver/parse_tree" ,
"label" : "Parse Tree Nodes"
} ,
{
"type" : "doc" ,
"id" : "driver/malloc" ,
"key" : "driver/malloc" ,
"label" : "Memory Allocation"
}
]
} ,
"cli" : {
"type" : "category" ,
"key" : "cli" ,
"label" : "Command-Line Tools" ,
"link" : {
"type" : "generated-index" ,
"title" : "Command-Line Tools" ,
"slug" : "/cli/" ,
"description" : "Reference for the driver binary and the companion command-line utilities shipped with FluffOS for compiling, inspecting, and converting LPC and save-file data."
} ,
"items" : [
{
"type" : "doc" ,
"id" : "cli/driver" ,
"key" : "cli/driver" ,
"label" : "driver — the game server"
} ,
{
"type" : "doc" ,
"id" : "cli/lpcc" ,
"key" : "cli/lpcc" ,
"label" : "lpcc — LPC compiler"
} ,
{
"type" : "doc" ,
"id" : "cli/symbol" ,
"key" : "cli/symbol" ,
"label" : "symbol — LPC inspector"
} ,
{
"type" : "doc" ,
"id" : "cli/o2json" ,
"key" : "cli/o2json" ,
"label" : "o2json — save file to JSON"
} ,
{
"type" : "doc" ,
"id" : "cli/json2o" ,
"key" : "cli/json2o" ,
"label" : "json2o — JSON to save file"
} ,
{
"type" : "doc" ,
"id" : "cli/portbind" ,
"key" : "cli/portbind" ,
"label" : "portbind — privileged ports"
} ,
{
"type" : "doc" ,
"id" : "cli/generate_keywords" ,
"key" : "cli/generate_keywords" ,
"label" : "generate_keywords — efun metadata"
}
]
}
}