~/bend-docscommunity

EdlibTypes.bend source

EdlibTypes.bend on the hub · documented module

import Base

# Edlib sentinels are distinct constructors, never wrapped U32 values.
type Mode is Data:
  NW{}
  SHW{}
  HW{}

type Task is Data:
  Distance{}
  Locations{}
  Path{}

type Threshold is Data:
  Unlimited{}
  MaxDistance{value: Nat}

type EqualityPair is Data:
  EqualityPair{first: Char, second: Char}

type CigarFormat is Data:
  Standard{}
  Extended{}

type AlignConfig is Data:
  AlignConfig{mode: Mode, task: Task, threshold: Threshold,
    equalities: List<&2, EqualityPair>, cigar_format: CigarFormat}

type Status is Data:
  Ok{}
  Error{}

type EditDistance is Data:
  Found{value: Nat}
  NotFound{}

type Coordinate is Data:
  BeforeStart{}
  Position{index: Nat}

type Operation is Data:
  Match{}
  Insert{}
  Delete{}
  Mismatch{}

type AlignResult is Data:
  AlignResult{status: Status, distance: EditDistance,
    end_locations: List<&2, Coordinate>,
    start_locations: Maybe<&2, List<&2, Coordinate>>,
    alignment: Maybe<&2, List<&2, Operation>>, cigar: Maybe<&2, String>}

def default_config() -> AlignConfig:
  AlignConfig{NW{}, Distance{}, Unlimited{}, Nil{}, Extended{}}

def end_coordinate(length: Nat) -> Coordinate:
  match length:
    case 0n:
      BeforeStart{}
    case 1n+p:
      Position{p}

def distance_show(distance: EditDistance) -> String:
  match distance:
    case Found{value}:
      Nat.show(value)
    case NotFound{}:
      "-1"

def coordinate_show(position: Coordinate) -> String:
  match position:
    case BeforeStart{}:
      "-1"
    case Position{index}:
      Nat.show(index)

def status_show(status: Status) -> String:
  match status:
    case Ok{}:
      "0"
    case Error{}:
      "1"

def operation_show(operation: Operation) -> String:
  match operation:
    case Match{}:
      "0"
    case Insert{}:
      "1"
    case Delete{}:
      "2"
    case Mismatch{}:
      "3"

def operation_char(format: CigarFormat, operation: Operation) -> Char:
  match format operation:
    case _ Insert{}:
      'I'
    case _ Delete{}:
      'D'
    case Standard{} _:
      'M'
    case Extended{} Match{}:
      '='
    case Extended{} Mismatch{}:
      'X'

def cigar_step(rest: Unit -> String, symbol: Char, count: Nat, same: Bool) -> String:
  match same:
    case True{}:
      rest(Unit{})
    case False{}:
      Nat.show(count) ++ SCon{symbol, rest(Unit{})}

def cigar_runs(operations: List<&2, Operation>, +format: CigarFormat,
               +symbol: Char, +count: Nat) -> String:
  match operations:
    case Nil{}:
      Nat.show(count) ++ SCon{symbol, ""}
    case h <> t:
      +next = operation_char(format, h)
      cigar_step(_ => cigar_runs(t, format, next,
        Bool.pick(Nat, Char.is_eq(symbol, next), 1n+count, 1n)),
        symbol, count, Char.is_eq(symbol, next))

def alignment_to_cigar(operations: List<&2, Operation>, +format: CigarFormat) -> String:
  match operations:
    case Nil{}:
      ""
    case h <> t:
      cigar_runs(t, format, operation_char(format, h), 1n)

def coordinates_show(locations: List<&2, Coordinate>) -> String:
  List.show(~&2, ~Coordinate, ~coordinate_show, locations)

def starts_show(locations: Maybe<&2, List<&2, Coordinate>>) -> String:
  match locations:
    case None{}:
      "null"
    case Some{values}:
      coordinates_show(values)

def alignment_show(alignment: Maybe<&2, List<&2, Operation>>) -> String:
  match alignment:
    case None{}:
      "null"
    case Some{operations}:
      List.show(~&2, ~Operation, ~operation_show, operations)

# CIGAR contains only decimal counts and MID=X, so needs no JSON escaping.
def cigar_show(cigar: Maybe<&2, String>) -> String:
  match cigar:
    case None{}:
      "null"
    case Some{value}:
      "\"" ++ value ++ "\""

def result_show(result: AlignResult) -> String:
  match result:
    case AlignResult{status, distance, ends, starts, alignment, cigar}:
      "{\"status\":" ++ status_show(status) ++
      ",\"edit_distance\":" ++ distance_show(distance) ++
      ",\"end_locations\":" ++ coordinates_show(ends) ++
      ",\"start_locations\":" ++ starts_show(starts) ++
      ",\"alignment\":" ++ alignment_show(alignment) ++
      ",\"cigar\":" ++ cigar_show(cigar) ++ "}"

def not_found() -> AlignResult:
  AlignResult{Ok{}, NotFound{}, Nil{}, None{}, None{}, None{}}

def within_threshold(threshold: Threshold, distance: Nat) -> Bool:
  match threshold:
    case Unlimited{}:
      True{}
    case MaxDistance{maximum}:
      Nat.is_le(distance, maximum)

def distance_result(distance: Nat, end: Coordinate, accepted: Bool) -> AlignResult:
  match accepted:
    case True{}:
      AlignResult{Ok{}, Found{distance}, [end], None{}, None{}, None{}}
    case False{}:
      not_found()