How to add new built-in functions, modules, pattern constructs, internal .m
rules, and parser operators. The architectural background lives in
SPEC.md; the contributor workflow (testing, valgrind, coding
standards) lives in CLAUDE.md.
The most common extension. Six steps.
Built-ins have signature:
Expr* builtin_myfunc(Expr* res);Ownership contract (mirrors SPEC.md §4):
- Return
NULLwhen the function cannot evaluate (wrong arity, symbolic args, etc.). The evaluator retains ownership ofres; do not free it. - Return a new
Expr*on success. The evaluator freesresfor you. Do not callexpr_free(res)yourself — seefeedback_builtin_res_ownershipin auto-memory if in doubt.
Template:
Expr* builtin_myfunc(Expr* res) {
size_t argc = res->data.function.arg_count;
if (argc != 1) return NULL;
Expr* arg = res->data.function.args[0];
if (arg->type != EXPR_INTEGER) return NULL;
int64_t v = arg->data.integer;
return expr_new_integer(v * v);
}Handle EXPR_BIGINT alongside EXPR_INTEGER when the function should work for
large integers, and EXPR_REAL when it should handle floats. For
Rational[n, d] or Complex[re, im] operands check EXPR_FUNCTION with the
appropriate head.
Add the prototype to the module's .h file:
Expr* builtin_myfunc(Expr* res);void mymodule_init(void) {
symtab_add_builtin("MyFunc", builtin_myfunc);
symtab_get_def("MyFunc")->attributes |= ATTR_LISTABLE | ATTR_PROTECTED;
symtab_set_docstring("MyFunc",
"MyFunc[x]\n\tComputes the square of x.");
}Docstrings must be terse — no inline examples; put those in the relevant
docs/spec/builtins/*.md file and the changelog entry.
If this is a new module, add mymodule_init() to core_init() in core.c.
| Attribute | When to use |
|---|---|
ATTR_PROTECTED |
Almost always — prevents user redefinition. |
ATTR_LISTABLE |
Threads over lists: f[{a, b}] → {f[a], f[b]}. |
ATTR_NUMERICFUNCTION |
Operates on numbers. |
ATTR_FLAT |
Associative: f[f[a, b], c] → f[a, b, c]. |
ATTR_ORDERLESS |
Commutative: f[b, a] → f[a, b]. |
ATTR_HOLDALL / ATTR_HOLDFIRST / ATTR_HOLDREST |
Suppress evaluation of some/all arguments. |
*Q predicates must return True or False, never symbolic and never NULL
(see feedback_q_predicates_return_bool in auto-memory).
Add to an existing tests/test_*.c or create a new one and register it in
tests/CMakeLists.txt.
#include "test_utils.h"
#include "expr.h"
#include "eval.h"
#include "core.h"
#include "symtab.h"
void test_myfunc_basic(void) {
assert_eval_eq("MyFunc[5]", "25", 0);
}
void test_myfunc_symbolic(void) {
assert_eval_eq("MyFunc[x]", "MyFunc[x]", 0);
}
int main(void) {
symtab_init();
core_init();
TEST(test_myfunc_basic);
TEST(test_myfunc_symbolic);
return 0;
}Helpers in tests/test_utils.h: TEST, ASSERT, ASSERT_STR_EQ,
ASSERT_MSG, assert_eval_eq(input, expected, is_fullform).
A builtin with no counterpart among the reference language's System names
(Mathilda's own extensions, such as NDArray or CompileDiagnostics) belongs in
the Mathilda`` context. Add it to the mathilda_onlytable insym_mark_mathilda_context (src/sym_names.c). Otherwise it is treated as a System`` name, and a package that defines a symbol of the same name writes to
the protected builtin instead of getting its own symbol.
Edit the matching docs/spec/builtins/*.md and add a changelog entry under
the current week's docs/spec/changelog/<YYYY-MM-DD>.md, where
<YYYY-MM-DD> is the Monday of the ISO week (Mon – Sun) the change lands
in. Create the file with a # Changelog: week of <Mon> (Mon) – <Sun> (Sun)
header (and a corresponding row in Mathilda_spec.md's changelog table) if
it does not yet exist. The top-level Mathilda_spec.md only needs touching
if you introduce a new top-level category or a new weekly changelog file.
- Create
src/mymodule.candsrc/mymodule.h(or a subdirectory analogous tosrc/poly/,src/linalg/,src/simp/,src/calculus/). - Define and declare
void mymodule_init(void). #include "mymodule.h"incore.cand callmymodule_init()fromcore_init().- New
.cfiles insrc/(and subdirectories already wired into the makefile) are picked up automatically bySRC = $(wildcard $(SRC_DIR)/*.c).
- Extend
match.cto recognize the new pattern during tree unification. - If the construct needs new syntax, extend
parse.c— define a precedence, add token recognition, and emit a standardExpr*function call. - Add tests in
tests/covering both match and non-match scenarios. - If the evaluator would prematurely evaluate the new pattern expression, guard the surrounding builtin with the appropriate Hold attribute.
For mathematical identities that are naturally rewrite rules:
- Create
src/internal/yourfile.m. - Add
Get["src/internal/yourfile.m"]tosrc/internal/init.m. - Define rules using normal Mathematica syntax:
MyFunc[0] := 0;
MyFunc[x_ + y_] := MyFunc[x] + MyFunc[y];
MyFunc[n_Integer x_] := n MyFunc[x];This approach is ideal for derivative rules, identities, and simplification
rules where the pattern-matching system does the heavy lifting (see
src/internal/deriv.m).
- In
parse.c, define a new precedence constant (consult the Mathematica documentation for the standard value). - Add token recognition in the lexer section of the parser.
- Implement the infix / prefix / postfix logic in
parse_expression_prec(). - The operator should produce a normal function-call expression (e.g.,
a <> bshould produceStringJoin[a, b]).