Skip to content

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:

arvl_to_dcsn_transition: |
  m_d[_dcsn] = m[_arvl]

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:

arvl_to_dcsn_transition: |
  m_d = m

...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:

  1. Explicit index: If s[_perch] or s[t+n], use the explicit index. No resolution needed.

  2. Native perch lookup: If s (bare) appears:

  3. Look up s in symbol declarations
  4. If found in a group with a canonical perch, assign that perch
  5. Example: m declared in prestate → resolve to m[_arvl]

  6. Perch value objects require explicit indices:

  7. Any symbol declared under values or shadow_value is a perch value object
  8. These require explicit perch indices (e.g., V[_dcsn], dV[_cntn])
  9. Bare use is an error because it is ambiguous across perches
  10. (In practice this most commonly applies to V and dV.)

  11. Unknown symbol: If s is not declared in any symbol group:

  12. If it's a parameter, treat as unindexed (parameters have no perch)
  13. 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:

assignment: (variable | symbol) "=" formula

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


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

  1. Parse bare symbols: m_d = m parses without error for adc-stage files

  2. Correct resolution: After resolution:

    arvl_to_dcsn_transition: |
      m_d = m
    
    becomes internally: m_d[_dcsn] = m[_arvl] (given prestate: [m], states: [m_d])

  3. Explicit indices preserved: V[_dcsn] = max_{c}(...) unchanged

  4. Error on ambiguous: Bare V or dV raises error

  5. Backwards compatible: Vanilla Dolo files (no dialect: adc-stage) unaffected


7. Test Cases

7.1 Basic transition

symbols:
  prestate:
    m: "@in R+"
  states:
    m_d: "@in R+"

equations:
  arvl_to_dcsn_transition: |
    m_d = m

Expected resolution: m_d[_dcsn] = m[_arvl]

7.2 Mixed bare and indexed

equations:
  dcsn_to_cntn_transition: |
    a = m_d - c

Given poststates: [a], states: [m_d], controls: [c]:

Expected: a[_cntn] = m_d[_dcsn] - c[_dcsn]

7.3 Value functions require indices

equations:
  mover:
    Bellman: |
      V = max_{c}(u(c) + β*V[_cntn])  # ERROR: bare V on LHS

Expected: Validation error "V requires explicit perch index"

7.4 Parameters (no perch)

equations:
  transition: |
    a = m - c + r*b  # r is parameter, no index needed

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

  1. Should we allow bare symbols on RHS only?
  2. Current proposal: allow on both LHS and RHS
  3. Alternative: require indexed LHS, allow bare RHS

  4. Shift operator syntax?

  5. Current: c[_cntn] means "c at continuation perch"
  6. Should we add explicit shift operator? e.g., shift(c, _cntn)

  7. Error recovery?

  8. If a symbol isn't declared, should we default to _dcsn or error?
  9. Current proposal: error (strict validation)