fluffos/docs/lpc/preprocessor/include.md
Yucong Sun 55956a24b7
Add inherit_program / include_file master applies; auto hot-reload demo (#1230)
* Add inherit_program / include_file master applies; auto hot-reload demo

New compile-time master applies, consulted for every inherit statement
and #include directive:

* mixed inherit_program(string from, string path, int priv)
  Called while compiling `from` for `inherit "path";` (priv nonzero for
  private inherits). A string return is an alternate path for the
  inherited file; an array-of-strings return is the inherited program's
  source itself (compiled via load_object_from_source under the inherit
  statement's name, through the existing load_object retry loop); any
  other return prevents the inheritance.

* mixed include_file(string compiled, string from, string path)
  Called when `from` is about to include `path` while compiling
  `compiled`. A string return is the translated path (resolved absolute
  from the mudlib root or relative to the includer; returning `path`
  unchanged keeps the "..."-vs-<...> search semantics); an
  array-of-strings return is the included text itself (pushed as an
  in-memory include buffer with the usual file-identity bookkeeping);
  any other return prevents the inclusion.

Both follow the valid_override/get_include_path precedent for calling
master LPC mid-compile (skipped without a VM context or master object),
and a missing apply keeps stock behavior.

The applies expose the full compile-time dependency graph, which the
testsuite uses to demonstrate mudlib auto hot-reload on file changes:
/single/hot_reload.lpc registers as the master's compile hooks, records
which source files each program's bytecode was built from (own source,
includes, inherited programs, transitively), and its call_out poller
destructs+reloads watched blueprints whose dependency closure changed
on disk - including reloading a stale parent when only the parent's
include changed.

Testsuite: single/tests/compiler/{inherit_program,include_file}.lpc pin
the apply semantics (redirect, inline source, deny, priv flag, argument
shapes on nested includes) via the scriptable /clone/compile_hook;
single/tests/applies/hot_reload.lpc demonstrates end-to-end hot reload
over a runtime-written inherit+include fixture chain. Docs added for
both applies.

Validated: full LPC suite (532 files) x2 on RelWithDebInfo, x2 on
Debug+ASan/UBSan, plus the 297 GTest unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

* docs: add hot reload guide for the new compile-time master applies

New concepts page (concepts/general/hot_reload.md) explaining why
"file changed -> reload" needs the compile-time dependency graph, how
the inherit_program / include_file master applies expose it, and a
step-by-step mudlib implementation with examples: master delegation,
dependency recording, closure computation, change detection with
size+mtime snapshots, parent-first reload ordering, and the call_out
poller. Documents blueprint-reload semantics and caveats (clones keep
the old program, no compiles inside the applies, records only complete
for compiles observed by the daemon), pointing at the testsuite
reference implementation. Cross-linked from both apply reference pages
and the concepts indices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

* docs/testsuite: say "master copy", not "blueprint"

Align the hot-reload guide, apply reference, daemon, and test comments
with the project's terminology for the object loaded from a file (see
clonep(3): "the master copy").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

* testsuite: edge cases for the compile-time applies; docs review fixes

compile_hooks_edge.lpc pins that unusual inherit_program/include_file
return values produce clean LPC errors or the documented behavior,
never a driver crash: empty array, non-string elements, redirect to
self, empty-string redirect, inline source vs already-loaded object,
multiline inline content, the apply itself throwing mid-compile
(safe_apply falls back to default resolution), extension-spelled
redirects, and denying the auto-included global include file.
Fixtures in /clone/adv_*; /clone/adv_hook wraps the scriptable hook
with a throwing include_file.

Docs fixes from review: the apply-page examples now look the daemon up
with find_object() instead of a path call_other (which would load the
target and trigger a compile mid-compile -- the exact pattern the same
pages forbid), and the hot-reload guide's ancestors() snippet carries
the seen-mapping argument to match the reference daemon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

* hot_reload: fix multi-watch and failed-reload handling; harden tests

Three defects from the LPC review round, each now pinned by the
demonstration test:

* check_now() collected and reloaded in one pass, so the first reload's
  snapshot refresh erased the change evidence for every other watched
  program sharing the dependency (a common header, or a watched parent
  iterating before its watched child - which then stayed bound to the
  destructed old parent forever). The stale set is now collected before
  any reload runs.

* A reload whose recompile throws (a syntax error mid-edit - the most
  common event in a hot-reload workflow) unwound poll() before the
  re-arm, killing the poller forever and leaving the watched master
  copy destructed. poll() re-arms first, check_now() catches per
  program, reload_count only counts successes, and closure_changed()
  treats a watched-but-not-loaded program as stale so the retry
  self-heals once the file compiles again.

* The three apply tests registered master compile hooks (and the
  hot-reload test armed the poller and wrote /data/hot) with cleanup on
  the success path only; a thrown check would leave the hook routing
  every remaining compile of the randomized run. Each test now runs its
  checks under catch and unhooks/tears down unconditionally, re-raising
  the error afterward.

The demo test now also covers the shared-dependency pass (watch parent
and child, change the common include, both reload in one pass) and the
broken-edit recovery cycle. The hot-reload guide's snippets are updated
to match the daemon.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

* simulate: let inline inherit source resolve its own unloaded inherits

From the C++ review round: master::inherit_program returning inline
source whose text itself inherits a not-yet-loaded program failed the
whole load with "#inherit is not supported when compiling from
in-memory source" -- reachable in the feature's primary use case, since
synthesized programs routinely inherit ordinary on-disk library files.

load_object_from_source() now runs the same iterative dance as
load_object(): when the compile aborts on an unloaded parent, load that
parent (from disk, or from further master-supplied inline source, so
synthesized-inheriting-synthesized chains work), then recompile the
same source string -- which is in hand, unlike the historical
no-filename rationale for rejecting #inherit here. Mirrors
load_object()'s guards: illegal-to-inherit-self, the duplicate-name
check after the parent's arbitrary LPC ran, and an inherit-chain bound
on the retry loop as a backstop against a master that redirects to a
fresh unloaded name on every recompile.

The inherit_program test now covers both new shapes (inline inheriting
unloaded on-disk, and two levels of inline source); the apply doc drops
the already-loaded-only caveat.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018DGhzJfPhDGJA94EPh1gEK

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-10 10:27:27 -04:00

60 lines
1.8 KiB
Markdown

---
title: preprocessor / include
---
# #include
```c
#include "defs.h"
#include <mudlib.h>
```
`#include` splices the named file into the compile at the point of the
directive, exactly as if its contents had been typed there. Included
files are re-read on every recompile of the including object.
## Search order
* **`#include "file"`** — first resolved **relative to the directory
of the including file**, then (if not found there) through the
include path.
* **`#include <file>`** — resolved through the include path only.
The include path is the config file's `include directories` list; the
master apply `get_include_path(file)` can override it per compiled
file. Names may contain subdirectories (`<sys/net.h>`), but `..` is
not allowed. Every candidate path is subject to the master's
`valid_read` check.
The master apply `include_file(compiled, from, path)` is consulted for
every directive before resolution: it can translate the include to
another path, supply the included text itself (an array-of-strings
return), or deny the inclusion. Returning `path` unchanged keeps the
behavior described above.
## Directive line details
Text after the closing `"` or `>` is ignored, so trailing comments are
fine:
```c
#include <mudlib.h> /* mud info defines */
```
The file name may also be produced by a macro:
```c
#define CONFIG "local.h"
#include CONFIG
```
Includes nest (an included file may `#include` further files) up to a
driver-enforced depth limit; diagnostics inside an included file print
the full `In file included from ...` chain (see
[diagnostics](../diagnostics)).
## #include vs inherit
`#include` copies text into each including object — every object gets
its own compiled copy. [`inherit`](../constructs/inherit) shares one
compiled program among all inheritors. Use header files for
definitions (`#define`s, prototypes) and `inherit` for code.