GuidesInject custom commands

Inject custom commands

Write an argspec, validate it, and register a custom command so the parser reads its arguments correctly.

The parser knows the argument structure of every command in its packages, but real corpora are full of project macros the knowledge base has never seen. Left unknown, a macro like \mycmd[opt]{body} parses as a bare command and its bracketed argument leaks out as loose tokens. You fix that by registering the command yourself: describe its signature as an argspec string, add it to a knowledge base as a context item, and a parser using that knowledge base reads its arguments exactly as if the command were built in.

The workflow has three steps: write the argspec, check it with validate_argspec, then build a context item and pass it to the knowledge base the parser uses. The example registers \mycmd with the signature o m — one optional bracketed argument followed by one mandatory group.

import texform

# 1. Write the signature and confirm it parses to the slots you expect.
check = texform.validate_argspec("o m")
print("valid:", check["valid"], "arg_count:", check["arg_count"])

# 2. Build a context item for the command. Python keys are snake_case.
item = {
    "target": "command",
    "name": "mycmd",
    "kind": "prefix",
    "allowed_mode": "math",
    "argspec": "o m",
}

# 3. Add it to a knowledge base, build a parser on it, and parse a formula.
kb = texform.KnowledgeBase(items=[item])
parser = texform.Parser(knowledge_base=kb)
doc = parser.parse(r"\mycmd[opt]{body} + \mycmd{bare}")["document"]
print("latex:", doc.to_latex())

for node in doc.find_commands("mycmd"):
    slots = [None if s is None else s["kind"] for s in node.arg_slots()]
    print("slots:", slots)
Output
valid: True arg_count: 2
latex: \mycmd [ o p t ] { b o d y } + \mycmd { b a r e }
slots: ['Math', 'Math']
slots: [None, 'Math']
import { KnowledgeBase, Parser, validateArgspec } from 'texform';

// 1. Write the signature and confirm it parses to the slots you expect.
const check = validateArgspec('o m');
console.log('valid:', check.valid, 'argCount:', check.argCount);

// 2. Build a context item for the command. JavaScript keys are camelCase.
const item = {
  target: 'command',
  name: 'mycmd',
  kind: 'prefix',
  allowedMode: 'math',
  argspec: 'o m',
};

// 3. Add it to a knowledge base, build a parser on it, and parse a formula.
const knowledgeBase = new KnowledgeBase({ items: [item] });
const parser = new Parser({ knowledgeBase });
const doc = parser.parse('\\mycmd[opt]{body} + \\mycmd{bare}').document;
console.log('latex:', doc.toLatex());

for (const node of doc.findCommands('mycmd')) {
  const slots = node.argSlots().map((s) => (s === null ? null : s.kind));
  console.log('slots:', JSON.stringify(slots));
}
Output
valid: true argCount: 2
latex: \mycmd [ o p t ] { b o d y } + \mycmd { b a r e }
slots: ["Math","Math"]
slots: [null,"Math"]

The signature is honored: \mycmd[opt]{body} fills both slots, while \mycmd{bare} leaves the optional slot empty (None in Python, null in JavaScript) and fills only the mandatory one. The parser bound [opt] as the optional argument instead of letting it fall through as stray characters, which is the whole point of registering the command.

Commands are not the only thing you can inject. A context item is a tagged record, and the target field selects one of three kinds: "command" as above, "environment" for a custom \begin{...} block (which also takes a body_mode, since an environment body can be math or text), and "delimiter" for a delimiter-control name usable after \left and \right. They all go into the same items list of the KnowledgeBase constructor.

Python
import texform

item = {
    "target": "environment",
    "name": "myenv",
    "allowed_mode": "math",
    "body_mode": "math",
    "argspec": "m",
}

kb = texform.KnowledgeBase(items=[item])
doc = texform.Parser(knowledge_base=kb).parse(r"\begin{myenv}{c} x \end{myenv}")["document"]
print(doc.to_latex())
Output
\begin {myenv} { c } x \end {myenv}

What you cannot inject is a rewrite rule. Context items extend the parser's knowledge of what a command's arguments look like; they do not tell the transform engine what to turn that command into. The rule set is compiled in, and the only user-facing control over it is switching rules off — see Disable a rewrite rule.

For the full argspec notation — every slot kind, delimited forms, and the extensions to xparse — see the argspec reference; for the context-item shape and construction options, see the KnowledgeBase API.

Next steps#

Last updated