spec_0.1g — Bare Symbol Resolution (Native Perch Inference)¶
Status: Draft Depends on: 0.1a (parse), 0.1c (symbols) Last updated: 2026-01-28
1. Motivation¶
Currently, dolang's assignment_block grammar rule requires indexed variables:
assignment_block: _NEWLINE? assignment (_NEWLINE assignment )* _NEWLINE?
assignment: variable "=" formula
variable: cname "[" date_index "]"
This forces modellers to write:
But the spec (§1.1.4) states:
"If a variable is written without an index, it is assumed to be at its canonical perch."
We want to allow the cleaner form:
...and infer perch indices from symbol declarations.
1.1 Semantic intent (align with “syntax → formal problem” reading)¶
This feature is syntactic sugar / normalization, not a semantic change.
In the “syntax → formal problem” reading
(06-from-syntax-to-formal-problem-definition.md),
transition equalities like w = exp(y) + b*r are read as implicit map definitions (they define the map \(g(\cdot)\) between perch objects). Bare symbol resolution simply makes the perch indices explicit using the stage’s canonical slot/perch conventions, so that:
- the compiler can type-check measurability (shift rules),
- later SYM → CORE steps can keep perches straight, and
- we do not force authors to write boilerplate indices everywhere.
2. Native Perch Resolution Rule¶
2.1 Symbol group → canonical perch mapping¶
This table should be read together with the canonical-slot conventions in
01 Symbols and declarations
and the filtration timing rules in
02 Stage structure and timing.
| Symbol group | Canonical perch | Canonical slot | Notes |
|---|---|---|---|
prestate |
[_arvl] |
-1 |
Arrival-perch state label |
states |
[_dcsn] |
0 |
Decision-perch state label |
poststates |
[_cntn] |
+1 |
Continuation-perch state label |
controls |
[_dcsn] |
0 |
Controls are chosen at decision perch (even if represented elsewhere) |
rewards |
[_dcsn] |
0 |
Reward objects are typically decision-perch measurable |
exogenous |
(edge → slot) | (by timing) | In v0.1 the default is “shock realized between -1 and 0”, so shocks are measurable at [_dcsn] |
parameters |
(no perch) | — | Parameters remain unindexed |
values, shadow_value |
(requires explicit) | -1/0/+1 |
Perch value objects must be explicitly indexed (no native default) |
2.2 Resolution algorithm¶
Given a bare symbol s in an equation:
-
Explicit index: If
s[_perch]ors[t+n], use the explicit index. No resolution needed. -
Native perch lookup: If
s(bare) appears: - Look up
sin symbol declarations - If found in a group with a canonical perch, assign that perch
-
Example:
mdeclared inprestate→ resolve tom[_arvl] -
Perch value objects require explicit indices:
- Any symbol declared under
valuesorshadow_valueis a perch value object - These require explicit perch indices (e.g.,
V[_dcsn],dV[_cntn]) - Bare use is an error because it is ambiguous across perches
-
(In practice this most commonly applies to
VanddV.) -
Unknown symbol: If
sis not declared in any symbol group: - If it's a parameter, treat as unindexed (parameters have no perch)
- Otherwise, raise a validation error
2.3 LHS vs RHS context (optional refinement)¶
For transition equations like m_d = m, we could infer:
- LHS variable gets the "target" perch of the transition
- RHS variables get their native perch
But for v0.1, we keep it simple: all bare symbols resolve to their native perch regardless of LHS/RHS position.
3. Implementation Options¶
Option A: Grammar change (dolang)¶
Modify assignment rule to accept bare symbols:
Then add a post-parse resolution pass that:
1. Walks the AST
2. Replaces bare symbol nodes with variable nodes using the native perch
Pros: Clean separation of parsing and semantic resolution Cons: Changes dolang grammar; may affect vanilla Dolo
Option B: Use equation_block for adc-stage¶
In model.py, for dialect: adc-stage, use equation_block instead of assignment_block:
if is_adc_stage:
start = "equation_block" # allows equality with bare symbols
else:
start = "assignment_block"
Then add a resolution pass to convert equality nodes to assignment with resolved indices.
Pros: No grammar changes; adc-stage specific Cons: Different AST structure to handle
Option C: Pre-parse rewriting (string level)¶
Before parsing, scan the equation string and rewrite bare symbols to indexed form: - Build a regex/tokenizer that identifies bare symbols - Replace with indexed form based on symbol declarations
Pros: No grammar or AST changes Cons: Fragile; regex parsing is error-prone
4. Recommended Approach: Option A (Grammar + Resolution Pass)¶
Option A is workable, but note the semantic framing from the SYM foundations: transition equations are equalities-as-implicit-map-definitions. For that reason, an alternative with a smaller “surface area” on vanilla Dolo parsing is:
- Use
equation_block(equality-friendly) for adc-stage blocks, and - apply the same AST resolution pass only in adc-stage mode.
This avoids broadening the vanilla assignment_block grammar while still allowing
authors to write w = exp(y) + b*r in stage-mode.
4.1 Grammar changes (dolang/grammar.lark)¶
// Allow bare symbol or indexed variable on LHS of assignment
assignment: (variable | symbol) "=" formula
4.2 Resolution pass (new module: dolang/perch_resolver.py)¶
def resolve_native_perches(tree, symbol_groups: dict) -> Tree:
"""
Walk AST and replace bare symbols with indexed variables
using native perch from symbol declarations.
Args:
tree: Lark parse tree
symbol_groups: {group_name: [symbol_names], ...}
Returns:
Modified tree with bare symbols resolved to indexed variables
"""
# Build symbol → native perch mapping
native_perch = {}
group_to_perch = {
'prestate': '_arvl',
'states': '_dcsn',
'poststates': '_cntn',
'controls': '_dcsn',
'exogenous': '_dcsn',
}
for group, names in symbol_groups.items():
if group in group_to_perch:
for name in names:
native_perch[name] = group_to_perch[group]
# Walk tree and resolve
return _resolve_tree(tree, native_perch)
4.3 Integration point (dolo/compiler/model.py)¶
The equations property in SymbolicModel needs modification at two locations:
4.3.1 Scalar equation blocks (line ~204)¶
# Current:
eqs = parse_string(v, start=start)
eqs = sanitize(eqs, variables=vars)
# Changed to:
eqs = parse_string(v, start=start)
if is_adc_stage:
from dolang.perch_resolver import resolve_native_perches
eqs = resolve_native_perches(eqs, self.symbols)
eqs = sanitize(eqs, variables=vars)
4.3.2 Nested sub-equation blocks (line ~376)¶
# Current:
eqs = parse_string(sub_v, start="assignment_block")
eqs = sanitize(eqs, variables=vars)
# Changed to:
eqs = parse_string(sub_v, start="assignment_block")
if is_adc_stage:
from dolang.perch_resolver import resolve_native_perches
eqs = resolve_native_perches(eqs, self.symbols)
eqs = sanitize(eqs, variables=vars)
4.3.3 Symbol access¶
The resolver needs the symbol groups to build the native perch mapping. At both integration points, self.symbols is available (it's computed lazily but will be ready before equations is accessed).
5. Validation Rules¶
5.1 Required explicit indices¶
The following must have explicit indices (error if bare):
- V (value function) and dV (shadow value / marginal value)
- more generally: all perch value objects declared under values / shadow_value
5.2 Shift validation¶
After resolution, validate that perch shifts are measurability-consistent:
- c[_cntn] allowed (EGM representation)
- c[_arvl] not allowed (control not measurable pre-shock)
(This validation already exists per §1.1.5; resolution just needs to produce indexed forms for it to check.)
6. Acceptance Criteria¶
-
Parse bare symbols:
m_d = mparses without error for adc-stage files -
Correct resolution: After resolution:
becomes internally:m_d[_dcsn] = m[_arvl](givenprestate: [m],states: [m_d]) -
Explicit indices preserved:
V[_dcsn] = max_{c}(...)unchanged -
Error on ambiguous: Bare
VordVraises error -
Backwards compatible: Vanilla Dolo files (no
dialect: adc-stage) unaffected
7. Test Cases¶
7.1 Basic transition¶
Expected resolution: m_d[_dcsn] = m[_arvl]
7.2 Mixed bare and indexed¶
Given poststates: [a], states: [m_d], controls: [c]:
Expected: a[_cntn] = m_d[_dcsn] - c[_dcsn]
7.3 Value functions require indices¶
Expected: Validation error "V requires explicit perch index"
7.4 Parameters (no perch)¶
Expected: r remains bare (parameters have no perch)
8. File Changes Summary¶
| File | Change |
|---|---|
packages/dolang/dolang/grammar.lark |
Modify assignment rule to allow symbol on LHS |
packages/dolang/dolang/perch_resolver.py |
New module: AST walker that resolves bare symbols to indexed variables |
packages/dolo/dolo/compiler/model.py |
Call resolve_native_perches() after parse_string() for adc-stage (2 locations: ~L204, ~L376) |
packages/dolang/tests/test_perch_resolver.py |
New test file: unit tests for resolution pass |
packages/dolo/tests/test_bare_symbol_parsing.py |
New test file: integration tests for adc-stage parsing |
9. Open Questions¶
- Should we allow bare symbols on RHS only?
- Current proposal: allow on both LHS and RHS
-
Alternative: require indexed LHS, allow bare RHS
-
Shift operator syntax?
- Current:
c[_cntn]means "c at continuation perch" -
Should we add explicit shift operator? e.g.,
shift(c, _cntn) -
Error recovery?
- If a symbol isn't declared, should we default to
_dcsnor error? - Current proposal: error (strict validation)