EdlibTypes.bend

bend-edit-distance@0.1.0.0 · 183 lines · 0 definitions

1import Base
2
3# Edlib sentinels are distinct constructors, never wrapped U32 values.
4type Mode is Data:
5  NW{}
6  SHW{}
7  HW{}
8
9type Task is Data:
10  Distance{}
11  Locations{}
12  Path{}
13
14type Threshold is Data:
15  Unlimited{}
16  MaxDistance{value: Nat}
17
18type EqualityPair is Data:
19  EqualityPair{first: Char, second: Char}
20
21type CigarFormat is Data:
22  Standard{}
23  Extended{}
24
25type AlignConfig is Data:
26  AlignConfig{mode: Mode, task: Task, threshold: Threshold,
27    equalities: List<&2, EqualityPair>, cigar_format: CigarFormat}
28
29type Status is Data:
30  Ok{}
31  Error{}
32
33type EditDistance is Data:
34  Found{value: Nat}
35  NotFound{}
36
37type Coordinate is Data:
38  BeforeStart{}
39  Position{index: Nat}
40
41type Operation is Data:
42  Match{}
43  Insert{}
44  Delete{}
45  Mismatch{}
46
47type AlignResult is Data:
48  AlignResult{status: Status, distance: EditDistance,
49    end_locations: List<&2, Coordinate>,
50    start_locations: Maybe<&2, List<&2, Coordinate>>,
51    alignment: Maybe<&2, List<&2, Operation>>, cigar: Maybe<&2, String>}
52
53def default_config() -> AlignConfig:
54  AlignConfig{NW{}, Distance{}, Unlimited{}, Nil{}, Extended{}}
55
56def end_coordinate(length: Nat) -> Coordinate:
57  match length:
58    case 0n:
59      BeforeStart{}
60    case 1n+p:
61      Position{p}
62
63def distance_show(distance: EditDistance) -> String:
64  match distance:
65    case Found{value}:
66      Nat.show(value)
67    case NotFound{}:
68      "-1"
69
70def coordinate_show(position: Coordinate) -> String:
71  match position:
72    case BeforeStart{}:
73      "-1"
74    case Position{index}:
75      Nat.show(index)
76
77def status_show(status: Status) -> String:
78  match status:
79    case Ok{}:
80      "0"
81    case Error{}:
82      "1"
83
84def operation_show(operation: Operation) -> String:
85  match operation:
86    case Match{}:
87      "0"
88    case Insert{}:
89      "1"
90    case Delete{}:
91      "2"
92    case Mismatch{}:
93      "3"
94
95def operation_char(format: CigarFormat, operation: Operation) -> Char:
96  match format operation:
97    case _ Insert{}:
98      'I'
99    case _ Delete{}:
100      'D'
101    case Standard{} _:
102      'M'
103    case Extended{} Match{}:
104      '='
105    case Extended{} Mismatch{}:
106      'X'
107
108def cigar_step(rest: Unit -> String, symbol: Char, count: Nat, same: Bool) -> String:
109  match same:
110    case True{}:
111      rest(Unit{})
112    case False{}:
113      Nat.show(count) ++ SCon{symbol, rest(Unit{})}
114
115def cigar_runs(operations: List<&2, Operation>, +format: CigarFormat,
116               +symbol: Char, +count: Nat) -> String:
117  match operations:
118    case Nil{}:
119      Nat.show(count) ++ SCon{symbol, ""}
120    case h <> t:
121      +next = operation_char(format, h)
122      cigar_step(_ => cigar_runs(t, format, next,
123        Bool.pick(Nat, Char.is_eq(symbol, next), 1n+count, 1n)),
124        symbol, count, Char.is_eq(symbol, next))
125
126def alignment_to_cigar(operations: List<&2, Operation>, +format: CigarFormat) -> String:
127  match operations:
128    case Nil{}:
129      ""
130    case h <> t:
131      cigar_runs(t, format, operation_char(format, h), 1n)
132
133def coordinates_show(locations: List<&2, Coordinate>) -> String:
134  List.show(~&2, ~Coordinate, ~coordinate_show, locations)
135
136def starts_show(locations: Maybe<&2, List<&2, Coordinate>>) -> String:
137  match locations:
138    case None{}:
139      "null"
140    case Some{values}:
141      coordinates_show(values)
142
143def alignment_show(alignment: Maybe<&2, List<&2, Operation>>) -> String:
144  match alignment:
145    case None{}:
146      "null"
147    case Some{operations}:
148      List.show(~&2, ~Operation, ~operation_show, operations)
149
150# CIGAR contains only decimal counts and MID=X, so needs no JSON escaping.
151def cigar_show(cigar: Maybe<&2, String>) -> String:
152  match cigar:
153    case None{}:
154      "null"
155    case Some{value}:
156      "\"" ++ value ++ "\""
157
158def result_show(result: AlignResult) -> String:
159  match result:
160    case AlignResult{status, distance, ends, starts, alignment, cigar}:
161      "{\"status\":" ++ status_show(status) ++
162      ",\"edit_distance\":" ++ distance_show(distance) ++
163      ",\"end_locations\":" ++ coordinates_show(ends) ++
164      ",\"start_locations\":" ++ starts_show(starts) ++
165      ",\"alignment\":" ++ alignment_show(alignment) ++
166      ",\"cigar\":" ++ cigar_show(cigar) ++ "}"
167
168def not_found() -> AlignResult:
169  AlignResult{Ok{}, NotFound{}, Nil{}, None{}, None{}, None{}}
170
171def within_threshold(threshold: Threshold, distance: Nat) -> Bool:
172  match threshold:
173    case Unlimited{}:
174      True{}
175    case MaxDistance{maximum}:
176      Nat.is_le(distance, maximum)
177
178def distance_result(distance: Nat, end: Coordinate, accepted: Bool) -> AlignResult:
179  match accepted:
180    case True{}:
181      AlignResult{Ok{}, Found{distance}, [end], None{}, None{}, None{}}
182    case False{}:
183      not_found()