Repository navigation
tutorials 07 errors and inspection
Computed configuration needs diagnostics that explain more than a final wrong value. lclang keeps source spans, variable evaluation stacks, cache state, definition owners, and dependency structure available through public values.
- how syntax and evaluation failures differ;
- how variable stacks identify the route to a failure;
- how
inspect_variableexplains a value without evaluating it; - how inspection changes after a value becomes cached.
Parsing fails before a Frame exists. Evaluation failures retain the nested definition route and the original host exception as their cause.
import asyncio
import lclang
async def main() -> None:
try:
lclang.parse_expression("1 +")
except lclang.LclSyntaxError as error:
assert error.span is not None
assert "expected an expression" in str(error)
else:
raise AssertionError("malformed syntax was accepted")
def divide(left: int, right: int) -> float:
return left / right
module = lclang.define_module(
"calculation",
{"ratio": "divide(total, count)", "result": "ratio * 100"},
)
async with lclang.define_frame(
module,
preset={"divide": divide, "total": 5, "count": 0},
) as frame:
try:
await frame.get("result")
except lclang.LclEvaluationError as error:
assert error.variable_stack == ("result", "ratio")
assert isinstance(error.__cause__, ZeroDivisionError)
else:
raise AssertionError("division failure was not reported")
asyncio.run(main())The incomplete addition fails while parsing, so its error has a source span but
no Frame stack. The second failure starts at result, enters ratio, and then
calls the host division function with zero. lclang wraps that host exception
once, preserving both the owner route result -> ratio and the original
ZeroDivisionError cause checked by the assertions.
Catch lclang.LclError at an application boundary when one expected family is
useful. Catch narrower subclasses when recovery differs. Cancellation,
KeyboardInterrupt, and other base exceptions are not converted into ordinary
language failures.
Inspection performs lookup and static dependency traversal but never calls, awaits, caches, or publishes dynamic traces.
import asyncio
import lclang
async def main() -> None:
module = lclang.define_module(
"invoice",
{
"subtotal": "unit_price * quantity",
"total": "subtotal + shipping",
},
)
async with lclang.define_frame(
module,
preset={"unit_price": 8, "quantity": 4, "shipping": 3},
) as frame:
before = frame.inspect_variable("total")
assert before.status.value == "NotEvaluated"
assert [str(item.var_name) for item in before.dependencies] == [
"subtotal",
"shipping",
]
assert before.dependencies[1].status.value == "ExternalProvided"
assert await frame.get("total") == 35
after = frame.inspect_variable("total")
assert after.status.value == "Cached"
assert after.current_value == 35
assert "(Cached) int: 35" in repr(after)
asyncio.run(main())Before evaluation, static analysis finds subtotal and shipping; lookup also
shows that shipping is an external value, but no result exists for total.
The explicit get computes 8 * 4 + 3 = 35 and commits it. A second inspection
therefore reports the same definition with Cached status and the integer
payload asserted in repr.
tree.to_lines() renders a compact nested list suitable for logs and issue
reports. Missing leaves remain visible with LclNameError. Repeated direct
names collapse within one parent, while a name reached through different
branches remains visible in each branch.
- Call
frame.has(name)to distinguish absence from failure. - Call
frame.get_definition(name)to see the selected AST without running it. - Call
frame.inspect_variable(name)to explain lookup and static structure. - Evaluate only if runtime evidence is needed.
- Inspect again or read
dependency_snapshot(name)to compare prediction with observed behavior.
This order respects lclang's design: observation should not accidentally cause the work being diagnosed.
Previous: Caching and recalculation | Next: Dependency analysis | Return to the series introduction
Published from ac045a6487d6. Documentation is maintained and tested in the source repository.