Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Alignment Index Spaces

Status: Current Last updated: 2026-04-16 21:50 EDT

%mor / %gra / %pho / %sin alignment code in this extension crosses three integer index spaces that all look identical when typed as usize. Confusing them is the bug class the typed newtypes defined in talkbank_model::alignment::indices exist to prevent.

This page is the canonical source for which space each integer lives in, how the spaces convert into each other, and a worked example on a post-clitic utterance.

The three spaces

Newtype0- / 1-indexedOver which sequenceWhere it comes from
MorItemIndex0-indexedMorTier::items (one entry per %mor item, no post-clitic expansion)MorAlignmentPair.target_index on the main↔%mor alignment
MorChunkIndex0-indexedMorTier::chunks() (item main word, then each post-clitic, then the optional terminator)GraAlignmentPair.mor_chunk_index on the %mor↔%gra alignment
SemanticWordIndex11-indexedAuthor-written position in a %gra relationGrammaticalRelation::index, ::head

The %gra ROOT sentinel (head == 0) is not a chunk — it is represented separately as GraHeadRef::Root so the sentinel is a variant, not a magic number.

%% Verified against: crates/talkbank-model/src/alignment/indices.rs,
%% crates/talkbank-model/src/alignment/{mor,pho,sin,gra/types}.rs.
classDiagram
    class MainWordIndex {
        +new(usize)
        +as_usize() usize
    }
    class MorItemIndex {
        +new(usize)
        +as_usize() usize
    }
    class MorChunkIndex {
        +new(usize)
        +as_usize() usize
    }
    class GraIndex {
        +new(usize)
        +as_usize() usize
    }
    class PhoItemIndex
    class SinItemIndex
    class SemanticWordIndex1 {
        +new(usize) Result~Self~
        +to_chunk_index() MorChunkIndex
    }
    class GraHeadRef {
        <<enumeration>>
        Root
        Word(SemanticWordIndex1)
    }
    class MorAlignmentPair {
        source: Option~MainWordIndex~
        target: Option~MorItemIndex~
    }
    class GraAlignmentPair {
        mor_chunk_index: Option~MorChunkIndex~
        gra_index: Option~GraIndex~
    }
    MorAlignmentPair --> MainWordIndex : source
    MorAlignmentPair --> MorItemIndex : target
    GraAlignmentPair --> MorChunkIndex : source
    GraAlignmentPair --> GraIndex : target
    GraHeadRef --> SemanticWordIndex1 : Word variant carries
    SemanticWordIndex1 --> MorChunkIndex : to_chunk_index

Worked example

Consider the CHAT line

*CHI: it's cookies .
%mor: pron|it~aux|be n|cookie .
%gra: 1|2|SUBJ 2|0|ROOT 3|2|OBJ 4|2|PUNCT

The %mor tier has two items (pron|it~aux|be and n|cookie). The same tier has four chunks when MorTier::chunks() expands the items by their post-clitics and appends the terminator:

Chunk indexChunk kindWord / TextHost item index
0Mainpron|it0
1PostCliticaux|be0
2Mainn|cookie1
3Terminator.(none)

The %gra relations address this chunk sequence, 1-indexed. So 2|0|ROOT says “semantic word 2 (chunk 1 = aux|be) has head 0 (ROOT).”

Common pitfall this file exists to prevent: taking relation.index == 2, treating it as MorItemIndex(2) — which would be out of range (only two items exist) — or MorItemIndex(1) after a naïve - 1 — which would pick up n|cookie instead of aux|be. Neither maps the semantic word to its %mor lemma.

The correct projection is:

// 1. Semantic word index → chunk index via subtract-1.
let chunk_idx = (relation.index as usize).checked_sub(1)?;

// 2. Chunk → host item for anyone who needs main↔%mor alignment.
let host_item_idx = mor_tier.item_index_of_chunk(chunk_idx)?;

When to use which projection

NeedUse
“What lemma does this %gra relation refer to?”MorTier::chunk_at(chunk_idx) then chunk.lemma()
“Which main-tier word does this %gra relation correspond to?”MorTier::item_index_of_chunk(chunk_idx) then look up the main↔%mor pair
“Is this relation pointing at ROOT?”relation.head_ref() → match on GraHeadRef::Root
“Walk all chunks in order”mor_tier.chunks() (never hand-roll `items.iter().flat_map(

The other alignments

%pho, %mod, and %sin do not expand — they align 1:1 with main-tier alignable words. Their typed pairs are:

TierPair aliasSourceTarget
%morMorAlignmentPairMainWordIndexMorItemIndex
%graGraAlignmentPairMorChunkIndexGraIndex
%pho (and %mod)PhoAlignmentPairMainWordIndexPhoItemIndex
%sinSinAlignmentPairMainWordIndexSinItemIndex

%wor is not a structural alignment — it is a timing sidecar (WorTimingSidecar) with a Positional { count } / Drifted { main_count, wor_count } shape rather than a pair list. Count mismatches are tolerated rather than diagnosed.

See also