marianoguerra/shrubbery/column does not have a README file

    Column

    pub enum Column {
    Plain(Int)
    Tabbed(Array[Int])
    } derive(Eq,
    Debug
    )

    An indentation column.

    Not a number. A tab advances to a stop that depends on what came before it, so two columns reached through different mixes of tabs and spaces are only PARTIALLY ordered: one is more indented than another only when it extends the other's sequence. When neither extends the other, comparing them is an error the user has to be told about — see ColumnOrder::Incomparable.

    Plain is the case that happens on every line of every file anyone has written, and is kept apart so it costs no allocation. Tabbed holds the alternating run lengths — chars, tabs, chars, tabs, …, chars — LEFTMOST first, so a comparison is a straight walk from the start of the line and an append is a concatenation. (The reference stores the same list reversed; the order here is the one that makes both operations read forwards.)

    Every entry is in half-columns. A run of n tabs is stored as 2n. That uniformity is what makes the +0.5 convention safe: a | at column N takes part in comparisons as N.5, and storing tab counts in the same unit means the half-column tolerance in cmp can never be triggered by a difference of one tab, which it would if the two units were mixed.

    Column::add_chars

    fn Column::add_chars(self : Column, n : Int) -> Column

    This column with n more character widths on its right.

    Column::add_tab

    fn Column::add_tab(self : Column) -> Column

    This column with one tab on its right.

    The tab starts a new run, so everything before it becomes a fixed prefix that a later column must reproduce exactly to be comparable at all.

    Column::cmp

    fn Column::cmp(self : Column, other : Column) -> ColumnOrder

    Compare two columns.

    Walks both run lists from the start of the line. A list that runs out while the other continues is the smaller column — it is a prefix of the other, which is exactly what "less indented" means here.

    Where the two diverge, one case is tolerated and the rest are errors. The tolerated case is a half-column difference in the LAST run of the shorter side: that is a | sitting half a column before a tab, and the reference answers every comparison about it with false rather than complaining. Everything else is a genuine mixed-tab ambiguity, and the user is told.

    Column::eq

    fn Column::eq(self : Column, other : Column) -> Bool

    Column::floor

    fn Column::floor(self : Column) -> Column

    Round down to a whole character width.

    Column::ge

    fn Column::ge(self : Column, other : Column) -> Bool

    Column::gt

    fn Column::gt(self : Column, other : Column) -> Bool

    Column::half_next

    fn Column::half_next(self : Column) -> Column

    Half a column further right, or the next whole column if already halfway.

    The reference's column-half-next. It is not simply half_up: applying it twice must not reach 3.0 by way of 2.5, or a | under a | would line up with a block's content.

    Column::half_up

    fn Column::half_up(self : Column) -> Column

    This column plus half a character width — the | convention.

    A | at column N takes part in comparisons as N.5, which is what lets a block's content and an alternative's | sit at the same visual column and still be ordered against each other.

    Column::le

    fn Column::le(self : Column, other : Column) -> Bool

    self <= other, defined as the reference defines it: NOT other < self.

    That is not the same as Lt || Eq. When the pair is Unordered — a | half a column before a tab — other < self is false, so <= is true, while Lt || Eq would be false. The reference leans on that, so the definition has to be the negated one.

    Column::lt

    fn Column::lt(self : Column, other : Column) -> Bool

    self < other, in the reference's sense: Unordered and Incomparable are both false here, and only Incomparable is worth reporting.

    Column::next

    fn Column::next(self : Column) -> Column

    The next whole column after this one.

    Floors first: the column after 2.5 is 3, not 3.5. That is what makes a block opened by a | at 2.5 demand content at 3.

    Column::of_chars

    fn Column::of_chars(n : Int) -> Column

    A column n whole character widths from the start of the line.

    Column::plus

    fn Column::plus(self : Column, other : Column) -> Column

    other placed to the RIGHT of self: self is the base, other the offset added to it.

    The reference's column+, with the arguments in the order the phrase "column a plus b" suggests rather than the order the Racket takes them.

    Column::sub

    fn Column::sub(self : Column, other : Column) -> Int

    self - other in half-columns, or 0 when the two have different tab structure.

    Zero rather than an error because a difference across different tab structures has no meaning to report — the reference makes the same choice.

    Column::to_display

    fn Column::to_display(self : Column) -> String

    A readable column, for a diagnostic or a test failure.

    4 for a plain column, 4.5 when the | half is on it, and 2+tab+1 when there is a tab in the middle — which is the form that makes an "incomparable indentation" message explain itself.

    ColumnOrder

    pub(all) enum ColumnOrder {
    Lt
    Eq
    Gt
    Unordered
    Incomparable
    } derive(Eq,
    Debug
    )

    The result of comparing two columns.

    Five answers, not three. Incomparable is the user-facing "incomparable indentation due to mixed tabs" error, and making it a constructor is what stops a caller quietly defaulting it to Eq. Unordered is the reference's half-a-column-before-a-tab tolerance: every comparison of the pair is false, but it is not an error. Collapsing the two would either turn a tolerated case into a failure or a failure into silence.

    ColumnOrder::name

    fn ColumnOrder::name(self : ColumnOrder) -> String

    count_graphemes

    fn count_graphemes(text : String, lines? : Int, col? : Column) -> (Int, Column)

    Walk text from (lines, col), returning where it ends up.

    The reference's count-graphemes, and the only place a column is advanced over source text. Three things it must get right, all of which are silent when wrong:

    • \r\n is ONE line break, and a lone \r is one too.
    • A tab starts a new run rather than advancing to a fixed stop.
    • Everything else advances by one per EXTENDED GRAPHEME CLUSTER, not one per code point.

    It iterates clusters rather than asking for the next boundary at each position, which lets the segmenter make a single pass. That is sound because \r\n, a lone \r, a lone \n and a tab are each a cluster of their own under UAX #29, so no cluster ever straddles a case boundary here.

    zero

    let zero : Column

    Source Files