LoadModule¶
Status: Stable
documented, exercised by the test suite and/or worked examples, with no known limitations recorded.
Description¶
LoadModule["relpath"]
loads the internal Mathilda source module at relpath (relative to src/internal), resolving the location independently of the current working directory. Each module is loaded at most once. Returns True if the module was located and loaded, False otherwise.
Examples (10)¶
Every input below was run against the current Mathilda build and its output recorded.
Basic Examples (3)¶
Load an internal module
Already loaded: still True, not re-read
Not found -> False
Applications (7)¶
Already loaded by the kernel at startup, so True without re-reading the file
One of the identity libraries FullSimplify loads on demand
Load-once: a repeat is a table hit, not a re-read
Not found: False, with the LoadModule::nofile message silenced
The message routes through the funnel, so Check sees it
The guard idiom internal modules use for an optional dependency
A non-string argument is left unevaluated
Algorithm¶
loadmodule.c -- runtime loading of Mathilda (.m) source modules.
See loadmodule.h for the contract. The file-reading loop here is the same one Get[] uses (builtin_get in readwrite.c is now a thin wrapper over mathilda_run_file); mathilda_load_module adds working-directory-independent path resolution and load-once bookkeeping on top.
Path resolution (mathilda_resolve_internal) is deliberately CWD-independent: a relocated or installed binary must still find its bundled src/internal tree — and so must an installed shared libmathilda loaded into some other program (a Python extension, a Jupyter kernel), which is located through dladdr. On glibc, readlink("/proc/self/exe") needs the POSIX feature-test macro and dladdr/Dl_info need _GNU_SOURCE to be visible under -std=c99; define it before any system header.
Implementation notes¶
Algorithm. builtin_loadmodule takes one EXPR_STRING relpath, calls
mathilda_load_module, and returns True or False. Any other argument shape
leaves the call unevaluated. mathilda_load_module returns True at once if
the relpath is already in the load-once table (lm_already_loaded). Otherwise it
resolves the path, runs the file with mathilda_run_file, and on a successful
open records the relpath (lm_mark_loaded), so a repeat call never re-reads it.
A relpath that resolves nowhere emits LoadModule::nofile through mth_message,
so Quiet and Check both see it, and returns False.
mathilda_resolve_internal is deliberately independent of the working
directory. It first tries the cached base directory (lm_base) from an
earlier hit, then searches in this order:
$MATHILDA_HOME/<relpath>, then$MATHILDA_HOME/internal/<relpath>. The parent form was added for issue #96, where a kernelspec pointing at the directory containinginternal/silently missed late-loaded tables.- Relative to the running executable (
lm_exe_dir, usingreadlink("/proc/self/exe")on Linux,_NSGetExecutablePathon macOS andGetModuleFileNameAon Windows):<exe_dir>/src/internal/and<exe_dir>/../share/mathilda/internal/. - Relative to the image that holds this code (
lm_self_dir, usingdladdron POSIX andGetModuleHandleExAon Windows):<lib_dir>/../share/mathilda/internal/. An installed sharedlibmathildaloaded into Python or Jupyter therefore finds its modules without$MATHILDA_HOME(issue #88). This step is skipped when that directory is the executable's own. - The compile-time
MATHILDA_PREFIX/share/mathilda/internal/. - A CWD ladder:
src/internal/up to three levels up, for the test binaries.
mathilda_run_file is the file-reading core that Get also wraps. It slurps the
file and loops parse_next_expression / evaluate, stopping at end of file or at
the first unparseable expression, and keeps the last value. LoadModule
discards that value and reports only whether the file was opened.
Data structures. The bookkeeping uses fixed-size tables: lm_loaded[256][256]
holds the relpaths (past 256 modules new entries are silently not recorded), and
lm_base caches the base directory. The file is read into one transient buffer.
On Linux _GNU_SOURCE is defined before any include, so readlink and dladdr
are visible under -std=c99 (SPEC.md §10).
Complexity / limits. A first load is O(file size) to read and evaluate. A
repeat is a linear scan of the table, which is effectively O(1) for the small
fixed module set. Each module is loaded at most once, keyed by the literal
relpath string. That is what lets callers load lazily without re-registering
rules: core.c loads simp/FullSimplify.m at startup, FullSimplify loads its
simp/transforms/*.m families, init.m loads distributions.m, and
Integrate loads PolyLogLadder.m, mixed/ParallelMixed.m and
ParallelMixedSpecial.m. A file read with Get is not recorded. A syntax error
part-way through a module still counts as a successful load. Attributes:
ATTR_PROTECTED.
Protected. ReturnsTrueif the module was located and loaded (or had already been loaded),Falseotherwise.- Resolution is independent of the current working directory and tries, in
order:
$MATHILDA_HOME/<relpath>(then$MATHILDA_HOME/internal/<relpath>, so pointing it at the directory containinginternal/also works);<exe_dir>/src/internal/<relpath>and<exe_dir>/../share/mathilda/internal/<relpath>(relative to the running binary, so a relocated or installed executable still finds its modules);$(PREFIX)/share/mathilda/internal/<relpath>when built with a compile-timeMATHILDA_PREFIX; and finally a CWD ladder (src/internal/,../src/internal/,../../src/internal/,../../../src/internal/). This works from the REPL (run at the repo root or from anywhere with the binary in place), from the test binaries (run fromtests/build/), and from a binary copied to abindirectory withMATHILDA_HOMEpointing atsrc/internal. The winning base directory is cached after the first successful lookup. - Each module is loaded at most once, so repeated calls — and the lazy
per-family loading used by
FullSimplify— never re-register rules. - Generalises the bespoke fallback previously hard-coded for the CRC integral
tables;
Get(above) shares its file-reading core.
Attributes: Protected.
References¶
See also: Get
- Source:
src/loadmodule.c - Specification:
docs/spec/builtins/file-io.md
Notes & additional examples¶
Notes¶
LoadModule["relpath"]loads a Mathilda.msource module, whererelpathis relative tosrc/internal(or the installedshare/mathilda/internal). It returnsTrueif the module was found and loaded, or had already been loaded, andFalseotherwise.- Each module is loaded at most once per session, keyed by the exact
relpathstring. This makes it safe to call from inside other modules and from lazy loaders:FullSimplifyloads itssimp/transforms/*.mfamilies on demand, andIntegrateloadsPolyLogLadder.mandmixed/ParallelMixed.mthis way. A module first loaded withGetis not in the table, soLoadModulewould read it again. - Resolution does not depend on the working directory. The search order is:
$MATHILDA_HOME/(or itsinternal/subdirectory, since v0.449); the running executable'ssrc/internal/and../share/mathilda/internal/; the directory of the image that holds the code, so an installed sharedlibmathildainside Python or Jupyter finds its modules; a compile-timePREFIX; and finally a CWD ladder. The winning base directory is cached. - A missing file emits
LoadModule::nofilethroughmth_message, so bothQuietandCheckwork. A file that opens but stops at a syntax error still counts as loaded (True), because reading simply ends at the first unparseable expression, as withGet. - Attributes:
Protected. The value of the module's last expression is discarded. UseGetto get it back. - See also:
Get,FullSimplify.