Architecture#
This page follows one call, HandCalcs(renderer)(source), from source code to output.
Each step links to the code that does it.
1. Run the code#
If no namespace is supplied, the interface runs the source with exec and keeps the
resulting globals (HandCalcs.__call__).
In Jupyter the notebook has already run the cell, and the decorator runs the function
with innerscope. Either way the result is a dictionary of values.
2. Parse into nodes#
HcSequence.from_source turns
the source into a tree:
AST_Parserwalks Python’sastand builds handcalcs nodes: dataclasses such asCalcLine,NameandDivOp. EveryNameis given its value from the namespace. Comments are classified as headings, comment lines, inline comments or commands (comment_parser), and# hc:flags are parsed withargparse.convert_if_treeturns eachif/elif/elsechain into anElifBlockofIfBlocks (and anElseBlock).group_linesgathers consecutive calculation lines into aCalcsBlockand consecutive comment lines into aCommentsBlock, so a renderer can treat a run of lines as one group (one LaTeXalignedenvironment, one HTML paragraph).set_levelsrecords each node’s nesting depth on itslevelattribute.
The root of the tree is an HcSequence whose sequence holds the top-level nodes. See
Nodes for every node type.
3. Create the render context#
renderer.create_context(**settings) builds a
BaseRenderContext from the global
configuration (and, for HandCalcs, a global_ns field holding the namespace). It has a
global layer, for settings that persist, and a line layer, for settings that apply
to the current line only. See The render context.
4. Render each node#
renderer.render(root, context) calls
render_root, which renders each
top-level node with
render_node. For one node,
render_node:
runs the node type’s
prerules (each may return a changed node);runs its
symornumrules, depending oncontext.current_mode;calls the node type’s handler, which returns the rendered piece, and usually calls
renderer.render(child, context)for the node’s children;runs its
postrules on the rendered piece.
Handlers and rules are looked up by the node’s type string in tables that
register fills. A calculation line is rendered twice per
sub-expression: once with current_mode = "sym" (names as symbols) and once with
"num" (names as values), by render_calcline.
The result is the render tree, a nested list mirroring the node tree. With the
built-in handlers, a line becomes a list of strings and a block becomes
[header, body]; see The render tree.
5. Complete#
renderer.complete(tree, context) produces the final output. Text renderers call
join, which walks the tree with
join_item and inserts spaces,
newlines and indentation. Handlers deliberately leave all of that out, so each renderer
decides its own layout in one place.
BaseRenderer.complete returns the tree unchanged; that is what you see from the
base renderer.
Where the code lives#
Step |
Module |
|---|---|
Interfaces |
|
Parsing |
|
Nodes |
|
Rendering core |
|
Built-in renderers |
|
Configuration |
Next: Nodes.