The render context#

Every handler and rule receives base_context, a BaseRenderContext. It carries the settings for the render, and lets handlers pass information to each other.

Two layers#

base_context.global_context

A RenderContext built by create_context from the configuration. Settings here last for the rest of the render. A standalone comment command (# hc: -f .2f on its own line) writes here.

base_context.line_context

A sparse RenderContext holding only the overrides for the current line. An inline command (x = a / b  # hc: -f .2f) writes here, and the line handlers reset it to empty when the line is finished.

base_context.current

The two layers merged, line over global, as a new RenderContext. Read settings from it.

def my_rule(renderer, node, base_context):
    context = base_context.current
    if context.format_code == ".2f": ...

Warning

base_context.current is a fresh copy each time. Writing to it has no effect. To set a value, write to base_context.line_context (for this line) or base_context.global_context (for the rest of the render).

A RenderContext is a plain object: fields are attributes, and any keyword given to it becomes one. Read fields that may be missing with getattr(context, "name", default).

current_mode#

The handlers for lines set current_mode on the line context before rendering an expression:

  • "sym": render names as symbols (their identifiers);

  • "num": render names as values.

render_calcline renders the assignment targets and the expression in sym mode, then the expression and the targets again in num mode, collecting the columns. The name handler reads current_mode to decide what to show, and render_node uses it to choose between sym and num rules.

If you write a handler for a line or block that renders expressions, set current_mode before rendering them, as the built-in ones do:

base_context.line_context.current_mode = "sym"
symbolic = "".join(renderer.render(n, base_context) for n in node.expression_tree)

Fields#

The fields the built-in renderers use are listed in Settings and the render context. In addition:

Field

Set by

Meaning

global_ns

HandCalcs

The namespace values came from (used for {{ }} in comments)

current_mode

line handlers

"sym" or "num" (see above)

param_line

the calc_line:pre rule

This line is a parameter: show target = value

line_break

# hc: -b

(consumed by render_comment_command; never stored)

How comment commands reach the context#

  1. The parser stores a command’s settings as a dict on CommentCommand.commands (a standalone command) or InlineCommand.commands (at the end of a line). Flags become their long names (-f → format_code); the assignment form keeps your names.

  2. render_comment_command copies a standalone command’s settings onto the global context.

  3. render_inline_command replaces the line context with the inline command’s settings. A calculation line renders its comment first, so the settings apply to the rest of that line.

So your own fields work the same way: # hc: highlight = True sets highlight on the global context, and a rule reads getattr(base_context.current, "highlight", False).

Setting defaults in create_context#

Override create_context to give your renderer its own defaults, while still letting the configuration override them:

class MyRenderer(BaseRenderer):
    def create_context(self, **kwargs):
        settings = {"indent": "  ", "assign": ":="} | kwargs
        return BaseRenderContext(RenderContext(**settings), RenderContext.sparse())

(BaseRenderContext and RenderContext are in handcalcs.renderers.base.) Merging with | like this avoids the TypeError the built-in LaTeX renderer raises when a configured field collides with one it sets itself.

Keeping state for a render#

The global context is created fresh for each render, so it is a safe place to keep state that handlers share during one render: counters, a table of names, collected footnotes. The Excel renderer keeps its formula builder and symbol table there (excel_state). Use a name that won’t collide with a setting:

def counter(base_context):
    gc = base_context.global_context
    gc._equation_number = getattr(gc, "_equation_number", 0) + 1
    return gc._equation_number

Next: Overridable methods.