TransformEngineclass#
Normalize a formula into the canonical form selected by a profile.
The engine runs a multi-phase pipeline (LowerAttributes, a fixed-point
Rewrite loop, FinalizeAst, FlattenGroups). A profile picks rule levels;
TransformConfig is nested by phase and controls per-run switches
without changing the selected rule set. The engine also bundles a parser, so
it exposes parse and shares its immutable knowledge_base().
Since 0.4.0 the bundled parser defaults to ParseConfig LENIENT, and
TransformConfig is the nested four-phase object (not flat
rewrite_enabled / max_iterations keys). Per-call config must be a
complete TransformConfig (or ParseConfig on parse); a dict
passed as config raises ConfigError. Other keywords are overlays
(normalize(src, rewrite={"enabled": False})). Nested overlay values must
be dicts, not phase-config class instances. Construct a full object with
TransformConfig(...) (all four phases required) or the profile
classmethods authoring() / faithful() / corpus() / equiv().
Normalization is gated on a complete tree: an incomplete parse raises
ParseError, since normalizing a tree with holes is meaningless. For the
conceptual model — profiles, the pipeline, and the eliminated-form contract —
see the Transforms guide.
import texform
engine = texform.TransformEngine(profile="corpus")
engine.normalize(r"a \over b") # '\frac { a } { b }'
engine.normalize(r"a \over b", rewrite={"enabled": False})
engine.normalize(r"a \over b", reject_unknown=True, flatten_groups={"enabled": False})
reported = engine.normalize_with_report(r"a \over b")
reported["normalized"] # '\frac { a } { b }'
cfg = engine.default_transform_config()
cfg.rewrite.max_iterations = 50
result = engine.parse(r"a \over b")
engine.transform(result["document"], cfg)
engine.transform_with_report(result["document"], texform.TransformConfig.faithful(), rewrite={"enabled": False})TransformEngine#
TransformEngine( profile: TransformProfile, knowledge_base: KnowledgeBase | None = None, disable_rules: list[str] | None = None, default_parse_config: ParseConfig | None = None,) -> NoneConstruct an engine sharing knowledge; invalid profiles or rules raise ConfigError.
| Parameter | Type | Default |
|---|---|---|
profile | Transform | — |
knowledge_base | Knowledge | None |
disable_rules | list[ | None |
default_parse_config | Parse | None |
normalize#
normalize( src: str, config: TransformConfig | None = None, **overrides,) -> strParse, transform, and serialize a formula in one call.
Normalization is gated on a complete tree. If the input cannot produce a
complete tree, normalize raises ParseError carrying the
diagnostics and the partial document. Empty input is complete and
normalizes normally. This path returns the serialized string and does
not build a report.
| Parameter | Type | Default | Description |
|---|---|---|---|
src | str | — | The LaTeX source string. |
config | Transform | None | A complete |
**overrides | Unpack[ | — | Keyword overlay from |
str — The serialized LaTeX string.
ParseError—If the source does not parse into a complete tree.
ConfigError—If
configis not aTransformConfig, a dict is passed asconfig, or an override key or value is invalid.
engine = texform.TransformEngine(profile="corpus")
engine.normalize(r"\dv{f}{x}")
# '\frac { \mathrm { d } f } { \mathrm { d } x }'normalize_with_report#
normalize_with_report( src: str, config: TransformConfig | None = None, **overrides,) -> NormalizeReportResultParse, transform, and serialize a formula, returning text and a report.
Accepts the same source, config, and keyword overlays as
normalize. Output text and errors follow that contract. The report
is diagnostic: its fields, phase divisions, and counters are not part
of the stable compatibility promise. A failure does not return a
partial report.
| Parameter | Type | Default | Description |
|---|---|---|---|
src | str | — | The LaTeX source string. |
config | Transform | None | A complete |
**overrides | Unpack[ | — | Keyword overlay from |
NormalizeReportResult — A NormalizeReportResult dict with normalized and report.
ParseError—If the source does not parse into a complete tree.
ConfigError—If
configor an override is invalid.
transform#
transform( document: Document, config: TransformConfig | None = None, **overrides,) -> NoneTransform a live Document in place.
transform accepts documents sharing this engine's knowledge-base
instance, including constructed, copied, and imported snapshots.
The document must also be complete;
a document that has_errors() is rejected with TransformError.
This path returns None and does not build a report.
| Parameter | Type | Default | Description |
|---|---|---|---|
document | Document | — | The live |
config | Transform | None | A complete |
**overrides | Unpack[ | — | Keyword overlay from |
None — None.
TransformError—If the document has a different knowledge base, has parse errors, or on a contract violation.
ConfigError—If
configis not aTransformConfig, a dict is passed asconfig, or an override key or value is invalid.
engine = texform.TransformEngine(profile="corpus")
result = engine.parse(r"a \over b")
document = result["document"]
assert document is not None
engine.transform(document)
document.to_latex() # '\frac { a } { b }'transform_with_report#
transform_with_report( document: Document, config: TransformConfig | None = None, **overrides,) -> TransformReportTransform a live Document in place and return its diagnostic report.
Accepts the same document, config, and keyword overlays as
transform. Document ownership, completeness, and errors follow that
contract. The report is diagnostic and is not part of the stable
compatibility promise. A failure does not return a partial report.
| Parameter | Type | Default | Description |
|---|---|---|---|
document | Document | — | The live |
config | Transform | None | A complete |
**overrides | Unpack[ | — | Keyword overlay from |
TransformReport — The diagnostic transform report dict.
TransformError—If the document has a different knowledge base, has parse errors, or on a contract violation.
ConfigError—If
configor an override is invalid.
parse#
parse( src: str, config: ParseConfig | None = None, **overrides,) -> ParseResultParse a LaTeX string using the engine's bundled parser.
Uses this engine's parser and default parse config. Engines default to
lenient parsing; pass config to override it per call. A document
produced here is owned by this engine and is the only kind transform
accepts.
| Parameter | Type | Default | Description |
|---|---|---|---|
src | str | — | The LaTeX source string. |
config | Parse | None | A complete |
**overrides | Unpack[ | — | Keyword overlay from |
ParseResult — A ParseResult dict with document and diagnostics.
ConfigError—If
configis not aParseConfig, a dict is passed asconfig, or an override key or value is invalid.
default_parse_config#
default_parse_config() -> ParseConfigReturn a new ParseConfig holding this engine's parse defaults.
default_transform_config#
default_transform_config() -> TransformConfigReturn a new TransformConfig holding this engine's transform defaults.
Knowledge-base lookups#
Query the knowledge the parser loaded. Predicate methods take a single name; lookup methods take name and a mode of "math" or "text" and return a knowledge-entry dict or None.
knowledge_base#
knowledge_base() -> KnowledgeBaseReturn the immutable knowledge base shared with parsed documents.
TransformConfigclass#
Complete transform configuration, nested by pipeline phase.
Since 0.4.0 this is the only accepted config= type for transform /
normalize. A dict passed as config raises ConfigError; use
keyword overlays (normalize(src, rewrite={"enabled": False})) or
construct this class. The constructor requires all four phase configs; the
classmethods authoring() / faithful() / corpus() / equiv()
return the defaults a profile actually uses.
The four attributes are shared references: cfg.rewrite.enabled = False
is visible through cfg.rewrite.enabled and takes effect when the object
is passed as config. Nested overlay values must be dicts, not these
phase-config instances.
config = texform.TransformConfig(
lower_attributes=texform.LowerAttributesConfig(),
rewrite=texform.RewriteConfig(max_iterations=50),
finalize_ast=texform.FinalizeAstConfig(),
flatten_groups=texform.FlattenGroupsConfig(enabled=False),
)
config.rewrite.max_iterations # 50
texform.TransformConfig.corpus().flatten_groups.preserve_rendered_spacing # FalseTransformConfig#
TransformConfig( lower_attributes: LowerAttributesConfig, rewrite: RewriteConfig, finalize_ast: FinalizeAstConfig, flatten_groups: FlattenGroupsConfig,) -> NoneConstruct a transform configuration. All four phases are required.
| Parameter | Type | Description |
|---|---|---|
lower_attributes | Lower | The LowerAttributes phase config. |
rewrite | Rewrite | The Rewrite phase config. |
finalize_ast | Finalize | The FinalizeAst phase config. |
flatten_groups | Flatten | The FlattenGroups phase config. |
TypeError—If a required phase argument is missing. Start with a profile classmethod such as
TransformConfig.corpus()to change only selected fields.
authoringclass method#
authoring() -> TransformConfigReturn the transform config the authoring profile uses by default.
faithfulclass method#
faithful() -> TransformConfigReturn the transform config the faithful profile uses by default.
corpusclass method#
corpus() -> TransformConfigReturn the transform config the corpus profile uses by default.
equivclass method#
equiv() -> TransformConfigReturn the transform config the equiv profile uses by default.
RewriteConfigclass#
Complete Rewrite phase configuration.
Nested under TransformConfig.rewrite. Disabling this phase leaves
legacy syntax such as \over untouched; it does not change which rule
levels the profile selected. Overlay with rewrite={"enabled": False};
do not pass this class as a nested overlay value.
cfg = texform.RewriteConfig(max_iterations=50)
cfg.enabled # True
cfg.max_iterations # 50RewriteConfig#
RewriteConfig(enabled: bool = True, max_iterations: int = 100) -> NoneConstruct a Rewrite phase configuration.
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | bool | True | Whether the phase runs. |
max_iterations | int | 100 | The cap on fixed-point passes. |
LowerAttributesConfigclass#
Complete LowerAttributes phase configuration.
This is a nested field of TransformConfig, not a dict overlay. Pass the
parent TransformConfig as config; to overlay only this phase use
lower_attributes={"enabled": False}. A dict passed as config on
transform / normalize raises ConfigError.
texform.LowerAttributesConfig().enabled # True
texform.LowerAttributesConfig(enabled=False).enabled # FalseFinalizeAstConfigclass#
Complete FinalizeAst phase configuration.
Nested under TransformConfig.finalize_ast. The phase performs local AST
cleanup such as merging adjacent Prime nodes and normalizing text
sequences. Overlay with finalize_ast={"enabled": False}.
texform.FinalizeAstConfig().enabled # TrueFlattenGroupsConfigclass#
Complete FlattenGroups phase configuration.
Nested under TransformConfig.flatten_groups. enabled governs whether
the phase runs. preserve_rendered_spacing keeps groups whose only
public-facing effect is rendered math spacing; it does not control
serializer source whitespace (see SerializeOptions). Structural guards
stay on even when preserve_rendered_spacing is False. Constructor
defaults match the authoring / faithful strict pair (both True); the
corpus and equiv profiles leave preserve_rendered_spacing off.
Overlay with flatten_groups={"enabled": False}; a dict passed as
config still raises ConfigError.
texform.FlattenGroupsConfig().enabled # True
texform.FlattenGroupsConfig().preserve_rendered_spacing # True
texform.FlattenGroupsConfig(enabled=False).enabled # False
texform.TransformConfig.corpus().flatten_groups.preserve_rendered_spacing # Falsepreserve_rendered_spacingattribute#
boolKeep groups whose only public-facing effect is rendered math spacing. Defaults to True. This is not serializer source spacing.
FlattenGroupsConfig#
FlattenGroupsConfig( enabled: bool = True, preserve_rendered_spacing: bool = True,) -> NoneConstruct a FlattenGroups phase configuration.
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled | bool | True | Whether the phase runs. |
preserve_rendered_spacing | bool | True | Keep groups whose only public-facing effect is rendered math spacing. |
NormalizeOverridesclassextends ParseOverrides, TransformOverrides#
Flat parse+transform overlays for normalize.
Combines ParseOverrides keys with nested phase overlays from
TransformOverrides. A dict passed as config still raises
ConfigError; these keys are keywords only
(normalize(src, rewrite={"enabled": False}, reject_unknown=True)).
engine = texform.TransformEngine(profile="corpus")
engine.normalize(r"a \over b", reject_unknown=True)
# '\frac { a } { b }'TransformOverridesdict type#
Keyword overlays for transform.
Each nested value must be a dict of that phase's overlay keys, not a complete phase-config class instance.
| Field | Type | Description |
|---|---|---|
lower_attributes | Lower | Overlay dict for the LowerAttributes phase. |
rewrite | Rewrite | Overlay dict for the Rewrite phase. |
finalize_ast | Finalize | Overlay dict for the FinalizeAst phase. |
flatten_groups | Flatten | Overlay dict for the FlattenGroups phase. |
RewriteOverridesdict type#
Keyword overlays for the Rewrite phase.
Nested under transform / normalize as rewrite={...}. The nested
value must be a dict, not a RewriteConfig instance.
| Field | Type | Description |
|---|---|---|
enabled | bool | Overlay for |
max_iterations | int | Overlay for |
LowerAttributesOverridesdict type#
Keyword overlays for the LowerAttributes phase.
Nested under transform / normalize as lower_attributes={...}.
The nested value must be a dict, not a LowerAttributesConfig instance.
| Field | Type | Description |
|---|---|---|
enabled | bool | Overlay for |
FinalizeAstOverridesdict type#
Keyword overlays for the FinalizeAst phase.
Nested under transform / normalize as finalize_ast={...}. The
nested value must be a dict, not a FinalizeAstConfig instance.
| Field | Type | Description |
|---|---|---|
enabled | bool | Overlay for |
FlattenGroupsOverridesdict type#
Keyword overlays for the FlattenGroups phase.
Nested under transform / normalize as flatten_groups={...}. The
nested value must be a dict, not a FlattenGroupsConfig instance.
| Field | Type | Description |
|---|---|---|
enabled | bool | | Overlay for |
preserve_rendered_spacing | bool | | Overlay for |
NormalizeReportResultdict type#
Text plus the diagnostic report from one explicit normalize report call.
Plain normalize returns the string directly and does not build this
object.
| Field | Type | Description |
|---|---|---|
normalized | str | The serialized LaTeX string. |
report | Transform | The diagnostic transform report for this call. |
TransformReportdict type#
Diagnostic report for one explicit report call.
Output text and errors follow the ordinary transform contract. Report fields, phase divisions, and these counters are diagnostic: they are not part of the stable compatibility promise. A disabled phase stays present as zeros or an empty list. The Python report uses snake_case; JavaScript exposes the same data in camelCase.
| Field | Type | Description |
|---|---|---|
warnings | list[ | Unknown input names, deduplicated and sorted by kind and name. |
lower_attributes | Lower | LowerAttributes activity, summed across its two passes. |
rewrite | Rewrite | Rewrite iterations and per-rule outcomes. |
finalize_ast | Finalize | Prime merges and text normalizations. |
flatten_groups | Flatten | Flatten actions and guard hits. |
TransformWarningdict type#
An unknown input name whose arguments or scope cannot be guaranteed.
| Field | Type |
|---|---|
kind | Literal[ |
name | str |
message | str |
LowerAttributesReportdict type#
The LowerAttributes phase report.
| Field | Type | Description |
|---|---|---|
attributes | list[ | Per-attribute activity, sorted by |
eliminated_empty_segments | int | Empty attribute segments removed by the phase. |
AttributeStatdict type#
Activity for one (attr, value) pair in LowerAttributes.
| Field | Type | Description |
|---|---|---|
attr | str | Stable attribute name, such as |
value | str | Stable value token for that attribute. |
consumed | Attribute | Input forms consumed by the phase. |
redundant | Attribute | Consumed forms that did not change the emitted attribute state. |
emitted | Attribute | Canonical forms emitted by the phase. |
AttributeFormCountsdict type#
Counts of one attribute form, split by carrier.
| Field | Type | Description |
|---|---|---|
declaratives | int | Count carried by declarative markup such as |
prefixes | int | Count carried by prefix commands such as |
RewriteReportdict type#
The Rewrite phase report.
| Field | Type | Description |
|---|---|---|
iterations | int | Fixed-point passes, including the final unchanged convergence check. Zero when the phase is disabled or no rules are scheduled. |
rules | list[ | Rules that recorded activity, sorted by |
RewriteRuleStatdict type#
One rewrite rule's outcome for a single call.
| Field | Type | Description |
|---|---|---|
key | str | Stable rule key. |
applied_count | int | Times the rule applied. |
skipped_count | int | Times the scheduler matched the rule and |
FinalizeAstReportdict type#
The FinalizeAst phase report.
| Field | Type | Description |
|---|---|---|
prime_run_merges | int | Contiguous |
text_normalizations | int | Text segments or text argument slots whose content changed. |
FlattenGroupsReportdict type#
The FlattenGroups phase report.
| Field | Type | Description |
|---|---|---|
actions | Flatten | Flatten actions that changed the tree. |
guard_hits | Flatten | Preserve-guard hits for this call. |
FlattenGroupsActionCountsdict type#
Flatten actions that changed the tree.
| Field | Type | Description |
|---|---|---|
removed_empty | int | Empty groups removed. |
replaced_single_child | int | Groups replaced by their only child. |
inlined_multi_child | int | Multi-child groups inlined into the parent. |
unwrapped_slot | int | Slot wrappers removed. |
FlattenGroupsGuardCountsdict type#
Preserve-guard hits for one FlattenGroups run.
A hit is the first guard branch reached for a group, plus the scripted-base
sub-count. command_contact_via_scripted_base is also included in
command_contact. These counters are not a partition of preserved groups.
| Field | Type | Description |
|---|---|---|
unknown_command_arguments | int | Possible arguments following an unknown command. |
argument_capture | int | Groups whose removal would let a command before or inside them read the next token as an omitted optional or delimited argument. |
declarative_scope | int | Declarative-scope protection. |
script_base | int | Script-base protection. |
env_body | int | Environment-body protection. |
infix_scope | int | Infix-scope protection. |
command_contact | int | Command-contact protection. |
command_contact_via_scripted_base | int | Command contact established through a scripted base. |
empty_group | int | Empty-group protection. |
lone_atom_spacing_char | int | Single spacing-character protection. |
leading_atom_spacing_char | int | Leading spacing-character protection. |
delimited_pair | int | Delimited-pair protection. |
TransformProfiletype#
Literal['authoring', 'faithful', 'corpus', 'equiv']Normalization profile name: "authoring", "faithful", "corpus", or "equiv".