Module refinery.lib.scripts.xlm
The XLM macro-language layer over the formula substrate of refinery.lib.excel: the
macrosheet model, the name table, the workbook view the interpreter reads, the command
registry with its severity classes, and the environment of answers the macro commands read.
Expand source code Browse git
"""
The XLM macro-language layer over the formula substrate of `refinery.lib.excel`: the
macrosheet model, the name table, the workbook view the interpreter reads, the command
registry with its severity classes, and the environment of answers the macro commands read.
"""
from __future__ import annotations
from refinery.lib.scripts.xlm.commands import XLM_COMMANDS, XlmSeverity, severity
from refinery.lib.scripts.xlm.deobfuscation import deobfuscate
from refinery.lib.scripts.xlm.engine import XlmEngine
from refinery.lib.scripts.xlm.environment import XlmEnvironment
from refinery.lib.scripts.xlm.evaluate import evaluate_expression
from refinery.lib.scripts.xlm.model import XlmCell, XlmMacrosheet, build_xlm_model
from refinery.lib.scripts.xlm.names import XlmNameEntry, XlmNameTable
from refinery.lib.scripts.xlm.references import (
XlmArrival,
XlmCursor,
expand_range,
resolve_reference,
)
from refinery.lib.scripts.xlm.trace import XlmStatus, XlmStep, visible_steps
from refinery.lib.scripts.xlm.values import (
XlmOutcome,
XlmReference,
XlmValue,
apply_binary,
concat,
condition,
unwrap_literal,
wrap_literal,
)
from refinery.lib.scripts.xlm.view import XlmView
__all__ = [
'XLM_COMMANDS',
'XlmArrival',
'XlmCell',
'XlmCursor',
'XlmEngine',
'XlmEnvironment',
'XlmMacrosheet',
'XlmNameEntry',
'XlmNameTable',
'XlmOutcome',
'XlmReference',
'XlmSeverity',
'XlmStatus',
'XlmStep',
'XlmValue',
'XlmView',
'apply_binary',
'build_xlm_model',
'concat',
'condition',
'deobfuscate',
'evaluate_expression',
'expand_range',
'resolve_reference',
'severity',
'unwrap_literal',
'visible_steps',
'wrap_literal',
]
Sub-modules
refinery.lib.scripts.xlm.blocks-
The block structure of a column of the macro language: which
ELSE,ELSE.IF, andEND.IFmarkers belong to which one-argument blockIF, computed … refinery.lib.scripts.xlm.commands-
The command registry of the XLM macro language: the names the language knows, each with the severity that decides at which output level a trace shows it.
refinery.lib.scripts.xlm.deobfuscation-
The static deobfuscation of an XLM program: the passes that clean the macrosheet model a listing starts from. The constant folding replaces what the …
refinery.lib.scripts.xlm.engine-
The engine of the emulator: it owns the mutable state of a run — the coordinate indexes the cell reads and the row fall-through consult, the values …
refinery.lib.scripts.xlm.environment-
The environment of answers the macro commands of an XLM workbook read: fixed workspace and window tables, and the document answers computed from the …
refinery.lib.scripts.xlm.evaluate-
The expression evaluator of the emulator: it walks one formula
Expressionof the model to theXlmValuethe macro language computes from it, … refinery.lib.scripts.xlm.guess-
The guess of the day of the month a program runs under: the one answer a DAY command cannot read from its workbook, searched by running the whole …
refinery.lib.scripts.xlm.handlers-
The handler table of the emulator: one handler per macro command the interpreter answers, drawn from the per-category registries, with the guarantee …
refinery.lib.scripts.xlm.memory-
The emulated state the system commands of the macro language touch: the files
FOPENopens andFWRITEappends to, and the memory regions the … refinery.lib.scripts.xlm.model-
The cell model of the XLM macro language: one
XlmMacrosheetscript per macrosheet of a workbook and oneXlmCellstatement per cell, built from the … refinery.lib.scripts.xlm.names-
The name table of an XLM workbook: the defined names the format stores, keyed for the case-insensitive lookup the macro language performs and resolved …
refinery.lib.scripts.xlm.references-
The resolution of the reference nodes of the formula model against the cell a formula sits in: the cursor of the running engine turns the offsets of …
refinery.lib.scripts.xlm.trace-
The trace the emulator produces: one step per executed cell, with the status of the cell and the output level that decides which steps a trace shows.
refinery.lib.scripts.xlm.values-
The value algebra of the XLM macro language: the values the emulator computes, the quoting the trace spells them with, and the operations the …
refinery.lib.scripts.xlm.view-
The workbook view of an XLM macro language program: the one object the interpreter is handed, combining the macrosheet model with the worksheet data …
Functions
def apply_binary(operator, left, right)-
The value of a binary operation. A partial operand leaves the operation unevaluated, spelled as its two operands around the operator, and an operand that is an error value is the value of the whole operation. Complete operands apply the operator table to numbers, compare the moments an ISO-spelled text names, and compare anything else as text. A division by zero degrades to
#DIV/0!, a result no finite number holds to#NUM!, and an arithmetic operand or a coercion that fails to#VALUE!, the errors Excel displays for them.Expand source code Browse git
def apply_binary(operator: XlBinaryOperator, left: XlmValue, right: XlmValue) -> XlmValue: """ The value of a binary operation. A partial operand leaves the operation unevaluated, spelled as its two operands around the operator, and an operand that is an error value is the value of the whole operation. Complete operands apply the operator table to numbers, compare the moments an ISO-spelled text names, and compare anything else as text. A division by zero degrades to `#DIV/0!`, a result no finite number holds to `#NUM!`, and an arithmetic operand or a coercion that fails to `#VALUE!`, the errors Excel displays for them. """ if operator is XlBinaryOperator.CONCAT: return concat(left, right) if operator is XlBinaryOperator.RANGE: return XlmValue( value=F'{left.unwrap()}:{right.unwrap()}', cells=(left, right), partial=left.partial or right.partial, ) if operator is XlBinaryOperator.ISECT or operator is XlBinaryOperator.UNION: separator = ' ' if operator is XlBinaryOperator.ISECT else ',' return XlmValue( value=F'{left.unwrap()}{separator}{right.unwrap()}', partial=True, ) if left.partial or right.partial: fragment = F'{left.unwrap()}{_OPERATOR_SYMBOLS[operator]}{right.unwrap()}' return XlmValue(value=fragment, partial=True) if left.error: return left if right.error: return right operand_left = _operand(left) operand_right = _operand(right) if is_number(operand_left) and is_number(operand_right): function = _OPERATOR_FUNCTIONS[operator] try: return _numeric_result(function(float(operand_left), float(operand_right))) except ZeroDivisionError: return error_value('#DIV/0!') except OverflowError: return error_value('#NUM!') moment_left = left.unwrap_date() moment_right = right.unwrap_date() if moment_left is not None and moment_right is not None: function = _OPERATOR_FUNCTIONS[operator] try: return _numeric_result(function(moment_left, moment_right)) except TypeError: return error_value('#VALUE!') if operator in _ARITHMETIC_OPERATORS: return error_value('#VALUE!') function = _OPERATOR_FUNCTIONS[operator] try: return _numeric_result(function(left.unwrap(), right.unwrap())) except TypeError: return error_value('#VALUE!') def build_xlm_model(workbook)-
One
XlmMacrosheetfor every macrosheet of the workbook, holding the cellssheet_cellsreads from it.Expand source code Browse git
def build_xlm_model(workbook: ExcelWorkbook) -> list[XlmMacrosheet]: """ One `XlmMacrosheet` for every macrosheet of the workbook, holding the cells `sheet_cells` reads from it. """ result: list[XlmMacrosheet] = [] for sheet in workbook.sheets(): if sheet.kind is not SheetKind.MACROSHEET: continue body: list[Statement] = list(sheet_cells(workbook, sheet).values()) result.append(XlmMacrosheet(name=sheet.name, kind=sheet.kind, body=body)) return result def concat(left, right)-
The
&of two values: their joined texts when both are complete, the two texts joined by the operator itself when either side is partial, and the error of the first side that is an error value.Expand source code Browse git
def concat(left: XlmValue, right: XlmValue) -> XlmValue: """ The `&` of two values: their joined texts when both are complete, the two texts joined by the operator itself when either side is partial, and the error of the first side that is an error value. """ if left.partial or right.partial: fragment = F'{left.unwrap()}&{right.unwrap()}' return XlmValue(value=fragment, partial=True) if left.error: return left if right.error: return right joined = left.unwrap() + right.unwrap() return XlmValue(value=joined) def condition(value)-
The truth of a value, as every command that reads a condition reads it: the spellings of the two truth values are theirs, a number holds when it is not zero — by its type, so the text of a number holds nothing — a value that holds nothing answers
FALSE, and a value that is an error value is that error. Any other text is the#VALUE!error Excel answers for a condition that spells no truth value.Expand source code Browse git
def condition(value: XlmValue) -> bool | XlmValue: """ The truth of a value, as every command that reads a condition reads it: the spellings of the two truth values are theirs, a number holds when it is not zero — by its type, so the text of a number holds nothing — a value that holds nothing answers `FALSE`, and a value that is an error value is that error. Any other text is the `#VALUE!` error Excel answers for a condition that spells no truth value. """ if value.error: return value data = value.value if data is None or data == '': return False if isinstance(data, bool): return data if isinstance(data, (int, float)): return data != 0 if isinstance(data, str): lowered = data.lower() if lowered == 'true': return True if lowered == 'false': return False return error_value('#VALUE!') def deobfuscate(view, start_point='')-
Clean the macrosheets of the view in place for a listing: fold every statically computable subtree into the value it computes, then remove the cells no formula leads to, keeping the entry points the run starts from.
Expand source code Browse git
def deobfuscate(view: XlmView, start_point: str = '') -> None: """ Clean the macrosheets of the view in place for a listing: fold every statically computable subtree into the value it computes, then remove the cells no formula leads to, keeping the entry points the run starts from. """ fold(view) sweep(view, start_point) def evaluate_expression(engine, node, cursor)-
The value one formula node computes. A function call contributes only its value — the jump a control command asks for is a question of the step the engine is running, not of the expression it reads — and it is nested, because the expression reads it rather than a step running it. A node the evaluator does not know computes nothing.
Expand source code Browse git
def evaluate_expression( engine: XlmEngine, node, cursor: XlmCursor, ) -> XlmValue: """ The value one formula node computes. A function call contributes only its value — the jump a control command asks for is a question of the step the engine is running, not of the expression it reads — and it is nested, because the expression reads it rather than a step running it. A node the evaluator does not know computes nothing. """ if node is None: return XlmValue() if isinstance(node, XlNumber): return XlmValue(value=node.value) if isinstance(node, XlString): return XlmValue(value=node.value) if isinstance(node, XlBoolean): return XlmValue(value=node.value) if isinstance(node, XlError): return error_value(node.value) if isinstance(node, XlParenExpression): return evaluate_expression(engine, node.operand, cursor) if isinstance(node, XlUnaryExpression): return _evaluate_unary(engine, node, cursor) if isinstance(node, XlBinaryExpression): if node.operator is XlBinaryOperator.RANGE: return _evaluate_range(engine, node, cursor) left = _scalar(engine, evaluate_expression(engine, node.left, cursor), cursor) right = _scalar(engine, evaluate_expression(engine, node.right, cursor), cursor) return apply_binary(node.operator, left, right) if isinstance(node, (XlA1Reference, XlR1C1Reference)): return engine.read_reference(resolve_reference(node, cursor), cursor) if isinstance(node, XlDefinedName): return engine.resolve_name(node, cursor) if isinstance(node, XlFunctionCall): return engine.call(node, cursor, nested=True).value if isinstance(node, XlArrayConstant): cells = tuple( evaluate_expression(engine, literal, cursor) for row in node.rows for literal in row ) return XlmValue(cells=cells) if isinstance(node, XlMissingArgument): return XlmValue() if isinstance(node, XlUnparsedFormula): return XlmValue(value=node.text, partial=True) return XlmValue() def expand_range(top_left, bottom_right, guard=None)-
Every cell address of the rectangle two corners span, in row-major order. The sheet of each address is the sheet of the top left corner. The guard a caller passes runs every so many addresses, so that a walk over a rectangle the workbook barely fills still answers to the deadline of the run.
Expand source code Browse git
def expand_range( top_left: XlmReference, bottom_right: XlmReference, guard: Callable[[], None] | None = None, ) -> Iterator[XlmReference]: """ Every cell address of the rectangle two corners span, in row-major order. The sheet of each address is the sheet of the top left corner. The guard a caller passes runs every so many addresses, so that a walk over a rectangle the workbook barely fills still answers to the deadline of the run. """ row_min = min(top_left.row, bottom_right.row) row_max = max(top_left.row, bottom_right.row) col_min = min(top_left.col, bottom_right.col) col_max = max(top_left.col, bottom_right.col) walked = 0 for row in range(row_min, row_max + 1): for col in range(col_min, col_max + 1): walked += 1 if guard is not None and not walked % _GUARD_YIELDS: guard() yield XlmReference(top_left.sheet, row, col) def resolve_reference(node, cursor)-
The address a reference node names, resolved against the cursor. The numbers of an A1 reference are absolute positions already; the number of a relative axis of an R1C1 reference is an offset from the cursor on that axis. A reference qualified by a 3-D span of two sheets names the first sheet of the span.
Expand source code Browse git
def resolve_reference( node: XlA1Reference | XlR1C1Reference, cursor: XlmCursor, ) -> XlmReference: """ The address a reference node names, resolved against the cursor. The numbers of an A1 reference are absolute positions already; the number of a relative axis of an R1C1 reference is an offset from the cursor on that axis. A reference qualified by a 3-D span of two sheets names the first sheet of the span. """ if isinstance(node, XlR1C1Reference): row = cursor.row + node.row if node.relative_row else node.row col = cursor.col + node.col if node.relative_col else node.col else: row = node.row col = node.col sheet = node.sheets[0] if node.sheets else None return XlmReference(sheet, row, col) def severity(command_name)-
The severity of a macro command; a name outside the registry is ordinary.
Expand source code Browse git
def severity(command_name: str) -> XlmSeverity: """ The severity of a macro command; a name outside the registry is ordinary. """ return XLM_COMMANDS.get(command_name, XlmSeverity.NORMAL) def unwrap_literal(text)-
The text of a quoted string literal without its quotes; a text that carries no quotes keeps its spelling, and a doubled quote inside the literal spells one quote.
Expand source code Browse git
def unwrap_literal(text: str) -> str: """ The text of a quoted string literal without its quotes; a text that carries no quotes keeps its spelling, and a doubled quote inside the literal spells one quote. """ if len(text) > 1 and text.startswith('"') and text.endswith('"'): return text[1:-1].replace('""', '"') return text def visible_steps(steps, level)-
The steps a trace at the given output level shows: a step the interpreter ignored never shows, at the string-extraction level only the triage commands show and only the quoted string literals their text carries, and otherwise a step shows when its own level reaches the requested one.
Expand source code Browse git
def visible_steps(steps, level: int): """ The steps a trace at the given output level shows: a step the interpreter ignored never shows, at the string-extraction level only the triage commands show and only the quoted string literals their text carries, and otherwise a step shows when its own level reaches the requested one. """ for step in steps: if step.status is XlmStatus.IGNORED: continue if level >= 3 and step.severity is XlmSeverity.IMPORTANT: strings = [match.group(0) for match in _QUOTED_TEXT.finditer(step.text)] if strings: yield step._replace(text='\n'.join(strings)) elif step.severity.value >= level: yield step def wrap_literal(data, must_wrap=False)-
The spelling the trace gives a value: a number, a boolean, and a text that already carries quotes keep their own spelling — the quoted text only while the caller does not insist on quotes — and every other text is quoted with its quotes doubled.
Expand source code Browse git
def wrap_literal(data: Any, must_wrap: bool = False) -> str: """ The spelling the trace gives a value: a number, a boolean, and a text that already carries quotes keep their own spelling — the quoted text only while the caller does not insist on quotes — and every other text is quoted with its quotes doubled. """ if isinstance(data, bool): return 'TRUE' if data else 'FALSE' if is_number(data) or ( isinstance(data, str) and len(data) > 1 and data.startswith('"') and data.endswith('"') and not must_wrap ): return str(data) if isinstance(data, float) and data.is_integer(): return str(int(data)) if isinstance(data, int): return str(data) return F'"{str(data).replace(chr(34), chr(34) * 2)}"'
Classes
class XlmArrival (*args, **kwds)-
How the engine reached the cell of the step it runs: by the jump a command asked for, by the row fall-through or the continuation below the cell a return hands its value to, by resuming a branch of a partial
IFor the anchor a run or a call starts at, or by the replay of a false branch onto the marker of its block. A marker that carries an arm — anELSE, or anELSE.IFthat names a condition — runs it for a jump and for a replay, and skips to theEND.IFof the block for a fall — an arm that ran to its end, or the cell below one a macro call returned to — and for a resume, which an empty true arm and an anchor a run or a call starts at arrive by.Expand source code Browse git
class XlmArrival(enum.Enum): """ How the engine reached the cell of the step it runs: by the jump a command asked for, by the row fall-through or the continuation below the cell a return hands its value to, by resuming a branch of a partial `IF` or the anchor a run or a call starts at, or by the replay of a false branch onto the marker of its block. A marker that carries an arm — an `ELSE`, or an `ELSE.IF` that names a condition — runs it for a jump and for a replay, and skips to the `END.IF` of the block for a fall — an arm that ran to its end, or the cell below one a macro call returned to — and for a resume, which an empty true arm and an anchor a run or a call starts at arrive by. """ JUMP = 0 FALL = 1 RESUME = 2 REPLAY = 3Ancestors
- enum.Enum
Class variables
var JUMP-
The type of the None singleton.
var FALL-
The type of the None singleton.
var RESUME-
The type of the None singleton.
var REPLAY-
The type of the None singleton.
class XlmCell (offset=-1, parent=None, leading_comments=<factory>, trailing_comments=<factory>, row=0, col=0, kind=CellKind.BLANK, assignment=False, formula=None, value=None)-
A single cell at a one-based
rowandcol. Theformulais the decoded expression tree, the unparsed carrier for a formula the reader could not decode, orNonefor a cell that holds only a value;valueis the cached value as the reader spelled it, of thekindthe reader gave it;assignmentmarks a formula the container says assigns to a name.Expand source code Browse git
@dataclass(repr=False, eq=False) class XlmCell(Statement): """ A single cell at a one-based `row` and `col`. The `formula` is the decoded expression tree, the unparsed carrier for a formula the reader could not decode, or `None` for a cell that holds only a value; `value` is the cached value as the reader spelled it, of the `kind` the reader gave it; `assignment` marks a formula the container says assigns to a name. """ row: int = 0 col: int = 0 kind: CellKind = CellKind.BLANK assignment: bool = False formula: Expression | None = None value: str | int | float | bool | None = NoneAncestors
Instance variables
var row-
The type of the None singleton.
var col-
The type of the None singleton.
var kind-
The type of the None singleton.
var assignment-
The type of the None singleton.
var formula-
The type of the None singleton.
var value-
The type of the None singleton.
Inherited members
class XlmCursor (sheet, row, col)-
The cell the engine is currently evaluating: the sheet it sits on with its one-based row and column. The relative references of the formula in this cell resolve against it.
Expand source code Browse git
class XlmCursor(NamedTuple): """ The cell the engine is currently evaluating: the sheet it sits on with its one-based row and column. The relative references of the formula in this cell resolve against it. """ sheet: str row: int col: int def reference(self) -> XlmReference: """ The address of the cursor's own cell. """ return XlmReference(None, self.row, self.col)Ancestors
- builtins.tuple
Instance variables
var sheet-
Alias for field number 0
Expand source code Browse git
class XlmCursor(NamedTuple): """ The cell the engine is currently evaluating: the sheet it sits on with its one-based row and column. The relative references of the formula in this cell resolve against it. """ sheet: str row: int col: int def reference(self) -> XlmReference: """ The address of the cursor's own cell. """ return XlmReference(None, self.row, self.col) var row-
Alias for field number 1
Expand source code Browse git
class XlmCursor(NamedTuple): """ The cell the engine is currently evaluating: the sheet it sits on with its one-based row and column. The relative references of the formula in this cell resolve against it. """ sheet: str row: int col: int def reference(self) -> XlmReference: """ The address of the cursor's own cell. """ return XlmReference(None, self.row, self.col) var col-
Alias for field number 2
Expand source code Browse git
class XlmCursor(NamedTuple): """ The cell the engine is currently evaluating: the sheet it sits on with its one-based row and column. The relative references of the formula in this cell resolve against it. """ sheet: str row: int col: int def reference(self) -> XlmReference: """ The address of the cursor's own cell. """ return XlmReference(None, self.row, self.col)
Methods
def reference(self)-
The address of the cursor's own cell.
Expand source code Browse git
def reference(self) -> XlmReference: """ The address of the cursor's own cell. """ return XlmReference(None, self.row, self.col)
class XlmEngine (view, output_level=0, day=-1, timeout=0, max_steps=1000000, *, cells=None)-
The interpreter of a workbook view. The macrosheet model is read through coordinate indexes the engine maintains itself, so that cells the program writes at run time stay visible to every reference that reads them afterwards; every write lands in an undo journal that a false branch of a partial
IFrolls back from. The start point and the deadline of the latest run are kept on the engine, because the trial runs of the day guess share them.Expand source code Browse git
class XlmEngine: """ The interpreter of a workbook view. The macrosheet model is read through coordinate indexes the engine maintains itself, so that cells the program writes at run time stay visible to every reference that reads them afterwards; every write lands in an undo journal that a false branch of a partial `IF` rolls back from. The start point and the deadline of the latest run are kept on the engine, because the trial runs of the day guess share them. """ def __init__( self, view, output_level: int = 0, day: int = -1, timeout: int = 0, max_steps: int = 1_000_000, *, cells: _CellIndex | None = None, ): self.view = view self.output_level = output_level self.day = day self.timeout = timeout self.max_steps = max_steps self.start_point = '' self.deadline: float | None = None self.aliases: dict[str, str] = {} self.indent_level = 0 self.indent_current_line = False self.arrival = XlmArrival.JUMP self.now_count = 0 self.char_errors = 0 self.iserror_at: XlmCursor | None = None self.iserror_flag = False self.iserror_repeats = 0 self.files = XlmFiles() self.memory = XlmMemory() self.environment = XlmEnvironment() self.active_cell: XlmReference | None = None self.failed_writes: set[XlmReference] = set() self.call_stack: list[XlmCursor | XlmReturnSlot] = [] self.branch_stack: list[XlmFrame] = [] self.while_stack: list[XlmLoop] = [] self._index = _CellIndex(view) if cells is None else cells self._journal: list[_Undo] = [] self._evaluating: set[int] = set() self._resolving: set[tuple[str, int | None]] = set() self._buffers: list[list[XlmStep]] = [] self._depth = 0 self._steps = 0 self._reads = 0 self._halted = False @property def ignore_processing(self) -> bool: """ Whether the engine skips the body of a loop: true while any open loop does not hold. """ return any(not loop.holds for loop in self.while_stack) def run(self, start_point: str = '') -> Iterator[XlmStep]: """ The trace of running the program: one step per executed cell. The entry points are the defined names that fuzzy-spell `auto_open` or `auto_close`; without one, the start point a caller named is the only entry. The run of an entry that exhausts its step budget ends with a step that says so, and a run that exhausts its deadline ends the same way and runs no further entry. An engine without a timeout of its own keeps the deadline it was given. """ self.start_point = start_point if self.timeout > 0: self.deadline = time.monotonic() + self.timeout return self._run() def trial(self, day: int) -> XlmEngine: """ A fresh engine for one trial run of the day guess: it shares this engine's view and the index over its cells — the cells, values, and names the runs so far have written — and the deadline of this engine's run, and answers the given day, but starts with no files, no memory, and no registered names of its own. """ trial = XlmEngine( self.view, self.output_level, day, 0, self.max_steps, cells=self._index, ) trial.deadline = self.deadline return trial def read_reference(self, reference: XlmReference, cursor: XlmCursor) -> XlmValue: """ The value a cell address holds. The sheet the reference fails to name is the sheet the cursor sits on. A cell the run left a value in — by a step that executed it, a value command, or a return — holds that value. Any other formula cell is evaluated with itself as the cursor its relative references resolve against, unless its evaluation is already under way, the reads of the running evaluation exhausted their budget, or it nests too deeply; such a cell, and a cell that holds only a value, reads as the value it was stored with, with the date flag a date cell carries. An address the workbook does not hold is blank. """ sheet = reference.sheet or cursor.sheet reference = XlmReference(sheet, reference.row, reference.col) cell = self._find_cell(sheet, reference.row, reference.col) if cell is None: if reference in self.failed_writes: address = F'{column_letters(reference.col)}{reference.row}' return XlmValue(value=address, partial=True) return _blank(reference) held = self._index.held.get(id(cell)) if held is not None: return dataclasses.replace(held, reference=reference) if not self._evaluating: self._reads = 0 if ( cell.formula is not None and id(cell) not in self._evaluating and self._reads < _READ_BUDGET ): self.check_deadline() self._reads += 1 self._evaluating.add(id(cell)) try: value = evaluate_expression( self, cell.formula, XlmCursor(sheet, cell.row, cell.col), ) except RecursionError: return self._stored(cell, reference) finally: self._evaluating.discard(id(cell)) value.reference = reference return value return self._stored(cell, reference) def resolve_name(self, node: XlDefinedName, cursor: XlmCursor) -> XlmValue: """ The value a defined name stands for: the formula the entry for it holds, evaluated through the evaluator with the scope of the reading cell. A name the workbook does not define, or one whose formula resolves no further, answers its own spelling; a formula that reads the name it defines, or nests too deeply, answers the spelling of the formula. """ entry = self._resolve_entry(node.name, node.sheet, cursor) if entry is None or entry.formula is None: return XlmValue(value=node.name, text=node.name) key = (entry.name.lower(), entry.sheet) if key in self._resolving: return XlmValue(value=synthesize_formula(entry.formula)) self._resolving.add(key) try: return evaluate_expression(self, entry.formula, cursor) except RecursionError: return XlmValue(value=synthesize_formula(entry.formula)) finally: self._resolving.discard(key) def call(self, call: XlFunctionCall, cursor: XlmCursor, nested: bool = False) -> XlmOutcome: """ The outcome of one function call. While the engine skips the body of a loop, every call but the heads and ends of loops is ignored. A call whose callee is itself a call answers what the inner call computed, spelled as the outer one; a registered alias dispatches under the name it was registered for; a callee that names a cell — directly or through a defined name — calls the macro there. A call is nested when an expression reads it instead of a step running it as the whole formula of its cell: a nested macro call runs its subroutine to the value it returns, and a nested command answers through the handler an expression reads it by, where it has one. Once the deadline of the run passed, no call answers. """ self.check_deadline() name = callee_name(call) if self.ignore_processing and name not in _LOOP_COMMANDS: return XlmOutcome( value=XlmValue(value=0, text='', partial=True), status=XlmStatus.IGNORED, ) if isinstance(call.callee, XlFunctionCall): inner = self.call(call.callee, cursor, nested) spelled = synthesize_formula(call) if inner.value.partial: return XlmOutcome(value=XlmValue(value=spelled, partial=True)) inner.value.text = spelled return inner target = self.aliases.get(name) if target is not None: return self.dispatch(target, call, cursor, nested) if isinstance(call.callee, (XlA1Reference, XlR1C1Reference)): reference = resolve_reference(call.callee, cursor) return self._call_cell(reference, cursor, call, nested) if isinstance(call.callee, XlDefinedName): reference = self._name_reference(call.callee, cursor) if reference is not None: return self._call_cell(reference, cursor, call, nested) else: entry = self._resolve_entry(name, None, cursor) reference = self._reference_of(entry.formula if entry else None, cursor) if reference is not None: return self._call_cell(reference, cursor, call, nested) return self.dispatch(name, call, cursor, nested) def dispatch( self, name: str, call: XlFunctionCall, cursor: XlmCursor, nested: bool = False, ) -> XlmOutcome: """ The outcome of a call under a name no alias rewrote. A nested call answers through the handler an expression reads its command by, where the command has one; every other command the handler table holds answers through its handler, and a command without one answers the spelled fallback. """ handler = EXPRESSION_HANDLERS.get(name) if nested else None if handler is None: handler = HANDLERS.get(name) if handler is not None: return handler(self, call, cursor) return self._unknown_command(name, call, cursor) def return_value(self, value: XlmValue) -> tuple[XlmCursor | None, bool]: """ Hand the value a `RETURN` computes to the macro call it returns from: a call that is the whole formula of its cell leaves the value in that cell and continues below it, and a call inside an expression takes the value as its own, which ends the run of the subroutine. A return without a call to return from continues below itself. The answer is the cursor to continue at and whether the run of a subroutine ended. """ if not self.call_stack: return None, False caller = self.call_stack.pop() if isinstance(caller, XlmReturnSlot): caller.value = value return None, True cell = self._find_cell(caller.sheet, caller.row, caller.col) if cell is not None: self.write_value(cell, value) return self.next_formula_cell(caller), False def anchor(self, reference: XlmReference) -> XlmCursor | None: """ The cursor the program continues at from an address: the first formula cell at or below it on the same column of the same macrosheet, or `None` when the sheet is no macrosheet, is not named at all, or holds no formula cell within the row bound. """ sheet = reference.sheet if sheet is None or self.view.macrosheet(sheet) is None: return None row = self._index.fall_through(sheet, reference.col, reference.row) if row is None: return None return XlmCursor(sheet, row, reference.col) def next_formula_cell(self, cursor: XlmCursor) -> XlmCursor | None: """ The cursor the row fall-through moves to: the first formula cell below the given one on the same column, bounded by the row bound. """ row = self._index.fall_through(cursor.sheet, cursor.col, cursor.row + 1) if row is None: return None return XlmCursor(cursor.sheet, row, cursor.col) def column_blocks(self, cursor: XlmCursor) -> dict[int, XlmBlock]: """ The block structure of the column of the cursor: every marker row mapped to the block `IF` it belongs to, paired over the live formula rows of the column, so that cells the program wrote at run time take part in the pairing. """ sheet = cursor.sheet.lower() markers: list[tuple[int, str]] = [] for row in self._index.formula_rows.get(sheet, {}).get(cursor.col, ()): cell = self._index.find(sheet, row, cursor.col) if cell is None or cell.formula is None: continue marker = marker_of(cell.formula) if marker is not None: markers.append((row, marker)) return pair_blocks(markers) def argument_reference( self, call: XlFunctionCall, index: int, cursor: XlmCursor, ) -> XlmReference | None: """ The cell address one argument of a call names, as `XlmEngine.node_reference` reads it. """ if index >= len(call.arguments): return None return self.node_reference(call.arguments[index], cursor) def node_reference(self, node, cursor: XlmCursor) -> XlmReference | None: """ The cell address a node names, with the sheet of a local reference filled in from the cursor: a reference resolves against it, a defined name through the reference its entry holds, and a string literal parses as a formula. Any other expression evaluates to the reference its value carries, or to the address its text spells. """ if isinstance(node, (XlA1Reference, XlR1C1Reference)): return self._local(resolve_reference(node, cursor), cursor) if isinstance(node, XlDefinedName): reference = self._name_reference(node, cursor) if reference is not None: return self._local(reference, cursor) if isinstance(node, XlString): return self._spelled_reference(node.value, cursor) value = evaluate_expression(self, node, cursor) if value.reference is not None: return self._local(value.reference, cursor) if value.partial or not isinstance(value.value, str): return None return self._spelled_reference(value.value, cursor) def range_corners(self, node, cursor: XlmCursor) -> tuple[XlmReference, XlmReference] | None: """ The corners a range argument names, or `None` when the argument is no range. """ if not isinstance(node, XlBinaryExpression) or node.operator is not XlBinaryOperator.RANGE: return None corners = [] for side in (node.left, node.right): reference = self.node_reference(side, cursor) if reference is None: return None corners.append(reference) return corners[0], corners[1] def range_cells(self, corners: tuple[XlmReference, XlmReference]) -> Iterator[XlmReference]: """ The addresses of the cells a range spans that the workbook holds, in row-major order: the cells the index holds on the sheet of the range, the cells of that sheet where it is a worksheet, and the failed writes inside it, which read as the values the program left unfinished. The walk counts the cells the workbook holds, not the coordinates the rectangle spans. """ first, last = corners sheet = first.sheet if sheet is None: return key = sheet.lower() row_min = min(first.row, last.row) row_max = max(first.row, last.row) col_min = min(first.col, last.col) col_max = max(first.col, last.col) held = self._index.cells.get(key, {}) worksheet = self.view.worksheet(sheet) coordinates = set(held) | set(worksheet or ()) coordinates.update( (reference.row, reference.col) for reference in self.failed_writes if (reference.sheet or '').lower() == key ) inside = ( coordinate for coordinate in coordinates if row_min <= coordinate[0] <= row_max and col_min <= coordinate[1] <= col_max ) for row, col in sorted(inside): yield XlmReference(sheet, row, col) def snapshot(self) -> XlmSnapshot: """ The state a false branch of a partial `IF` rolls back to: the position of the journal, and the state of the run the journal does not hold. """ return XlmSnapshot( len(self._journal), tuple(self.call_stack), tuple(loop.copy() for loop in self.while_stack), self.active_cell, ) def state_digest(self) -> bytes: """ The digest of the whole state a run can observe: the cells its index holds, with the value each holds and the formula it spells, the entries of the name table, the registered aliases, the failed-write marks, the content of every open file, every region of memory, and the cell the program selected. A run that returns to the digest it anchored changes nothing the program can see. """ parts: list[str] = [] for sheet in sorted(self._index.cells): cells = self._index.cells[sheet] for row, col in sorted(cells): cell = cells[(row, col)] held = self._index.held.get(id(cell)) value = held.text if held is not None else repr(cell.value) formula = cell.formula spelling = synthesize_formula(formula) if formula is not None else '' parts.append(F'{sheet}!{row}!{col}={value}|{spelling}') for entry in self.view.names.entries(): formula = entry.formula spelling = synthesize_formula(formula) if formula is not None else '' parts.append(F'name:{entry.name.lower()}@{entry.sheet}={spelling}') for name in sorted(self.aliases): parts.append(F'alias:{name}={self.aliases[name]}') for reference in sorted(self.failed_writes): parts.append(F'failed:{reference.a1()}') for name, content in self.files.snapshot(): parts.append(F'file:{name}={content}') for base, data in self.memory.snapshot(): parts.append(F'memory:{base}={len(data)}:{data.hex()}') parts.append(F'active:{self.active_cell}') return hashlib.sha256('\n'.join(parts).encode()).digest() def rollback(self, snapshot: XlmSnapshot) -> None: """ Return to a snapshot: every change the journal holds past its position is undone — cells, names, aliases, files, memory regions, and failed-write marks return to what they carried, and cells the writes created leave their sheet again — and the call stack, the open loops, and the selected cell are what they were. """ while len(self._journal) > snapshot.journal: self._journal.pop().undo(self) self.call_stack = list(snapshot.call_stack) self.while_stack = [loop.copy() for loop in snapshot.loops] self.active_cell = snapshot.active_cell def write_value(self, cell: XlmCell, value: XlmValue) -> None: """ Leave the value a step computed — or a macro call returned — in the cell it belongs to, undoable by a branch that rolls back: a later read of the cell answers that value instead of evaluating the formula of the cell again. """ self._journal.append(_Write( self._index.sheet_of.get(id(cell), ''), cell, cell.value, cell.formula, self._index.held.get(id(cell)), False, )) held = _held(value) set_value(cell, 'value', held.value) self._index.hold(cell, held) def write_cell( self, reference: XlmReference, value: XlmValue, cursor: XlmCursor, value_only: bool = False, ) -> None: """ Write a value into the macrosheet cell an address names, as the mutation commands do, undoable by a branch that rolls back; a cell that does not exist is created. A value-only write changes the value the cell holds and leaves its formula in place. Any other write enters the value the way a typed entry does: a text that starts with `=` installs a formula, which a later read of the cell evaluates, and anything else replaces the formula of the cell by a constant. An address off every macrosheet is not written. """ sheet = reference.sheet or cursor.sheet macrosheet = self.view.macrosheet(sheet) if macrosheet is None: return key = macrosheet.name.lower() cell = self._index.find(key, reference.row, reference.col) created = cell is None if cell is None: cell = XlmCell(row=reference.row, col=reference.col) self._index.insert(key, cell) if macrosheet.body: edit = BodyEdit(macrosheet) edit.splice(macrosheet.body[-1], [macrosheet.body[-1], cell]) edit.apply() else: set_body(macrosheet, [cell]) self._journal.append(_Write( key, cell, cell.value, cell.formula, self._index.held.get(id(cell)), created, )) held: XlmValue | None = _held(value) formula = cell.formula if not value_only: formula = None if isinstance(value.value, str) and value.value.startswith('='): formula = parse_formula(value.value) held = None self._index.update_formula_rows(key, cell, cell.formula is not None, formula is not None) set_value(cell, 'value', value.value if held is None else held.value) set_child(cell, 'formula', formula) self._index.hold(cell, held) def define_name(self, entry: XlmNameEntry) -> None: """ Define the name an entry carries, as the name commands do, undoable by a branch that rolls back. """ self._journal.append(_NameDefine(self.view.names.entries())) self.view.names.define(entry) def assign_name(self, name: str, formula, cursor: XlmCursor) -> None: """ Define a name as `SET.NAME` and the `FOR.CELL` loop do, undoable by a branch that rolls back: the definition replaces the entry a read on the sheet of the cursor resolves the name to, so that the sheet reads back what it assigned, and a name the sheet resolves to no entry is defined for the whole workbook. A name assigned no formula reads as one the workbook does not define. """ entry = self.view.names.resolve(name, self.view.sheet_index(cursor.sheet)) sheet = None if entry is None else entry.sheet self.define_name(XlmNameEntry(name=name, sheet=sheet, formula=formula)) def register_alias(self, name: str, target: str) -> None: """ Register the target a command name dispatches to, as `REGISTER` does, undoable by a branch that rolls back. """ self._journal.append(_AliasRegistration(name, self.aliases.get(name))) self.aliases[name] = target def open_file(self, name: str, access: str = '1') -> None: """ Open the name as a file, as `FOPEN` does, undoable by a branch that rolls back; a name that already answers a file changes nothing. """ if self.files.opened(name): return self._journal.append(_FileOpen(name)) self.files.open(name, access) def write_file(self, name: str, text: str) -> bool: """ Append to a file the program opened, as `FWRITE` does, undoable by a branch that rolls back, reporting whether the file took the write. """ length = self.files.size(name) if length is None: return False self._journal.append(_FileWrite(name, length)) self.files.write(name, text) return True def allocate_memory(self, base: int, size: int) -> int: """ Reserve a region of memory, as `Kernel32.VirtualAlloc` does, undoable by a branch that rolls back. """ self._journal.append(_RegionAlloc()) return self.memory.allocate(base, size) def write_memory(self, base: int, data: bytes, size: int) -> bool: """ Write bytes into a region of memory, as the Kernel32 write commands do, undoable by a branch that rolls back, reporting whether a region took the whole write. """ before = self.memory.peek(base, size) if before is None: return False self.memory.write(base, data, size) self._journal.append(_MemoryWrite(base, before)) return True def mark_failed(self, reference: XlmReference) -> None: """ Mark an address as the destination of a write that never finished, undoable by a branch that rolls back. """ if reference in self.failed_writes: return self._journal.append(_FailedMark(reference, True)) self.failed_writes.add(reference) def unmark_failed(self, reference: XlmReference) -> None: """ Drop the failed-write mark of an address, undoable by a branch that rolls back. """ if reference not in self.failed_writes: return self._journal.append(_FailedMark(reference, False)) self.failed_writes.discard(reference) def _run(self) -> Iterator[XlmStep]: for reference in self.view.entry_points(self.start_point): if self._expired(): return anchor = self.anchor(reference) if anchor is None: continue self._steps = 0 self._halted = False self.while_stack.clear() self.call_stack.clear() yield from self._run_entry(anchor) def _run_entry( self, anchor: XlmCursor, caller: XlmReturnSlot | None = None, ) -> Iterator[XlmStep]: """ The steps of running the program from one anchor until every branch it opened ran out. The run of a subroutine names the call it returns to: its branches are its own, it ends with the first value it returns, and a halt ends it together with the branch of every run that called it, each after its calling step; a budget or deadline it exhausts ends the run that called it at once. The steps of the subroutines a step called precede the step. """ observed: list[tuple[str, int, int]] = [] anchors: dict[tuple[tuple[str, int, int], ...], bytes] = {} digest_step = 0 buffer: list[XlmStep] = [] branches = self.branch_stack indent = 0 if caller is None else self.indent_level self.branch_stack = [XlmFrame(anchor, None, None, indent, '')] self._buffers.append(buffer) cursor = anchor try: while self.branch_stack: frame = self.branch_stack.pop() if frame.snapshot is not None: self.rollback(frame.snapshot) anchors.clear() cursor = frame.cursor node = frame.branch self.indent_level = frame.indent self.arrival = XlmArrival.REPLAY if frame.replays else XlmArrival.RESUME stack_record = True while cursor is not None: self._steps += 1 if self._steps > self.max_steps: raise _Exhausted(F'step budget of {self.max_steps} exhausted') cell = self._find_cell(cursor.sheet, cursor.row, cursor.col) if cell is None: break if node is None: node = cell.formula if node is None: break if stack_record: previous_indent = self.indent_level - 1 if self.indent_level > 0 else 0 else: previous_indent = self.indent_level if caller is None: self._reads = 0 self._evaluating.add(id(cell)) try: with RecursionDepth(TREE_RECURSION_DEPTH): outcome = self._execute(node, cursor) finally: self._evaluating.discard(id(cell)) status = outcome.status if status is None: partial = outcome.value.partial status = ( XlmStatus.PartialEvaluation if partial else XlmStatus.FullEvaluation ) text = outcome.value.text or '' if not self.while_stack and text != 'NEXT': observed.append((cursor.sheet.lower(), cursor.row, cursor.col)) if len(observed) >= _WINDOW_WARMUP: window = tuple(observed[-10:]) if window in anchors: if self._steps - digest_step >= _DIGEST_STEPS: digest_step = self._steps if self.state_digest() == anchors[window]: break else: anchors[window] = self.state_digest() digest_step = self._steps if status is not XlmStatus.IGNORED: self.write_value(cell, outcome.value) if stack_record: text = (frame.desc + ' ' + text).strip() if self.indent_current_line: previous_indent = self.indent_level self.indent_current_line = False yield from buffer buffer.clear() if status is not XlmStatus.IGNORED: yield XlmStep( cursor.sheet, cursor.row, cursor.col, status, text, previous_indent, self._step_severity(node), ) if caller is not None: if outcome.halts: self._halted = True if caller.value is not None or self._halted: return elif self._halted: self._halted = False break self.check_deadline() if outcome.jump is not None: cursor = outcome.jump self.arrival = ( XlmArrival.JUMP if outcome.arrival is None else outcome.arrival ) elif status in _FALL_THROUGH: cursor = self.next_formula_cell(cursor) self.arrival = XlmArrival.FALL else: break node = None stack_record = False except _Exhausted as exhausted: yield from buffer if caller is not None: raise yield self._error_step(cursor or anchor, str(exhausted)) except Exception as error: yield from buffer yield self._error_step(cursor or anchor, F'{type(error).__name__}: {error}') finally: self._buffers.pop() self.branch_stack = branches def _subroutine(self, reference: XlmReference, spelled: str) -> XlmValue: """ The value a macro call inside an expression computes: the subroutine runs from where the call names it until it returns, its steps join the trace ahead of the step that called it, and the value it returns is the value of the call. Calls and loops the subroutine leaves open end with it. A subroutine that never returns — it halts, runs out of formulas, or nests too deeply — leaves the call unfinished. """ anchor = self.anchor(reference) if anchor is None or self._depth >= _SUBROUTINE_DEPTH_LIMIT: return XlmValue(value=spelled, partial=True) caller = XlmReturnSlot() indent = self.indent_level calls = len(self.call_stack) loops = len(self.while_stack) steps: list[XlmStep] = [] self.call_stack.append(caller) self._depth += 1 try: steps.extend(self._run_entry(anchor, caller)) finally: self._depth -= 1 self.indent_level = indent del self.call_stack[calls:] del self.while_stack[loops:] if self._buffers: self._buffers[-1].extend(steps) if caller.value is None: return XlmValue(value=spelled, partial=True) return _held(caller.value) def _expired(self) -> bool: return self.deadline is not None and time.monotonic() > self.deadline def check_deadline(self) -> None: """ Raise the exhaustion of the run's deadline, for the walks a command takes inside one step, so that a walk over a rectangle the workbook barely fills still answers to the deadline of the run. """ if self._expired(): raise _Exhausted('the run exceeded its timeout') def _execute(self, node, cursor: XlmCursor) -> XlmOutcome: """ The outcome of the step that runs a node as the whole formula of its cell. A call runs as a command, and any other expression is evaluated. A step that starts inside the body of a loop the engine skips is ignored like every command there, even when the expression holds the loop end that stops the skipping. """ if isinstance(node, XlFunctionCall): return self.call(node, cursor) skipping = self.ignore_processing value = evaluate_expression(self, node, cursor) if skipping: return XlmOutcome(value=value, status=XlmStatus.IGNORED) return XlmOutcome(value=value) def _stored(self, cell: XlmCell, reference: XlmReference) -> XlmValue: if cell.value is None: return _blank(reference) return XlmValue( value=cell.value, date=cell.kind is CellKind.DATE, reference=reference, ) def _step_severity(self, node) -> XlmSeverity: if isinstance(node, XlFunctionCall) and isinstance(node.callee, str): return severity(node.callee.strip('"')) return XlmSeverity.JUMP def _error_step(self, cursor: XlmCursor, message: str) -> XlmStep: return XlmStep( cursor.sheet, cursor.row, cursor.col, XlmStatus.Error, message, self.indent_level, XlmSeverity.JUMP, ) def _unknown_command(self, name: str, call: XlFunctionCall, cursor: XlmCursor) -> XlmOutcome: """ The fallback for a command no handler answers: the call spelled unevaluated, with every argument evaluated for the side effects it may have had and a missing argument spelling the empty slot the call wrote. """ arguments = [ evaluate_expression(self, argument, cursor).text or '' for argument in call.arguments ] text = F'={name}({",".join(arguments)})' return XlmOutcome(value=XlmValue(value=text, partial=True)) def _call_cell( self, reference: XlmReference, cursor: XlmCursor, call: XlFunctionCall, nested: bool, ) -> XlmOutcome: spelled = synthesize_formula(call) if reference.sheet is None: reference = XlmReference(cursor.sheet, reference.row, reference.col) if self.view.macrosheet(reference.sheet) is None: return XlmOutcome(value=XlmValue(value=0, text=spelled), status=XlmStatus.Error) if nested: return XlmOutcome(value=self._subroutine(reference, spelled)) self.call_stack.append(cursor) return XlmOutcome( value=XlmValue(value=0, text=spelled), jump=self.anchor(reference), arrival=XlmArrival.RESUME, ) def _resolve_entry(self, name: str, sheet: str | None, cursor: XlmCursor): if sheet is not None: return self.view.names.resolve(name, self.view.sheet_index(sheet)) return self.view.names.resolve(name, self.view.sheet_index(cursor.sheet)) def _name_reference(self, node: XlDefinedName, cursor: XlmCursor) -> XlmReference | None: entry = self._resolve_entry(node.name, node.sheet, cursor) if entry is None: return None return self._reference_of(entry.formula, cursor) def _reference_of(self, formula, cursor: XlmCursor) -> XlmReference | None: if isinstance(formula, (XlA1Reference, XlR1C1Reference)): return resolve_reference(formula, cursor) return None def _spelled_reference(self, text: str, cursor: XlmCursor) -> XlmReference | None: return self._local(self._reference_of(parse_formula(text), cursor), cursor) def _local(self, reference: XlmReference | None, cursor: XlmCursor) -> XlmReference | None: if reference is None: return None if reference.sheet is not None: return reference return XlmReference(cursor.sheet, reference.row, reference.col) def _find_cell(self, sheet: str, row: int, col: int) -> XlmCell | None: cell = self._index.find(sheet, row, col) if cell is not None: return cell worksheet = self.view.worksheet(sheet) if worksheet is not None: return worksheet.get((row, col)) return NoneSubclasses
- refinery.lib.scripts.xlm.deobfuscation.fold._StaticEngine
Instance variables
var ignore_processing-
Whether the engine skips the body of a loop: true while any open loop does not hold.
Expand source code Browse git
@property def ignore_processing(self) -> bool: """ Whether the engine skips the body of a loop: true while any open loop does not hold. """ return any(not loop.holds for loop in self.while_stack)
Methods
def run(self, start_point='')-
The trace of running the program: one step per executed cell. The entry points are the defined names that fuzzy-spell
auto_openorauto_close; without one, the start point a caller named is the only entry. The run of an entry that exhausts its step budget ends with a step that says so, and a run that exhausts its deadline ends the same way and runs no further entry. An engine without a timeout of its own keeps the deadline it was given.Expand source code Browse git
def run(self, start_point: str = '') -> Iterator[XlmStep]: """ The trace of running the program: one step per executed cell. The entry points are the defined names that fuzzy-spell `auto_open` or `auto_close`; without one, the start point a caller named is the only entry. The run of an entry that exhausts its step budget ends with a step that says so, and a run that exhausts its deadline ends the same way and runs no further entry. An engine without a timeout of its own keeps the deadline it was given. """ self.start_point = start_point if self.timeout > 0: self.deadline = time.monotonic() + self.timeout return self._run() def trial(self, day)-
A fresh engine for one trial run of the day guess: it shares this engine's view and the index over its cells — the cells, values, and names the runs so far have written — and the deadline of this engine's run, and answers the given day, but starts with no files, no memory, and no registered names of its own.
Expand source code Browse git
def trial(self, day: int) -> XlmEngine: """ A fresh engine for one trial run of the day guess: it shares this engine's view and the index over its cells — the cells, values, and names the runs so far have written — and the deadline of this engine's run, and answers the given day, but starts with no files, no memory, and no registered names of its own. """ trial = XlmEngine( self.view, self.output_level, day, 0, self.max_steps, cells=self._index, ) trial.deadline = self.deadline return trial def read_reference(self, reference, cursor)-
The value a cell address holds. The sheet the reference fails to name is the sheet the cursor sits on. A cell the run left a value in — by a step that executed it, a value command, or a return — holds that value. Any other formula cell is evaluated with itself as the cursor its relative references resolve against, unless its evaluation is already under way, the reads of the running evaluation exhausted their budget, or it nests too deeply; such a cell, and a cell that holds only a value, reads as the value it was stored with, with the date flag a date cell carries. An address the workbook does not hold is blank.
Expand source code Browse git
def read_reference(self, reference: XlmReference, cursor: XlmCursor) -> XlmValue: """ The value a cell address holds. The sheet the reference fails to name is the sheet the cursor sits on. A cell the run left a value in — by a step that executed it, a value command, or a return — holds that value. Any other formula cell is evaluated with itself as the cursor its relative references resolve against, unless its evaluation is already under way, the reads of the running evaluation exhausted their budget, or it nests too deeply; such a cell, and a cell that holds only a value, reads as the value it was stored with, with the date flag a date cell carries. An address the workbook does not hold is blank. """ sheet = reference.sheet or cursor.sheet reference = XlmReference(sheet, reference.row, reference.col) cell = self._find_cell(sheet, reference.row, reference.col) if cell is None: if reference in self.failed_writes: address = F'{column_letters(reference.col)}{reference.row}' return XlmValue(value=address, partial=True) return _blank(reference) held = self._index.held.get(id(cell)) if held is not None: return dataclasses.replace(held, reference=reference) if not self._evaluating: self._reads = 0 if ( cell.formula is not None and id(cell) not in self._evaluating and self._reads < _READ_BUDGET ): self.check_deadline() self._reads += 1 self._evaluating.add(id(cell)) try: value = evaluate_expression( self, cell.formula, XlmCursor(sheet, cell.row, cell.col), ) except RecursionError: return self._stored(cell, reference) finally: self._evaluating.discard(id(cell)) value.reference = reference return value return self._stored(cell, reference) def resolve_name(self, node, cursor)-
The value a defined name stands for: the formula the entry for it holds, evaluated through the evaluator with the scope of the reading cell. A name the workbook does not define, or one whose formula resolves no further, answers its own spelling; a formula that reads the name it defines, or nests too deeply, answers the spelling of the formula.
Expand source code Browse git
def resolve_name(self, node: XlDefinedName, cursor: XlmCursor) -> XlmValue: """ The value a defined name stands for: the formula the entry for it holds, evaluated through the evaluator with the scope of the reading cell. A name the workbook does not define, or one whose formula resolves no further, answers its own spelling; a formula that reads the name it defines, or nests too deeply, answers the spelling of the formula. """ entry = self._resolve_entry(node.name, node.sheet, cursor) if entry is None or entry.formula is None: return XlmValue(value=node.name, text=node.name) key = (entry.name.lower(), entry.sheet) if key in self._resolving: return XlmValue(value=synthesize_formula(entry.formula)) self._resolving.add(key) try: return evaluate_expression(self, entry.formula, cursor) except RecursionError: return XlmValue(value=synthesize_formula(entry.formula)) finally: self._resolving.discard(key) def call(self, call, cursor, nested=False)-
The outcome of one function call. While the engine skips the body of a loop, every call but the heads and ends of loops is ignored. A call whose callee is itself a call answers what the inner call computed, spelled as the outer one; a registered alias dispatches under the name it was registered for; a callee that names a cell — directly or through a defined name — calls the macro there. A call is nested when an expression reads it instead of a step running it as the whole formula of its cell: a nested macro call runs its subroutine to the value it returns, and a nested command answers through the handler an expression reads it by, where it has one. Once the deadline of the run passed, no call answers.
Expand source code Browse git
def call(self, call: XlFunctionCall, cursor: XlmCursor, nested: bool = False) -> XlmOutcome: """ The outcome of one function call. While the engine skips the body of a loop, every call but the heads and ends of loops is ignored. A call whose callee is itself a call answers what the inner call computed, spelled as the outer one; a registered alias dispatches under the name it was registered for; a callee that names a cell — directly or through a defined name — calls the macro there. A call is nested when an expression reads it instead of a step running it as the whole formula of its cell: a nested macro call runs its subroutine to the value it returns, and a nested command answers through the handler an expression reads it by, where it has one. Once the deadline of the run passed, no call answers. """ self.check_deadline() name = callee_name(call) if self.ignore_processing and name not in _LOOP_COMMANDS: return XlmOutcome( value=XlmValue(value=0, text='', partial=True), status=XlmStatus.IGNORED, ) if isinstance(call.callee, XlFunctionCall): inner = self.call(call.callee, cursor, nested) spelled = synthesize_formula(call) if inner.value.partial: return XlmOutcome(value=XlmValue(value=spelled, partial=True)) inner.value.text = spelled return inner target = self.aliases.get(name) if target is not None: return self.dispatch(target, call, cursor, nested) if isinstance(call.callee, (XlA1Reference, XlR1C1Reference)): reference = resolve_reference(call.callee, cursor) return self._call_cell(reference, cursor, call, nested) if isinstance(call.callee, XlDefinedName): reference = self._name_reference(call.callee, cursor) if reference is not None: return self._call_cell(reference, cursor, call, nested) else: entry = self._resolve_entry(name, None, cursor) reference = self._reference_of(entry.formula if entry else None, cursor) if reference is not None: return self._call_cell(reference, cursor, call, nested) return self.dispatch(name, call, cursor, nested) def dispatch(self, name, call, cursor, nested=False)-
The outcome of a call under a name no alias rewrote. A nested call answers through the handler an expression reads its command by, where the command has one; every other command the handler table holds answers through its handler, and a command without one answers the spelled fallback.
Expand source code Browse git
def dispatch( self, name: str, call: XlFunctionCall, cursor: XlmCursor, nested: bool = False, ) -> XlmOutcome: """ The outcome of a call under a name no alias rewrote. A nested call answers through the handler an expression reads its command by, where the command has one; every other command the handler table holds answers through its handler, and a command without one answers the spelled fallback. """ handler = EXPRESSION_HANDLERS.get(name) if nested else None if handler is None: handler = HANDLERS.get(name) if handler is not None: return handler(self, call, cursor) return self._unknown_command(name, call, cursor) def return_value(self, value)-
Hand the value a
RETURNcomputes to the macro call it returns from: a call that is the whole formula of its cell leaves the value in that cell and continues below it, and a call inside an expression takes the value as its own, which ends the run of the subroutine. A return without a call to return from continues below itself. The answer is the cursor to continue at and whether the run of a subroutine ended.Expand source code Browse git
def return_value(self, value: XlmValue) -> tuple[XlmCursor | None, bool]: """ Hand the value a `RETURN` computes to the macro call it returns from: a call that is the whole formula of its cell leaves the value in that cell and continues below it, and a call inside an expression takes the value as its own, which ends the run of the subroutine. A return without a call to return from continues below itself. The answer is the cursor to continue at and whether the run of a subroutine ended. """ if not self.call_stack: return None, False caller = self.call_stack.pop() if isinstance(caller, XlmReturnSlot): caller.value = value return None, True cell = self._find_cell(caller.sheet, caller.row, caller.col) if cell is not None: self.write_value(cell, value) return self.next_formula_cell(caller), False def anchor(self, reference)-
The cursor the program continues at from an address: the first formula cell at or below it on the same column of the same macrosheet, or
Nonewhen the sheet is no macrosheet, is not named at all, or holds no formula cell within the row bound.Expand source code Browse git
def anchor(self, reference: XlmReference) -> XlmCursor | None: """ The cursor the program continues at from an address: the first formula cell at or below it on the same column of the same macrosheet, or `None` when the sheet is no macrosheet, is not named at all, or holds no formula cell within the row bound. """ sheet = reference.sheet if sheet is None or self.view.macrosheet(sheet) is None: return None row = self._index.fall_through(sheet, reference.col, reference.row) if row is None: return None return XlmCursor(sheet, row, reference.col) def next_formula_cell(self, cursor)-
The cursor the row fall-through moves to: the first formula cell below the given one on the same column, bounded by the row bound.
Expand source code Browse git
def next_formula_cell(self, cursor: XlmCursor) -> XlmCursor | None: """ The cursor the row fall-through moves to: the first formula cell below the given one on the same column, bounded by the row bound. """ row = self._index.fall_through(cursor.sheet, cursor.col, cursor.row + 1) if row is None: return None return XlmCursor(cursor.sheet, row, cursor.col) def column_blocks(self, cursor)-
The block structure of the column of the cursor: every marker row mapped to the block
IFit belongs to, paired over the live formula rows of the column, so that cells the program wrote at run time take part in the pairing.Expand source code Browse git
def column_blocks(self, cursor: XlmCursor) -> dict[int, XlmBlock]: """ The block structure of the column of the cursor: every marker row mapped to the block `IF` it belongs to, paired over the live formula rows of the column, so that cells the program wrote at run time take part in the pairing. """ sheet = cursor.sheet.lower() markers: list[tuple[int, str]] = [] for row in self._index.formula_rows.get(sheet, {}).get(cursor.col, ()): cell = self._index.find(sheet, row, cursor.col) if cell is None or cell.formula is None: continue marker = marker_of(cell.formula) if marker is not None: markers.append((row, marker)) return pair_blocks(markers) def argument_reference(self, call, index, cursor)-
The cell address one argument of a call names, as
XlmEngine.node_reference()reads it.Expand source code Browse git
def argument_reference( self, call: XlFunctionCall, index: int, cursor: XlmCursor, ) -> XlmReference | None: """ The cell address one argument of a call names, as `XlmEngine.node_reference` reads it. """ if index >= len(call.arguments): return None return self.node_reference(call.arguments[index], cursor) def node_reference(self, node, cursor)-
The cell address a node names, with the sheet of a local reference filled in from the cursor: a reference resolves against it, a defined name through the reference its entry holds, and a string literal parses as a formula. Any other expression evaluates to the reference its value carries, or to the address its text spells.
Expand source code Browse git
def node_reference(self, node, cursor: XlmCursor) -> XlmReference | None: """ The cell address a node names, with the sheet of a local reference filled in from the cursor: a reference resolves against it, a defined name through the reference its entry holds, and a string literal parses as a formula. Any other expression evaluates to the reference its value carries, or to the address its text spells. """ if isinstance(node, (XlA1Reference, XlR1C1Reference)): return self._local(resolve_reference(node, cursor), cursor) if isinstance(node, XlDefinedName): reference = self._name_reference(node, cursor) if reference is not None: return self._local(reference, cursor) if isinstance(node, XlString): return self._spelled_reference(node.value, cursor) value = evaluate_expression(self, node, cursor) if value.reference is not None: return self._local(value.reference, cursor) if value.partial or not isinstance(value.value, str): return None return self._spelled_reference(value.value, cursor) def range_corners(self, node, cursor)-
The corners a range argument names, or
Nonewhen the argument is no range.Expand source code Browse git
def range_corners(self, node, cursor: XlmCursor) -> tuple[XlmReference, XlmReference] | None: """ The corners a range argument names, or `None` when the argument is no range. """ if not isinstance(node, XlBinaryExpression) or node.operator is not XlBinaryOperator.RANGE: return None corners = [] for side in (node.left, node.right): reference = self.node_reference(side, cursor) if reference is None: return None corners.append(reference) return corners[0], corners[1] def range_cells(self, corners)-
The addresses of the cells a range spans that the workbook holds, in row-major order: the cells the index holds on the sheet of the range, the cells of that sheet where it is a worksheet, and the failed writes inside it, which read as the values the program left unfinished. The walk counts the cells the workbook holds, not the coordinates the rectangle spans.
Expand source code Browse git
def range_cells(self, corners: tuple[XlmReference, XlmReference]) -> Iterator[XlmReference]: """ The addresses of the cells a range spans that the workbook holds, in row-major order: the cells the index holds on the sheet of the range, the cells of that sheet where it is a worksheet, and the failed writes inside it, which read as the values the program left unfinished. The walk counts the cells the workbook holds, not the coordinates the rectangle spans. """ first, last = corners sheet = first.sheet if sheet is None: return key = sheet.lower() row_min = min(first.row, last.row) row_max = max(first.row, last.row) col_min = min(first.col, last.col) col_max = max(first.col, last.col) held = self._index.cells.get(key, {}) worksheet = self.view.worksheet(sheet) coordinates = set(held) | set(worksheet or ()) coordinates.update( (reference.row, reference.col) for reference in self.failed_writes if (reference.sheet or '').lower() == key ) inside = ( coordinate for coordinate in coordinates if row_min <= coordinate[0] <= row_max and col_min <= coordinate[1] <= col_max ) for row, col in sorted(inside): yield XlmReference(sheet, row, col) def snapshot(self)-
The state a false branch of a partial
IFrolls back to: the position of the journal, and the state of the run the journal does not hold.Expand source code Browse git
def snapshot(self) -> XlmSnapshot: """ The state a false branch of a partial `IF` rolls back to: the position of the journal, and the state of the run the journal does not hold. """ return XlmSnapshot( len(self._journal), tuple(self.call_stack), tuple(loop.copy() for loop in self.while_stack), self.active_cell, ) def state_digest(self)-
The digest of the whole state a run can observe: the cells its index holds, with the value each holds and the formula it spells, the entries of the name table, the registered aliases, the failed-write marks, the content of every open file, every region of memory, and the cell the program selected. A run that returns to the digest it anchored changes nothing the program can see.
Expand source code Browse git
def state_digest(self) -> bytes: """ The digest of the whole state a run can observe: the cells its index holds, with the value each holds and the formula it spells, the entries of the name table, the registered aliases, the failed-write marks, the content of every open file, every region of memory, and the cell the program selected. A run that returns to the digest it anchored changes nothing the program can see. """ parts: list[str] = [] for sheet in sorted(self._index.cells): cells = self._index.cells[sheet] for row, col in sorted(cells): cell = cells[(row, col)] held = self._index.held.get(id(cell)) value = held.text if held is not None else repr(cell.value) formula = cell.formula spelling = synthesize_formula(formula) if formula is not None else '' parts.append(F'{sheet}!{row}!{col}={value}|{spelling}') for entry in self.view.names.entries(): formula = entry.formula spelling = synthesize_formula(formula) if formula is not None else '' parts.append(F'name:{entry.name.lower()}@{entry.sheet}={spelling}') for name in sorted(self.aliases): parts.append(F'alias:{name}={self.aliases[name]}') for reference in sorted(self.failed_writes): parts.append(F'failed:{reference.a1()}') for name, content in self.files.snapshot(): parts.append(F'file:{name}={content}') for base, data in self.memory.snapshot(): parts.append(F'memory:{base}={len(data)}:{data.hex()}') parts.append(F'active:{self.active_cell}') return hashlib.sha256('\n'.join(parts).encode()).digest() def rollback(self, snapshot)-
Return to a snapshot: every change the journal holds past its position is undone — cells, names, aliases, files, memory regions, and failed-write marks return to what they carried, and cells the writes created leave their sheet again — and the call stack, the open loops, and the selected cell are what they were.
Expand source code Browse git
def rollback(self, snapshot: XlmSnapshot) -> None: """ Return to a snapshot: every change the journal holds past its position is undone — cells, names, aliases, files, memory regions, and failed-write marks return to what they carried, and cells the writes created leave their sheet again — and the call stack, the open loops, and the selected cell are what they were. """ while len(self._journal) > snapshot.journal: self._journal.pop().undo(self) self.call_stack = list(snapshot.call_stack) self.while_stack = [loop.copy() for loop in snapshot.loops] self.active_cell = snapshot.active_cell def write_value(self, cell, value)-
Leave the value a step computed — or a macro call returned — in the cell it belongs to, undoable by a branch that rolls back: a later read of the cell answers that value instead of evaluating the formula of the cell again.
Expand source code Browse git
def write_value(self, cell: XlmCell, value: XlmValue) -> None: """ Leave the value a step computed — or a macro call returned — in the cell it belongs to, undoable by a branch that rolls back: a later read of the cell answers that value instead of evaluating the formula of the cell again. """ self._journal.append(_Write( self._index.sheet_of.get(id(cell), ''), cell, cell.value, cell.formula, self._index.held.get(id(cell)), False, )) held = _held(value) set_value(cell, 'value', held.value) self._index.hold(cell, held) def write_cell(self, reference, value, cursor, value_only=False)-
Write a value into the macrosheet cell an address names, as the mutation commands do, undoable by a branch that rolls back; a cell that does not exist is created. A value-only write changes the value the cell holds and leaves its formula in place. Any other write enters the value the way a typed entry does: a text that starts with
=installs a formula, which a later read of the cell evaluates, and anything else replaces the formula of the cell by a constant. An address off every macrosheet is not written.Expand source code Browse git
def write_cell( self, reference: XlmReference, value: XlmValue, cursor: XlmCursor, value_only: bool = False, ) -> None: """ Write a value into the macrosheet cell an address names, as the mutation commands do, undoable by a branch that rolls back; a cell that does not exist is created. A value-only write changes the value the cell holds and leaves its formula in place. Any other write enters the value the way a typed entry does: a text that starts with `=` installs a formula, which a later read of the cell evaluates, and anything else replaces the formula of the cell by a constant. An address off every macrosheet is not written. """ sheet = reference.sheet or cursor.sheet macrosheet = self.view.macrosheet(sheet) if macrosheet is None: return key = macrosheet.name.lower() cell = self._index.find(key, reference.row, reference.col) created = cell is None if cell is None: cell = XlmCell(row=reference.row, col=reference.col) self._index.insert(key, cell) if macrosheet.body: edit = BodyEdit(macrosheet) edit.splice(macrosheet.body[-1], [macrosheet.body[-1], cell]) edit.apply() else: set_body(macrosheet, [cell]) self._journal.append(_Write( key, cell, cell.value, cell.formula, self._index.held.get(id(cell)), created, )) held: XlmValue | None = _held(value) formula = cell.formula if not value_only: formula = None if isinstance(value.value, str) and value.value.startswith('='): formula = parse_formula(value.value) held = None self._index.update_formula_rows(key, cell, cell.formula is not None, formula is not None) set_value(cell, 'value', value.value if held is None else held.value) set_child(cell, 'formula', formula) self._index.hold(cell, held) def define_name(self, entry)-
Define the name an entry carries, as the name commands do, undoable by a branch that rolls back.
Expand source code Browse git
def define_name(self, entry: XlmNameEntry) -> None: """ Define the name an entry carries, as the name commands do, undoable by a branch that rolls back. """ self._journal.append(_NameDefine(self.view.names.entries())) self.view.names.define(entry) def assign_name(self, name, formula, cursor)-
Define a name as
SET.NAMEand theFOR.CELLloop do, undoable by a branch that rolls back: the definition replaces the entry a read on the sheet of the cursor resolves the name to, so that the sheet reads back what it assigned, and a name the sheet resolves to no entry is defined for the whole workbook. A name assigned no formula reads as one the workbook does not define.Expand source code Browse git
def assign_name(self, name: str, formula, cursor: XlmCursor) -> None: """ Define a name as `SET.NAME` and the `FOR.CELL` loop do, undoable by a branch that rolls back: the definition replaces the entry a read on the sheet of the cursor resolves the name to, so that the sheet reads back what it assigned, and a name the sheet resolves to no entry is defined for the whole workbook. A name assigned no formula reads as one the workbook does not define. """ entry = self.view.names.resolve(name, self.view.sheet_index(cursor.sheet)) sheet = None if entry is None else entry.sheet self.define_name(XlmNameEntry(name=name, sheet=sheet, formula=formula)) def register_alias(self, name, target)-
Register the target a command name dispatches to, as
REGISTERdoes, undoable by a branch that rolls back.Expand source code Browse git
def register_alias(self, name: str, target: str) -> None: """ Register the target a command name dispatches to, as `REGISTER` does, undoable by a branch that rolls back. """ self._journal.append(_AliasRegistration(name, self.aliases.get(name))) self.aliases[name] = target def open_file(self, name, access='1')-
Open the name as a file, as
FOPENdoes, undoable by a branch that rolls back; a name that already answers a file changes nothing.Expand source code Browse git
def open_file(self, name: str, access: str = '1') -> None: """ Open the name as a file, as `FOPEN` does, undoable by a branch that rolls back; a name that already answers a file changes nothing. """ if self.files.opened(name): return self._journal.append(_FileOpen(name)) self.files.open(name, access) def write_file(self, name, text)-
Append to a file the program opened, as
FWRITEdoes, undoable by a branch that rolls back, reporting whether the file took the write.Expand source code Browse git
def write_file(self, name: str, text: str) -> bool: """ Append to a file the program opened, as `FWRITE` does, undoable by a branch that rolls back, reporting whether the file took the write. """ length = self.files.size(name) if length is None: return False self._journal.append(_FileWrite(name, length)) self.files.write(name, text) return True def allocate_memory(self, base, size)-
Reserve a region of memory, as
Kernel32.VirtualAllocdoes, undoable by a branch that rolls back.Expand source code Browse git
def allocate_memory(self, base: int, size: int) -> int: """ Reserve a region of memory, as `Kernel32.VirtualAlloc` does, undoable by a branch that rolls back. """ self._journal.append(_RegionAlloc()) return self.memory.allocate(base, size) def write_memory(self, base, data, size)-
Write bytes into a region of memory, as the Kernel32 write commands do, undoable by a branch that rolls back, reporting whether a region took the whole write.
Expand source code Browse git
def write_memory(self, base: int, data: bytes, size: int) -> bool: """ Write bytes into a region of memory, as the Kernel32 write commands do, undoable by a branch that rolls back, reporting whether a region took the whole write. """ before = self.memory.peek(base, size) if before is None: return False self.memory.write(base, data, size) self._journal.append(_MemoryWrite(base, before)) return True def mark_failed(self, reference)-
Mark an address as the destination of a write that never finished, undoable by a branch that rolls back.
Expand source code Browse git
def mark_failed(self, reference: XlmReference) -> None: """ Mark an address as the destination of a write that never finished, undoable by a branch that rolls back. """ if reference in self.failed_writes: return self._journal.append(_FailedMark(reference, True)) self.failed_writes.add(reference) def unmark_failed(self, reference)-
Drop the failed-write mark of an address, undoable by a branch that rolls back.
Expand source code Browse git
def unmark_failed(self, reference: XlmReference) -> None: """ Drop the failed-write mark of an address, undoable by a branch that rolls back. """ if reference not in self.failed_writes: return self._journal.append(_FailedMark(reference, False)) self.failed_writes.discard(reference) def check_deadline(self)-
Raise the exhaustion of the run's deadline, for the walks a command takes inside one step, so that a walk over a rectangle the workbook barely fills still answers to the deadline of the run.
Expand source code Browse git
def check_deadline(self) -> None: """ Raise the exhaustion of the run's deadline, for the walks a command takes inside one step, so that a walk over a rectangle the workbook barely fills still answers to the deadline of the run. """ if self._expired(): raise _Exhausted('the run exceeded its timeout')
class XlmEnvironment-
The answers the environment-reading macro commands get from an emulated Excel installation: the workspace and window tables and the two document answers.
Expand source code Browse git
class XlmEnvironment: """ The answers the environment-reading macro commands get from an emulated Excel installation: the workspace and window tables and the two document answers. """ def workspace(self, number: int) -> str | None: """ The workspace-table answer, or `None` for a number the table holds none for; a call the table does not answer reports as fully evaluated to no value. """ return _WORKSPACE.get(number) def window(self, number: int) -> str | int | None: """ The window-table answer, which is also the text a GET.WINDOW trace line shows: for numbers 1 and 30 the table holds the placeholder `[Book1]Sheet1` even though the value the command returns names the workbook and the sheet it was called from. """ return _WINDOW.get(number) def window_value(self, number: int, workbook_name: str, sheet_name: str) -> str | int | None: """ The value the GET.WINDOW command returns: the window-table answer, except that numbers 1 and 30 name the given workbook and sheet. """ if number in (1, 30): return F'[{workbook_name}]{sheet_name}' return _WINDOW.get(number) def document(self, number: int, workbook_name: str, sheet_name: str) -> str | None: """ The document-table answer, which holds two entries: number 76 names the workbook and the given sheet, number 88 names the workbook, and every other number yields a partial evaluation, which `None` stands for here. """ if number == 76: return F'[{workbook_name}]{sheet_name}' if number == 88: return workbook_name return None def cell_info(self, sheet_name: str, row: int, col: int, number: int) -> str | None: """ Not implemented: the style surface a real answer reads — row heights, font sizes and colors, alignment — does not exist, so every GET.CELL call yields nothing. """ return NoneMethods
def workspace(self, number)-
The workspace-table answer, or
Nonefor a number the table holds none for; a call the table does not answer reports as fully evaluated to no value.Expand source code Browse git
def workspace(self, number: int) -> str | None: """ The workspace-table answer, or `None` for a number the table holds none for; a call the table does not answer reports as fully evaluated to no value. """ return _WORKSPACE.get(number) def window(self, number)-
The window-table answer, which is also the text a GET.WINDOW trace line shows: for numbers 1 and 30 the table holds the placeholder
[Book1]Sheet1even though the value the command returns names the workbook and the sheet it was called from.Expand source code Browse git
def window(self, number: int) -> str | int | None: """ The window-table answer, which is also the text a GET.WINDOW trace line shows: for numbers 1 and 30 the table holds the placeholder `[Book1]Sheet1` even though the value the command returns names the workbook and the sheet it was called from. """ return _WINDOW.get(number) def window_value(self, number, workbook_name, sheet_name)-
The value the GET.WINDOW command returns: the window-table answer, except that numbers 1 and 30 name the given workbook and sheet.
Expand source code Browse git
def window_value(self, number: int, workbook_name: str, sheet_name: str) -> str | int | None: """ The value the GET.WINDOW command returns: the window-table answer, except that numbers 1 and 30 name the given workbook and sheet. """ if number in (1, 30): return F'[{workbook_name}]{sheet_name}' return _WINDOW.get(number) def document(self, number, workbook_name, sheet_name)-
The document-table answer, which holds two entries: number 76 names the workbook and the given sheet, number 88 names the workbook, and every other number yields a partial evaluation, which
Nonestands for here.Expand source code Browse git
def document(self, number: int, workbook_name: str, sheet_name: str) -> str | None: """ The document-table answer, which holds two entries: number 76 names the workbook and the given sheet, number 88 names the workbook, and every other number yields a partial evaluation, which `None` stands for here. """ if number == 76: return F'[{workbook_name}]{sheet_name}' if number == 88: return workbook_name return None def cell_info(self, sheet_name, row, col, number)-
Not implemented: the style surface a real answer reads — row heights, font sizes and colors, alignment — does not exist, so every GET.CELL call yields nothing.
Expand source code Browse git
def cell_info(self, sheet_name: str, row: int, col: int, number: int) -> str | None: """ Not implemented: the style surface a real answer reads — row heights, font sizes and colors, alignment — does not exist, so every GET.CELL call yields nothing. """ return None
class XlmMacrosheet (offset=-1, parent=None, leading_comments=<factory>, trailing_comments=<factory>, body=<factory>, name='', kind=SheetKind.MACROSHEET)-
A macrosheet as a script of cells in document order. The
bodyfield is inherited fromScriptand holdsXlmCellstatements; a body typed as a list of cells would fail the variance check the framework's field classification runs.Expand source code Browse git
@dataclass(repr=False, eq=False) class XlmMacrosheet(Script): """ A macrosheet as a script of cells in document order. The `body` field is inherited from `refinery.lib.scripts.Script` and holds `XlmCell` statements; a body typed as a list of cells would fail the variance check the framework's field classification runs. """ name: str = '' kind: SheetKind = SheetKind.MACROSHEET def cell(self, row: int, col: int) -> XlmCell | None: """ The cell at the one-based `row` and `col`, or `None` where the sheet holds none. """ for item in self.body: if isinstance(item, XlmCell) and item.row == row and item.col == col: return item return None def listing(self, sort_formulas: bool = False) -> list[XlmCell]: """ The cells in the order the extract listing shows them: the cells that hold a formula first and the cells that hold only a value after, each group in document order — or, when sorted, the formulas by column letters and then by row. """ cells = [cell for cell in self.body if isinstance(cell, XlmCell)] formulas = [cell for cell in cells if cell.formula is not None] if sort_formulas: formulas.sort(key=lambda cell: (column_letters(cell.col), cell.row)) return formulas + [cell for cell in cells if cell.formula is None] def next_formula_cell(self, row: int, col: int) -> XlmCell | None: """ The row fall-through of the macro language: the first cell at or below `row` in the same column that holds a formula, bounded by `_ROW_FALL_THROUGH_LIMIT` rows below the starting row. """ result: XlmCell | None = None for item in self.body: if not isinstance(item, XlmCell) or item.col != col or item.formula is None: continue if not row <= item.row <= row + _ROW_FALL_THROUGH_LIMIT: continue if result is None or item.row < result.row: result = item return resultAncestors
Instance variables
var name-
The type of the None singleton.
var kind-
The type of the None singleton.
Methods
def cell(self, row, col)-
The cell at the one-based
rowandcol, orNonewhere the sheet holds none.Expand source code Browse git
def cell(self, row: int, col: int) -> XlmCell | None: """ The cell at the one-based `row` and `col`, or `None` where the sheet holds none. """ for item in self.body: if isinstance(item, XlmCell) and item.row == row and item.col == col: return item return None def listing(self, sort_formulas=False)-
The cells in the order the extract listing shows them: the cells that hold a formula first and the cells that hold only a value after, each group in document order — or, when sorted, the formulas by column letters and then by row.
Expand source code Browse git
def listing(self, sort_formulas: bool = False) -> list[XlmCell]: """ The cells in the order the extract listing shows them: the cells that hold a formula first and the cells that hold only a value after, each group in document order — or, when sorted, the formulas by column letters and then by row. """ cells = [cell for cell in self.body if isinstance(cell, XlmCell)] formulas = [cell for cell in cells if cell.formula is not None] if sort_formulas: formulas.sort(key=lambda cell: (column_letters(cell.col), cell.row)) return formulas + [cell for cell in cells if cell.formula is None] def next_formula_cell(self, row, col)-
The row fall-through of the macro language: the first cell at or below
rowin the same column that holds a formula, bounded by_ROW_FALL_THROUGH_LIMITrows below the starting row.Expand source code Browse git
def next_formula_cell(self, row: int, col: int) -> XlmCell | None: """ The row fall-through of the macro language: the first cell at or below `row` in the same column that holds a formula, bounded by `_ROW_FALL_THROUGH_LIMIT` rows below the starting row. """ result: XlmCell | None = None for item in self.body: if not isinstance(item, XlmCell) or item.col != col or item.formula is None: continue if not row <= item.row <= row + _ROW_FALL_THROUGH_LIMIT: continue if result is None or item.row < result.row: result = item return result
Inherited members
class XlmNameEntry (name, sheet, formula)-
A defined name as the macro language sees it: the name as written, the zero-based index of the sheet the name is scoped to in the full sheet table of the format — chartsheets included — or
Nonewhen the name is global, and the decoded formula.Expand source code Browse git
class XlmNameEntry(NamedTuple): """ A defined name as the macro language sees it: the name as written, the zero-based index of the sheet the name is scoped to in the full sheet table of the format — chartsheets included — or `None` when the name is global, and the decoded formula. """ name: str sheet: int | None formula: Expression | NoneAncestors
- builtins.tuple
Instance variables
var name-
Alias for field number 0
Expand source code Browse git
class XlmNameEntry(NamedTuple): """ A defined name as the macro language sees it: the name as written, the zero-based index of the sheet the name is scoped to in the full sheet table of the format — chartsheets included — or `None` when the name is global, and the decoded formula. """ name: str sheet: int | None formula: Expression | None var sheet-
Alias for field number 1
Expand source code Browse git
class XlmNameEntry(NamedTuple): """ A defined name as the macro language sees it: the name as written, the zero-based index of the sheet the name is scoped to in the full sheet table of the format — chartsheets included — or `None` when the name is global, and the decoded formula. """ name: str sheet: int | None formula: Expression | None var formula-
Alias for field number 2
Expand source code Browse git
class XlmNameEntry(NamedTuple): """ A defined name as the macro language sees it: the name as written, the zero-based index of the sheet the name is scoped to in the full sheet table of the format — chartsheets included — or `None` when the name is global, and the decoded formula. """ name: str sheet: int | None formula: Expression | None
class XlmNameTable (workbook)-
The defined names of a workbook as a list in document order, keeping an entry for every sheet a name is scoped to. The table is mutable because
SET.NAMEandDEFINE.NAMEdefine names at run time.Expand source code Browse git
class XlmNameTable: """ The defined names of a workbook as a list in document order, keeping an entry for every sheet a name is scoped to. The table is mutable because `SET.NAME` and `DEFINE.NAME` define names at run time. """ def __init__(self, workbook: ExcelWorkbook): self._entries: list[XlmNameEntry] = [ XlmNameEntry( name=record.name, sheet=record.sheet, formula=workbook.formula(record.formula), ) for record in workbook.defined_names() ] def entries(self) -> list[XlmNameEntry]: """ Every entry, in document order. """ return list(self._entries) def resolve(self, name: str, sheet: int | None = None) -> XlmNameEntry | None: """ The entry a name spells, matched case-insensitively. An entry scoped to the given sheet wins over a global entry, and a name scoped only to another sheet answers nothing — the way Excel answers `#NAME?`. `None` when the workbook defines no such name for the scope. """ key = name.lower() scoped: XlmNameEntry | None = None global_entry: XlmNameEntry | None = None for entry in self._entries: if entry.name.lower() != key: continue if entry.sheet == sheet and scoped is None: scoped = entry elif entry.sheet is None and global_entry is None: global_entry = entry return scoped or global_entry def fuzzy(self, pattern: str) -> list[XlmNameEntry]: """ The entries a pattern might mean, in document order: every entry whose name starts with the pattern, and when none does, every entry whose name contains the pattern's characters in order, each character of the name matching at most one of the pattern. """ pattern = pattern.lower() matches = [entry for entry in self._entries if entry.name.lower().startswith(pattern)] if matches: return matches return [entry for entry in self._entries if _is_subsequence(pattern, entry.name.lower())] def define(self, entry: XlmNameEntry) -> None: """ Define the name an entry carries, replacing the entry that spells it in the same scope and appending a new one when no such entry exists. """ key = entry.name.lower() for position, existing in enumerate(self._entries): if existing.name.lower() == key and existing.sheet == entry.sheet: self._entries[position] = entry return self._entries.append(entry) def restore(self, entries: list[XlmNameEntry]) -> None: """ Hold the entries an earlier state of the table held, as the undo of a definition performs. """ self._entries = list(entries) def undefine(self, name: str) -> None: """ Remove every entry a name spells, case-insensitively, whatever sheet it is scoped to. """ key = name.lower() self._entries = [entry for entry in self._entries if entry.name.lower() != key]Methods
def entries(self)-
Every entry, in document order.
Expand source code Browse git
def entries(self) -> list[XlmNameEntry]: """ Every entry, in document order. """ return list(self._entries) def resolve(self, name, sheet=None)-
The entry a name spells, matched case-insensitively. An entry scoped to the given sheet wins over a global entry, and a name scoped only to another sheet answers nothing — the way Excel answers
#NAME?.Nonewhen the workbook defines no such name for the scope.Expand source code Browse git
def resolve(self, name: str, sheet: int | None = None) -> XlmNameEntry | None: """ The entry a name spells, matched case-insensitively. An entry scoped to the given sheet wins over a global entry, and a name scoped only to another sheet answers nothing — the way Excel answers `#NAME?`. `None` when the workbook defines no such name for the scope. """ key = name.lower() scoped: XlmNameEntry | None = None global_entry: XlmNameEntry | None = None for entry in self._entries: if entry.name.lower() != key: continue if entry.sheet == sheet and scoped is None: scoped = entry elif entry.sheet is None and global_entry is None: global_entry = entry return scoped or global_entry def fuzzy(self, pattern)-
The entries a pattern might mean, in document order: every entry whose name starts with the pattern, and when none does, every entry whose name contains the pattern's characters in order, each character of the name matching at most one of the pattern.
Expand source code Browse git
def fuzzy(self, pattern: str) -> list[XlmNameEntry]: """ The entries a pattern might mean, in document order: every entry whose name starts with the pattern, and when none does, every entry whose name contains the pattern's characters in order, each character of the name matching at most one of the pattern. """ pattern = pattern.lower() matches = [entry for entry in self._entries if entry.name.lower().startswith(pattern)] if matches: return matches return [entry for entry in self._entries if _is_subsequence(pattern, entry.name.lower())] def define(self, entry)-
Define the name an entry carries, replacing the entry that spells it in the same scope and appending a new one when no such entry exists.
Expand source code Browse git
def define(self, entry: XlmNameEntry) -> None: """ Define the name an entry carries, replacing the entry that spells it in the same scope and appending a new one when no such entry exists. """ key = entry.name.lower() for position, existing in enumerate(self._entries): if existing.name.lower() == key and existing.sheet == entry.sheet: self._entries[position] = entry return self._entries.append(entry) def restore(self, entries)-
Hold the entries an earlier state of the table held, as the undo of a definition performs.
Expand source code Browse git
def restore(self, entries: list[XlmNameEntry]) -> None: """ Hold the entries an earlier state of the table held, as the undo of a definition performs. """ self._entries = list(entries) def undefine(self, name)-
Remove every entry a name spells, case-insensitively, whatever sheet it is scoped to.
Expand source code Browse git
def undefine(self, name: str) -> None: """ Remove every entry a name spells, case-insensitively, whatever sheet it is scoped to. """ key = name.lower() self._entries = [entry for entry in self._entries if entry.name.lower() != key]
class XlmOutcome (value=None, jump=None, status=None, halts=False, arrival=None)-
What one macro command produced: the value it computed, the jump it asks the engine to take, the status that overrides the one its value derives, whether it halts the program, and the arrival the engine lands the jump by, for a jump the command that asks for it does not name — the step a macro call jumps to and the cell a return continues at. A handler that neither jumps nor overrides a status leaves both empty.
Expand source code Browse git
class XlmOutcome: """ What one macro command produced: the value it computed, the jump it asks the engine to take, the status that overrides the one its value derives, whether it halts the program, and the arrival the engine lands the jump by, for a jump the command that asks for it does not name — the step a macro call jumps to and the cell a return continues at. A handler that neither jumps nor overrides a status leaves both empty. """ def __init__( self, value: XlmValue | None = None, jump: XlmCursor | None = None, status: XlmStatus | None = None, halts: bool = False, arrival: XlmArrival | None = None, ): self.value = XlmValue() if value is None else value self.jump = jump self.status = status self.halts = halts self.arrival = arrival class XlmReference (sheet, row, col)-
The address of one cell: the sheet it sits on —
Nonenames the sheet the reading formula is on — and its one-based row and column.Expand source code Browse git
class XlmReference(NamedTuple): """ The address of one cell: the sheet it sits on — `None` names the sheet the reading formula is on — and its one-based row and column. """ sheet: str | None row: int col: int def a1(self) -> str: """ The A1 spelling of the address, qualified by its sheet when it names one. """ cell = F'{column_letters(self.col)}{self.row}' if self.sheet is None: return cell return F'{self.sheet}!{cell}'Ancestors
- builtins.tuple
Instance variables
var sheet-
Alias for field number 0
Expand source code Browse git
class XlmReference(NamedTuple): """ The address of one cell: the sheet it sits on — `None` names the sheet the reading formula is on — and its one-based row and column. """ sheet: str | None row: int col: int def a1(self) -> str: """ The A1 spelling of the address, qualified by its sheet when it names one. """ cell = F'{column_letters(self.col)}{self.row}' if self.sheet is None: return cell return F'{self.sheet}!{cell}' var row-
Alias for field number 1
Expand source code Browse git
class XlmReference(NamedTuple): """ The address of one cell: the sheet it sits on — `None` names the sheet the reading formula is on — and its one-based row and column. """ sheet: str | None row: int col: int def a1(self) -> str: """ The A1 spelling of the address, qualified by its sheet when it names one. """ cell = F'{column_letters(self.col)}{self.row}' if self.sheet is None: return cell return F'{self.sheet}!{cell}' var col-
Alias for field number 2
Expand source code Browse git
class XlmReference(NamedTuple): """ The address of one cell: the sheet it sits on — `None` names the sheet the reading formula is on — and its one-based row and column. """ sheet: str | None row: int col: int def a1(self) -> str: """ The A1 spelling of the address, qualified by its sheet when it names one. """ cell = F'{column_letters(self.col)}{self.row}' if self.sheet is None: return cell return F'{self.sheet}!{cell}'
Methods
def a1(self)-
The A1 spelling of the address, qualified by its sheet when it names one.
Expand source code Browse git
def a1(self) -> str: """ The A1 spelling of the address, qualified by its sheet when it names one. """ cell = F'{column_letters(self.col)}{self.row}' if self.sheet is None: return cell return F'{self.sheet}!{cell}'
class XlmSeverity (*args, **kwds)-
The output level a macro command shows at: the commands that move execution print at level 0, ordinary commands at level 1, and the commands that matter for triage at level 2.
Expand source code Browse git
class XlmSeverity(enum.Enum): """ The output level a macro command shows at: the commands that move execution print at level 0, ordinary commands at level 1, and the commands that matter for triage at level 2. """ JUMP = 0 NORMAL = 1 IMPORTANT = 2Ancestors
- enum.Enum
Class variables
var JUMP-
The type of the None singleton.
var NORMAL-
The type of the None singleton.
var IMPORTANT-
The type of the None singleton.
class XlmStatus (*args, **kwds)-
The state one executed cell left the program in.
Expand source code Browse git
class XlmStatus(enum.Enum): """ The state one executed cell left the program in. """ FullEvaluation = enum.auto() PartialEvaluation = enum.auto() Error = enum.auto() NotImplemented = enum.auto() End = enum.auto() Branching = enum.auto() FullBranching = enum.auto() IGNORED = enum.auto()Ancestors
- enum.Enum
Class variables
var FullEvaluation-
The type of the None singleton.
var PartialEvaluation-
The type of the None singleton.
var Error-
The type of the None singleton.
var NotImplemented-
The type of the None singleton.
var End-
The type of the None singleton.
var Branching-
The type of the None singleton.
var FullBranching-
The type of the None singleton.
var IGNORED-
The type of the None singleton.
class XlmStep (sheet, row, col, status, text, indent, severity)-
One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all.
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverityAncestors
- builtins.tuple
Instance variables
var sheet-
Alias for field number 0
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var row-
Alias for field number 1
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var col-
Alias for field number 2
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var status-
Alias for field number 3
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var text-
Alias for field number 4
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var indent-
Alias for field number 5
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity var severity-
Alias for field number 6
Expand source code Browse git
class XlmStep(NamedTuple): """ One executed cell of a run: the sheet it sits on with its one-based row and column, the status the run left it in, the text the trace prints for it, its indentation inside a branch, and the output level it prints at — the level of its command, or the lowest level when the cell is no command call at all. """ sheet: str row: int col: int status: XlmStatus text: str indent: int severity: XlmSeverity
class XlmValue (value=None, text=None, partial=False, date=False, reference=None, cells=None, error=False)-
One value of the macro language as the emulator computes it:
valueis the Python object behind it,textthe spelling the trace prints,partialmarks a value whose computation the program never finished,datemarks a value that came from a date cell,referencenames the cell address the value stands for,cellsholds the elements of an array or the corners of a range, anderrormarks an error value such as the#DIV/0!of a division by zero. A given spelling of a number is normalized unless the value is a text, whose spelling is its content.Expand source code Browse git
@dataclass(repr=False, eq=False) class XlmValue: """ One value of the macro language as the emulator computes it: `value` is the Python object behind it, `text` the spelling the trace prints, `partial` marks a value whose computation the program never finished, `date` marks a value that came from a date cell, `reference` names the cell address the value stands for, `cells` holds the elements of an array or the corners of a range, and `error` marks an error value such as the `#DIV/0!` of a division by zero. A given spelling of a number is normalized unless the value is a text, whose spelling is its content. """ value: Any = None text: str | None = None partial: bool = False date: bool = False reference: XlmReference | None = None cells: tuple[XlmValue, ...] | None = None error: bool = False def __post_init__(self): if self.text is None: if isinstance(self.value, bool): self.text = 'TRUE' if self.value else 'FALSE' elif self.partial: self.text = str(self.value) else: self.text = _default_text(self.value) elif not isinstance(self.value, str) and is_number(self.text): number = float(self.text) self.text = str(int(number) if number.is_integer() else number) def unwrap(self) -> str: """ The content of the value as a text. A text the program finished computing is its own content, and any other finished value is spelled as itself, whatever spelling the trace gives the command that computed it; a value the program never finished is the spelling of what it left unfinished, without the quotes of a string literal. """ if self.partial: return unwrap_literal(self.text or '') if isinstance(self.value, str): return self.value return _default_text(self.value) def unwrap_date(self) -> datetime.datetime | None: """ The moment an ISO-spelled text names, in one of the spellings the macro language writes into cells, or `None` for a text that names none. """ for spelling in _DATETIME_FORMATS: try: return datetime.datetime.strptime(self.unwrap(), spelling) except ValueError: continue return NoneInstance variables
var value-
The type of the None singleton.
var text-
The type of the None singleton.
var partial-
The type of the None singleton.
var date-
The type of the None singleton.
var reference-
The type of the None singleton.
var cells-
The type of the None singleton.
var error-
The type of the None singleton.
Methods
def unwrap(self)-
The content of the value as a text. A text the program finished computing is its own content, and any other finished value is spelled as itself, whatever spelling the trace gives the command that computed it; a value the program never finished is the spelling of what it left unfinished, without the quotes of a string literal.
Expand source code Browse git
def unwrap(self) -> str: """ The content of the value as a text. A text the program finished computing is its own content, and any other finished value is spelled as itself, whatever spelling the trace gives the command that computed it; a value the program never finished is the spelling of what it left unfinished, without the quotes of a string literal. """ if self.partial: return unwrap_literal(self.text or '') if isinstance(self.value, str): return self.value return _default_text(self.value) def unwrap_date(self)-
The moment an ISO-spelled text names, in one of the spellings the macro language writes into cells, or
Nonefor a text that names none.Expand source code Browse git
def unwrap_date(self) -> datetime.datetime | None: """ The moment an ISO-spelled text names, in one of the spellings the macro language writes into cells, or `None` for a text that names none. """ for spelling in _DATETIME_FORMATS: try: return datetime.datetime.strptime(self.unwrap(), spelling) except ValueError: continue return None
class XlmView (data)-
A workbook opened for interpretation. The macrosheets are the model the trace and the deobfuscation passes work on; the worksheets are data, each read into a coordinate dictionary of model cells when it is first asked for, because no reader offers random access to them. Sheet names are matched case-insensitively, as Excel matches them.
Expand source code Browse git
class XlmView: """ A workbook opened for interpretation. The macrosheets are the model the trace and the deobfuscation passes work on; the worksheets are data, each read into a coordinate dictionary of model cells when it is first asked for, because no reader offers random access to them. Sheet names are matched case-insensitively, as Excel matches them. """ def __init__(self, data: bytes | bytearray | memoryview): workbook = open_workbook(data) self._workbook = workbook self._macrosheets: dict[str, XlmMacrosheet] = {} for macrosheet in build_xlm_model(workbook): self._macrosheets.setdefault(macrosheet.name.lower(), macrosheet) self._worksheet_sources: dict[str, ExcelSheet] = {} for sheet in workbook.sheets(): if sheet.kind is SheetKind.WORKSHEET: self._worksheet_sources.setdefault(sheet.name.lower(), sheet) self._worksheets: dict[str, dict[tuple[int, int], XlmCell]] = {} self.names = XlmNameTable(workbook) self.workbook_name = _WORKBOOK_NAMES[workbook.format] self.international = International() def macrosheets(self) -> list[XlmMacrosheet]: """ Every macrosheet of the workbook in document order. """ return list(self._macrosheets.values()) def entry_points(self, start_point: str = '') -> list[XlmReference]: """ The cells a run of the program starts at: every defined name that fuzzy-spells `auto_open` or `auto_close` points at one, and without such a name a start point a caller named is the only entry. """ result: list[XlmReference] = [] for pattern in ('auto_open', 'auto_close'): for entry in self.names.fuzzy(pattern): formula = entry.formula if not isinstance(formula, (XlA1Reference, XlR1C1Reference)): continue reference = resolve_reference(formula, XlmCursor('', 0, 0)) if reference.sheet is not None: result.append(reference) if not result and start_point: parsed = parse_formula(start_point) if isinstance(parsed, (XlA1Reference, XlR1C1Reference)): reference = resolve_reference(parsed, XlmCursor('', 0, 0)) if reference.sheet is not None: result.append(reference) return result def macrosheet(self, name: str) -> XlmMacrosheet | None: """ The macrosheet a name spells, or `None` when the workbook has none. """ return self._macrosheets.get(name.lower()) def worksheet(self, name: str) -> dict[tuple[int, int], XlmCell] | None: """ The cells of the worksheet a name spells, keyed by one-based row and column as `refinery.lib.scripts.xlm.model.sheet_cells` reads them, or `None` when the workbook has no such worksheet. """ key = name.lower() cells = self._worksheets.get(key) if cells is None: sheet = self._worksheet_sources.get(key) if sheet is None: return None cells = self._worksheets[key] = sheet_cells(self._workbook, sheet) return cells def sheet_index(self, name: str) -> int | None: """ The position of the sheet a name spells in the full sheet table of the workbook — the sequence the scopes of its defined names count, chartsheets included — or `None` when the workbook has no such sheet. """ return self._workbook.sheet_index(name) def cell(self, sheet_name: str, row: int, col: int) -> XlmCell | None: """ The cell of the sheet a name spells at the one-based `row` and `col`, or `None` when the sheet or the cell does not exist. """ macrosheet = self.macrosheet(sheet_name) if macrosheet is not None: return macrosheet.cell(row, col) worksheet = self.worksheet(sheet_name) if worksheet is not None: return worksheet.get((row, col)) return NoneMethods
def macrosheets(self)-
Every macrosheet of the workbook in document order.
Expand source code Browse git
def macrosheets(self) -> list[XlmMacrosheet]: """ Every macrosheet of the workbook in document order. """ return list(self._macrosheets.values()) def entry_points(self, start_point='')-
The cells a run of the program starts at: every defined name that fuzzy-spells
auto_openorauto_closepoints at one, and without such a name a start point a caller named is the only entry.Expand source code Browse git
def entry_points(self, start_point: str = '') -> list[XlmReference]: """ The cells a run of the program starts at: every defined name that fuzzy-spells `auto_open` or `auto_close` points at one, and without such a name a start point a caller named is the only entry. """ result: list[XlmReference] = [] for pattern in ('auto_open', 'auto_close'): for entry in self.names.fuzzy(pattern): formula = entry.formula if not isinstance(formula, (XlA1Reference, XlR1C1Reference)): continue reference = resolve_reference(formula, XlmCursor('', 0, 0)) if reference.sheet is not None: result.append(reference) if not result and start_point: parsed = parse_formula(start_point) if isinstance(parsed, (XlA1Reference, XlR1C1Reference)): reference = resolve_reference(parsed, XlmCursor('', 0, 0)) if reference.sheet is not None: result.append(reference) return result def macrosheet(self, name)-
The macrosheet a name spells, or
Nonewhen the workbook has none.Expand source code Browse git
def macrosheet(self, name: str) -> XlmMacrosheet | None: """ The macrosheet a name spells, or `None` when the workbook has none. """ return self._macrosheets.get(name.lower()) def worksheet(self, name)-
The cells of the worksheet a name spells, keyed by one-based row and column as
sheet_cells()reads them, orNonewhen the workbook has no such worksheet.Expand source code Browse git
def worksheet(self, name: str) -> dict[tuple[int, int], XlmCell] | None: """ The cells of the worksheet a name spells, keyed by one-based row and column as `refinery.lib.scripts.xlm.model.sheet_cells` reads them, or `None` when the workbook has no such worksheet. """ key = name.lower() cells = self._worksheets.get(key) if cells is None: sheet = self._worksheet_sources.get(key) if sheet is None: return None cells = self._worksheets[key] = sheet_cells(self._workbook, sheet) return cells def sheet_index(self, name)-
The position of the sheet a name spells in the full sheet table of the workbook — the sequence the scopes of its defined names count, chartsheets included — or
Nonewhen the workbook has no such sheet.Expand source code Browse git
def sheet_index(self, name: str) -> int | None: """ The position of the sheet a name spells in the full sheet table of the workbook — the sequence the scopes of its defined names count, chartsheets included — or `None` when the workbook has no such sheet. """ return self._workbook.sheet_index(name) def cell(self, sheet_name, row, col)-
The cell of the sheet a name spells at the one-based
rowandcol, orNonewhen the sheet or the cell does not exist.Expand source code Browse git
def cell(self, sheet_name: str, row: int, col: int) -> XlmCell | None: """ The cell of the sheet a name spells at the one-based `row` and `col`, or `None` when the sheet or the cell does not exist. """ macrosheet = self.macrosheet(sheet_name) if macrosheet is not None: return macrosheet.cell(row, col) worksheet = self.worksheet(sheet_name) if worksheet is not None: return worksheet.get((row, col)) return None