Bidi

Bidi :: # (opaque)

Retained paragraph analysis for Unicode 17 UAX #9. The retained tape keeps original scalar identities and coordinates; it deliberately never retains the input Str. Line reordering is a separate operation because L1 is defined at actual line boundaries.

paragraph_ranges : Str -> List(TextRange)

Split complete text according to P1. A paragraph separator belongs to the preceding returned range. CR LF is one separator (and therefore one preceding-paragraph suffix); a lone CR, lone LF, and U+001C..U+001E each terminate their preceding paragraph. No artificial empty paragraph is produced after a final separator.

analyze_range : Str, TextRange, BaseDirection, Limits -> Try(Analysis, Error)

Analyze one P1 paragraph selected from a complete source. Validation scans through the selected P1 boundary, then replays only the selected seamless slice for retained analysis; this explicit replay keeps global coordinates without retaining the source. Line ranges and visual mappings remain absolute to source.

matched_brackets : Analysis -> List([Some(U64), None])

Matched paired-bracket partner indices after BD16/N0, or None for an unmatched/non-bracket scalar. Indices always refer to original logical scalars, never an X9-filtered working buffer.

logical_runs : Analysis -> Iter(LevelRun)

Lazily walk maximal same-level spans in the X9-filtered logical sequence. Removed controls bridge equal-level runs but are not themselves yielded.

collect_logical_runs : Analysis -> List(LevelRun)

Explicit materializing convenience for clients that need every retained run at once. logical_runs is the primary, early-stoppable traversal.

reorder_line : Analysis, ScalarRange -> Try(LineOrder, Error)

L1/L2 for a scalar-aligned, paragraph-local logical line. The display permutation omits only X9 controls. LRI/RLI/FSI/PDI remain in it, marked non_rendering in the analysis for renderers that do not draw controls.

Error : [ScalarLimitExceeded({ limit : U64, required : U64, stage : LimitStage, range : TextRange }), ByteLimitExceeded({ limit : U64, required : U64, stage : LimitStage, range : ByteRange }), MultipleParagraphs, InvalidParagraphRange(TextRange), LineOutOfBounds({ requested : ScalarRange, paragraph : ScalarRange })]

Limit errors identify the operation before the retained tape is committed. Their paragraph range is in the caller's original source coordinates.

ScalarInfo : { range : TextRange, scalar : Scalar, original_class : Value, working_class : Value, level : [Some(U8), None], matched_bracket : [Some(U64), None], non_rendering : Bool, needs_mirrored_glyph : Bool, mirroring_glyph : [Some(Scalar), None] }
LevelRun : { range : TextRange, level : U8, direction : Direction }
VisualRun : { logical_range : ScalarRange, level : U8, direction : Direction }

Analysis

:= { requested_base : BaseDirection, paragraph_range : TextRange, paragraph_level : U8, entries : List(ScalarInfo), byte_len : U64, scalar_len : U64 }

LineOrder

:= { line_range : ScalarRange, levels : List([Some(U8), None]), visual_to_logical : List(U64), logical_to_visual : List([Some(U64), None]), visual_runs : List(VisualRun), mirroring : List(MirrorInfo) }

RunSequences

:= { links : List([Some(U64), None]), starts : List(U64), sos : List(Value), eos : List(Value) }

Private intermediate links for the X10/N0 implementation. No public operation exposes this value.