nx_sam_test.nx source
↩ module page · 519 lines · 24626 B
1// nx_sam_test.nx -- KAT for SAM v1.4.6 single-line writer.
2//
3// expect_exit: 0
4//
5// license_tier: ORIGINAL
6
7import "nx_syscalls.nx"
8import "nx_const.nx"
9import "nx_sam.nx"
10
11func main() -> i64 {
12
13 // ============================================================
14 // Section A -- nx_emit_signed_int.
15 // ============================================================
16
17 let buf: *u8 = sys_mmap(64)
18
19 let n1: i64 = nx_emit_signed_int(42, buf, 0, 64)
20 if n1 != 2 { return 1 }
21 if (buf[0] & 0xff) != 0x34 { return 2 } // '4'
22 if (buf[1] & 0xff) != 0x32 { return 3 } // '2'
23
24 let n2: i64 = nx_emit_signed_int(-300, buf, 0, 64)
25 if n2 != 4 { return 4 }
26 if (buf[0] & 0xff) != 0x2D { return 5 } // '-'
27 if (buf[1] & 0xff) != 0x33 { return 6 } // '3'
28 if (buf[2] & 0xff) != 0x30 { return 7 } // '0'
29 if (buf[3] & 0xff) != 0x30 { return 8 } // '0'
30
31 let n3: i64 = nx_emit_signed_int(0, buf, 0, 64)
32 if n3 != 1 { return 9 }
33 if (buf[0] & 0xff) != 0x30 { return 10 }
34
35 // ============================================================
36 // Section B -- canonical SAM line.
37 //
38 // QNAME="r1", FLAG=0, RNAME="chr1", POS=12345, MAPQ=60,
39 // CIGAR="3M", RNEXT="*", PNEXT=0, TLEN=0, SEQ="ACG", QUAL="III"
40 //
41 // Expected: "r1\t0\tchr1\t12345\t60\t3M\t*\t0\t0\tACG\tIII\n"
42 // Length: r1(2) + \t + 0(1) + \t + chr1(4) + \t + 12345(5) + \t
43 // + 60(2) + \t + 3M(2) + \t + *(1) + \t + 0(1) + \t + 0(1)
44 // + \t + ACG(3) + \t + III(3) + \n
45 // = 2+1+1+1+4+1+5+1+2+1+2+1+1+1+1+1+1+1+3+1+3+1 = 34
46 // ============================================================
47
48 // Build concat'd str_buf: "r1" + "chr1" + "3M" + "ACG" + "III"
49 // = 2 + 4 + 2 + 3 + 3 = 14 bytes
50 let str_buf: *u8 = sys_mmap(64)
51 str_buf[0] = 0x72; str_buf[1] = 0x31 // r1
52 str_buf[2] = 0x63; str_buf[3] = 0x68; str_buf[4] = 0x72; str_buf[5] = 0x31 // chr1
53 str_buf[6] = 0x33; str_buf[7] = 0x4D // 3M
54 str_buf[8] = 0x41; str_buf[9] = 0x43; str_buf[10] = 0x47 // ACG
55 str_buf[11] = 0x49; str_buf[12] = 0x49; str_buf[13] = 0x49 // III (Phred Q=40)
56
57 let str_lens: *i64 = sys_mmap(64) as *i64
58 str_lens[NX_SAM_STR_QNAME] = 2
59 str_lens[NX_SAM_STR_RNAME] = 4
60 str_lens[NX_SAM_STR_CIGAR] = 2
61 str_lens[NX_SAM_STR_SEQ] = 3
62
63 let fields: *i64 = sys_mmap(64) as *i64
64 fields[NX_SAM_FIELD_FLAG] = 0
65 fields[NX_SAM_FIELD_POS] = 12345
66 fields[NX_SAM_FIELD_MAPQ] = 60
67 fields[NX_SAM_FIELD_TLEN] = 0
68
69 let out_buf: *u8 = sys_mmap(128)
70 let n: i64 = sam_write_line(str_buf, str_lens, fields, out_buf, 128)
71 if n != 36 { return n + 100 }
72
73 // Byte-by-byte layout (36 bytes, indices 0..35):
74 // 0:r 1:1 2:\t 3:0 4:\t 5:c 6:h 7:r 8:1 9:\t
75 // 10:1 11:2 12:3 13:4 14:5 15:\t 16:6 17:0 18:\t 19:3
76 // 20:M 21:\t 22:* 23:\t 24:0 25:\t 26:0 27:\t 28:A 29:C
77 // 30:G 31:\t 32:I 33:I 34:I 35:\n
78
79 if (out_buf[0] & 0xff) != 0x72 { return 50 } // r
80 if (out_buf[1] & 0xff) != 0x31 { return 51 } // 1
81 if (out_buf[2] & 0xff) != 0x09 { return 52 } // \t
82 if (out_buf[3] & 0xff) != 0x30 { return 53 } // FLAG '0'
83 if (out_buf[4] & 0xff) != 0x09 { return 54 }
84 if (out_buf[5] & 0xff) != 0x63 { return 55 } // c of chr1
85 if (out_buf[8] & 0xff) != 0x31 { return 56 } // 1 of chr1
86 if (out_buf[9] & 0xff) != 0x09 { return 57 }
87 if (out_buf[14] & 0xff) != 0x35 { return 58 } // '5' end of 12345
88 if (out_buf[16] & 0xff) != 0x36 { return 59 } // MAPQ '6'
89 if (out_buf[19] & 0xff) != 0x33 { return 60 } // CIGAR '3'
90 if (out_buf[20] & 0xff) != 0x4D { return 61 } // CIGAR 'M'
91 if (out_buf[22] & 0xff) != 0x2A { return 62 } // RNEXT '*'
92 if (out_buf[24] & 0xff) != 0x30 { return 63 } // PNEXT '0'
93 if (out_buf[26] & 0xff) != 0x30 { return 64 } // TLEN '0'
94 if (out_buf[28] & 0xff) != 0x41 { return 65 } // SEQ 'A'
95 if (out_buf[30] & 0xff) != 0x47 { return 66 } // SEQ 'G'
96 if (out_buf[32] & 0xff) != 0x49 { return 67 } // QUAL 'I'
97 if (out_buf[34] & 0xff) != 0x49 { return 68 } // QUAL 'I' last
98 if (out_buf[35] & 0xff) != 0x0A { return 69 } // \n
99
100 // ============================================================
101 // Section C -- negative TLEN (paired-end mate downstream).
102 // FLAG=99, RNAME chr1, POS=200, MAPQ=60, CIGAR=3M, TLEN=-150,
103 // SEQ=ACG, QUAL=III.
104 // Expected: tlen field is "-150" (4 bytes).
105 // ============================================================
106
107 fields[NX_SAM_FIELD_FLAG] = 99
108 fields[NX_SAM_FIELD_POS] = 200
109 fields[NX_SAM_FIELD_TLEN] = 0 - 150
110
111 let n_neg: i64 = sam_write_line(str_buf, str_lens, fields, out_buf, 128)
112 if n_neg < 0 { return 80 }
113 // Delta vs canonical: FLAG "0"->"99" (+1), POS "12345"->"200" (-2),
114 // TLEN "0"->"-150" (+3). Net = +2. Total = 36 + 2 = 38.
115 if n_neg != 38 { return n_neg + 200 }
116
117 // ============================================================
118 // Section D -- bad inputs.
119 // ============================================================
120
121 // Reset fields to canonical for next checks.
122 fields[NX_SAM_FIELD_POS] = 12345
123 fields[NX_SAM_FIELD_TLEN] = 0
124
125 // Empty QNAME -> -1.
126 str_lens[NX_SAM_STR_QNAME] = 0
127 if sam_write_line(str_buf, str_lens, fields, out_buf, 128) != -1 { return 90 }
128 str_lens[NX_SAM_STR_QNAME] = 2
129
130 // POS < 1 -> -1.
131 fields[NX_SAM_FIELD_POS] = 0
132 if sam_write_line(str_buf, str_lens, fields, out_buf, 128) != -1 { return 91 }
133 fields[NX_SAM_FIELD_POS] = 12345
134
135 // Capacity overflow -> -1.
136 if sam_write_line(str_buf, str_lens, fields, out_buf, 10) != -1 { return 92 }
137
138 // ============================================================
139 // Section E -- sam_write_hd_line. Fixed 25-byte output.
140 // ============================================================
141
142 let hd_buf: *u8 = sys_mmap(64)
143 let n_hd: i64 = sam_write_hd_line(hd_buf, 64)
144 if n_hd != 25 { return n_hd + 300 }
145
146 // Byte-by-byte:
147 // 0:@ 1:H 2:D 3:\t 4:V 5:N 6:: 7:1 8:. 9:6 10:\t
148 // 11:S 12:O 13:: 14:c 15:o 16:o 17:r 18:d 19:i 20:n 21:a 22:t 23:e 24:\n
149 if (hd_buf[0] & 0xff) != 0x40 { return 100 } // @
150 if (hd_buf[1] & 0xff) != 0x48 { return 101 } // H
151 if (hd_buf[2] & 0xff) != 0x44 { return 102 } // D
152 if (hd_buf[3] & 0xff) != 0x09 { return 103 } // \t
153 if (hd_buf[4] & 0xff) != 0x56 { return 104 } // V
154 if (hd_buf[7] & 0xff) != 0x31 { return 105 } // 1
155 if (hd_buf[9] & 0xff) != 0x36 { return 106 } // 6
156 if (hd_buf[11] & 0xff) != 0x53 { return 107 } // S
157 if (hd_buf[14] & 0xff) != 0x63 { return 108 } // c (coord)
158 if (hd_buf[23] & 0xff) != 0x65 { return 109 } // e (end of coordinate)
159 if (hd_buf[24] & 0xff) != 0x0A { return 110 } // \n
160
161 // Capacity overflow.
162 if sam_write_hd_line(hd_buf, 24) != -1 { return 111 }
163
164 // ============================================================
165 // Section F -- sam_write_sq_line.
166 // "@SQ\tSN:chr1\tLN:248956422\n"
167 // Length: 3 + 1 + 3 + 4 + 1 + 3 + 9 + 1 = 25 bytes
168 // ============================================================
169
170 let chr1: *u8 = sys_mmap(8)
171 chr1[0]=0x63; chr1[1]=0x68; chr1[2]=0x72; chr1[3]=0x31
172
173 let sq_buf: *u8 = sys_mmap(64)
174 let n_sq: i64 = sam_write_sq_line(chr1, 4, 248956422, sq_buf, 64)
175 if n_sq != 25 { return n_sq + 400 }
176
177 // 0:@ 1:S 2:Q 3:\t 4:S 5:N 6:: 7:c 8:h 9:r 10:1 11:\t
178 // 12:L 13:N 14:: 15:2 16:4 17:8 18:9 19:5 20:6 21:4 22:2 23:2 24:\n
179 if (sq_buf[0] & 0xff) != 0x40 { return 120 } // @
180 if (sq_buf[1] & 0xff) != 0x53 { return 121 } // S
181 if (sq_buf[2] & 0xff) != 0x51 { return 122 } // Q
182 if (sq_buf[3] & 0xff) != 0x09 { return 123 } // \t
183 if (sq_buf[4] & 0xff) != 0x53 { return 124 } // S
184 if (sq_buf[6] & 0xff) != 0x3A { return 125 } // :
185 if (sq_buf[7] & 0xff) != 0x63 { return 126 } // c of chr1
186 if (sq_buf[10] & 0xff) != 0x31 { return 127 } // 1 of chr1
187 if (sq_buf[11] & 0xff) != 0x09 { return 128 } // \t
188 if (sq_buf[12] & 0xff) != 0x4C { return 129 } // L
189 if (sq_buf[14] & 0xff) != 0x3A { return 130 } // :
190 if (sq_buf[15] & 0xff) != 0x32 { return 131 } // 2 (start of length)
191 if (sq_buf[23] & 0xff) != 0x32 { return 132 } // 2 (end of length)
192 if (sq_buf[24] & 0xff) != 0x0A { return 133 } // \n
193
194 // Smaller contig: "chrM" length=16569 -> "@SQ\tSN:chrM\tLN:16569\n" = 21 bytes
195 let chrM: *u8 = sys_mmap(8)
196 chrM[0]=0x63; chrM[1]=0x68; chrM[2]=0x72; chrM[3]=0x4D
197 let n_sq_m: i64 = sam_write_sq_line(chrM, 4, 16569, sq_buf, 64)
198 if n_sq_m != 21 { return n_sq_m + 500 }
199
200 // Bad inputs.
201 if sam_write_sq_line(chr1, 0, 100, sq_buf, 64) != -1 { return 140 } // empty chrom
202 if sam_write_sq_line(chr1, 4, 0, sq_buf, 64) != -1 { return 141 } // length=0
203 if sam_write_sq_line(chr1, 4, 100, sq_buf, 5) != -1 { return 142 } // capacity
204
205 // ============================================================
206 // Section G -- sam_write_pg_line.
207 // id="nx_align" (8), name="nx_align" (8), version="0.1.0" (5).
208 // Expected: "@PG\tID:nx_align\tPN:nx_align\tVN:0.1.0\n"
209 // Length: 3 + 1 + 3 + 8 + 1 + 3 + 8 + 1 + 3 + 5 + 1 = 37 bytes
210 // ============================================================
211
212 // str_buf = "nx_align" + "nx_align" + "0.1.0" = 8 + 8 + 5 = 21 bytes
213 let pg_strs: *u8 = sys_mmap(64)
214 pg_strs[0]=0x6E; pg_strs[1]=0x78; pg_strs[2]=0x5F // nx_
215 pg_strs[3]=0x61; pg_strs[4]=0x6C; pg_strs[5]=0x69; pg_strs[6]=0x67; pg_strs[7]=0x6E // align
216 pg_strs[8]=0x6E; pg_strs[9]=0x78; pg_strs[10]=0x5F // nx_
217 pg_strs[11]=0x61; pg_strs[12]=0x6C; pg_strs[13]=0x69; pg_strs[14]=0x67; pg_strs[15]=0x6E // align
218 pg_strs[16]=0x30; pg_strs[17]=0x2E; pg_strs[18]=0x31; pg_strs[19]=0x2E; pg_strs[20]=0x30 // 0.1.0
219
220 let pg_lens: *i64 = sys_mmap(64) as *i64
221 pg_lens[NX_SAM_PG_ID] = 8
222 pg_lens[NX_SAM_PG_NAME] = 8
223 pg_lens[NX_SAM_PG_VERSION] = 5
224
225 let pg_buf: *u8 = sys_mmap(128)
226 let n_pg: i64 = sam_write_pg_line(pg_strs, pg_lens, pg_buf, 128)
227 if n_pg != 37 { return n_pg + 600 }
228
229 // Spot-check key positions:
230 // 0:@ 1:P 2:G 3:\t 4:I 5:D 6:: 7:n 8:x 9:_ 10:a 11:l 12:i 13:g 14:n
231 // 15:\t 16:P 17:N 18:: 19-26:nx_align 27:\t 28:V 29:N 30::
232 // 31:0 32:. 33:1 34:. 35:0 36:\n
233 if (pg_buf[0] & 0xff) != 0x40 { return 150 } // @
234 if (pg_buf[1] & 0xff) != 0x50 { return 151 } // P
235 if (pg_buf[2] & 0xff) != 0x47 { return 152 } // G
236 if (pg_buf[3] & 0xff) != 0x09 { return 153 } // \t
237 if (pg_buf[4] & 0xff) != 0x49 { return 154 } // I
238 if (pg_buf[5] & 0xff) != 0x44 { return 155 } // D
239 if (pg_buf[6] & 0xff) != 0x3A { return 156 } // :
240 if (pg_buf[7] & 0xff) != 0x6E { return 157 } // n of nx_align
241 if (pg_buf[14] & 0xff) != 0x6E { return 158 } // n end of nx_align
242 if (pg_buf[15] & 0xff) != 0x09 { return 159 } // \t
243 if (pg_buf[16] & 0xff) != 0x50 { return 160 } // P
244 if (pg_buf[17] & 0xff) != 0x4E { return 161 } // N
245 if (pg_buf[18] & 0xff) != 0x3A { return 162 } // :
246 if (pg_buf[27] & 0xff) != 0x09 { return 163 } // \t before VN
247 if (pg_buf[28] & 0xff) != 0x56 { return 164 } // V
248 if (pg_buf[29] & 0xff) != 0x4E { return 165 } // N
249 if (pg_buf[31] & 0xff) != 0x30 { return 166 } // 0 of version
250 if (pg_buf[35] & 0xff) != 0x30 { return 167 } // 0 end of version
251 if (pg_buf[36] & 0xff) != 0x0A { return 168 } // \n
252
253 // Bad inputs.
254 pg_lens[NX_SAM_PG_ID] = 0
255 if sam_write_pg_line(pg_strs, pg_lens, pg_buf, 128) != -1 { return 170 }
256 pg_lens[NX_SAM_PG_ID] = 8
257
258 // ============================================================
259 // Section H -- sam_write_rg_line.
260 // id="rg1" (3) + sample="patient_001" (11) -> str_buf 14 bytes.
261 // Expected: "@RG\tID:rg1\tSM:patient_001\n"
262 // Length: 3 + 1 + 3 + 3 + 1 + 3 + 11 + 1 = 26 bytes
263 // ============================================================
264
265 let rg_strs: *u8 = sys_mmap(64)
266 rg_strs[0]=0x72; rg_strs[1]=0x67; rg_strs[2]=0x31 // rg1
267 rg_strs[3]=0x70; rg_strs[4]=0x61; rg_strs[5]=0x74; rg_strs[6]=0x69 // pati
268 rg_strs[7]=0x65; rg_strs[8]=0x6E; rg_strs[9]=0x74 // ent
269 rg_strs[10]=0x5F // _
270 rg_strs[11]=0x30; rg_strs[12]=0x30; rg_strs[13]=0x31 // 001
271
272 let rg_lens: *i64 = sys_mmap(64) as *i64
273 rg_lens[NX_SAM_RG_ID] = 3
274 rg_lens[NX_SAM_RG_SAMPLE] = 11
275
276 let rg_buf: *u8 = sys_mmap(128)
277 let n_rg: i64 = sam_write_rg_line(rg_strs, rg_lens, rg_buf, 128)
278 if n_rg != 26 { return n_rg + 700 }
279
280 // Byte-by-byte:
281 // 0:@ 1:R 2:G 3:\t 4:I 5:D 6:: 7:r 8:g 9:1 10:\t
282 // 11:S 12:M 13:: 14:p 15:a 16:t 17:i 18:e 19:n 20:t 21:_
283 // 22:0 23:0 24:1 25:\n
284 if (rg_buf[0] & 0xff) != 0x40 { return 180 } // @
285 if (rg_buf[1] & 0xff) != 0x52 { return 181 } // R
286 if (rg_buf[2] & 0xff) != 0x47 { return 182 } // G
287 if (rg_buf[3] & 0xff) != 0x09 { return 183 } // \t
288 if (rg_buf[4] & 0xff) != 0x49 { return 184 } // I
289 if (rg_buf[5] & 0xff) != 0x44 { return 185 } // D
290 if (rg_buf[6] & 0xff) != 0x3A { return 186 } // :
291 if (rg_buf[7] & 0xff) != 0x72 { return 187 } // r
292 if (rg_buf[9] & 0xff) != 0x31 { return 188 } // 1
293 if (rg_buf[10] & 0xff) != 0x09 { return 189 } // \t
294 if (rg_buf[11] & 0xff) != 0x53 { return 190 } // S
295 if (rg_buf[12] & 0xff) != 0x4D { return 191 } // M
296 if (rg_buf[13] & 0xff) != 0x3A { return 192 } // :
297 if (rg_buf[14] & 0xff) != 0x70 { return 193 } // p (patient)
298 if (rg_buf[21] & 0xff) != 0x5F { return 194 } // _
299 if (rg_buf[24] & 0xff) != 0x31 { return 195 } // 1 (end of 001)
300 if (rg_buf[25] & 0xff) != 0x0A { return 196 } // \n
301
302 // Bad input.
303 rg_lens[NX_SAM_RG_ID] = 0
304 if sam_write_rg_line(rg_strs, rg_lens, rg_buf, 128) != -1 { return 197 }
305
306 // ============================================================
307 // Section I -- sam_append_tag_int (G6.0c).
308 // Append "\tNM:i:5" (7 bytes) to a buffer starting at off=0.
309 // ============================================================
310
311 let tag_buf: *u8 = sys_mmap(128)
312 let off1: i64 = sam_append_tag_int(tag_buf, 0, 128, 0x4E, 0x4D, 5)
313 if off1 != 7 { return off1 + 800 }
314 // \t N M : i : 5
315 if (tag_buf[0] & 0xff) != 0x09 { return 200 } // \t
316 if (tag_buf[1] & 0xff) != 0x4E { return 201 } // N
317 if (tag_buf[2] & 0xff) != 0x4D { return 202 } // M
318 if (tag_buf[3] & 0xff) != 0x3A { return 203 } // :
319 if (tag_buf[4] & 0xff) != 0x69 { return 204 } // i
320 if (tag_buf[5] & 0xff) != 0x3A { return 205 } // :
321 if (tag_buf[6] & 0xff) != 0x35 { return 206 } // 5
322
323 // Chain: append AS:i:48 after. Expected: "\tNM:i:5\tAS:i:48"
324 let off2: i64 = sam_append_tag_int(tag_buf, off1, 128, 0x41, 0x53, 48)
325 if off2 != 15 { return off2 + 850 } // 7 (NM tag) + 8 (\tAS:i:48)
326 if (tag_buf[7] & 0xff) != 0x09 { return 210 } // \t
327 if (tag_buf[8] & 0xff) != 0x41 { return 211 } // A
328 if (tag_buf[9] & 0xff) != 0x53 { return 212 } // S
329 if (tag_buf[10] & 0xff) != 0x3A { return 213 } // :
330 if (tag_buf[11] & 0xff) != 0x69 { return 214 } // i
331 if (tag_buf[12] & 0xff) != 0x3A { return 215 } // :
332 if (tag_buf[13] & 0xff) != 0x34 { return 216 } // 4
333 if (tag_buf[14] & 0xff) != 0x38 { return 217 } // 8
334
335 // Negative value: NM:i:-3 -> "\tNM:i:-3" = 8 bytes
336 let tag_buf2: *u8 = sys_mmap(128)
337 let off_neg: i64 = sam_append_tag_int(tag_buf2, 0, 128, 0x4E, 0x4D, 0 - 3)
338 if off_neg != 8 { return off_neg + 900 }
339 if (tag_buf2[6] & 0xff) != 0x2D { return 220 } // '-'
340 if (tag_buf2[7] & 0xff) != 0x33 { return 221 } // '3'
341
342 // Multi-digit value: AS:i:12345 -> "\tAS:i:12345" = 11 bytes
343 let off_big: i64 = sam_append_tag_int(tag_buf, 0, 128, 0x41, 0x53, 12345)
344 if off_big != 11 { return off_big + 950 }
345
346 // Capacity overflow.
347 if sam_append_tag_int(tag_buf, 0, 5, 0x4E, 0x4D, 5) != -1 { return 230 }
348
349 // ============================================================
350 // Section J -- sam_append_tag_str (G6.0c.2).
351 // Append "\tRG:Z:rg1" (9 bytes) starting at off=0.
352 // ============================================================
353
354 let rg_id: *u8 = sys_mmap(8)
355 rg_id[0]=0x72; rg_id[1]=0x67; rg_id[2]=0x31 // "rg1"
356
357 let str_buf2: *u8 = sys_mmap(128)
358 let s1: i64 = sam_append_tag_str(str_buf2, 0, 128, 0x52, 0x47, rg_id, 3)
359 if s1 != 9 { return s1 + 950 }
360 // \t R G : Z : r g 1
361 if (str_buf2[0] & 0xff) != 0x09 { return 240 }
362 if (str_buf2[1] & 0xff) != 0x52 { return 241 } // R
363 if (str_buf2[2] & 0xff) != 0x47 { return 242 } // G
364 if (str_buf2[3] & 0xff) != 0x3A { return 243 } // :
365 if (str_buf2[4] & 0xff) != 0x5A { return 244 } // Z
366 if (str_buf2[5] & 0xff) != 0x3A { return 245 } // :
367 if (str_buf2[6] & 0xff) != 0x72 { return 246 } // r
368 if (str_buf2[7] & 0xff) != 0x67 { return 247 } // g
369 if (str_buf2[8] & 0xff) != 0x31 { return 248 } // 1
370
371 // Chain: append MD:Z:5T2A after. Total = 9 + (1 + 2 + 3 + 5) = 20 bytes.
372 let md_str: *u8 = sys_mmap(8)
373 md_str[0]=0x35; md_str[1]=0x54; md_str[2]=0x32; md_str[3]=0x41 // "5T2A"
374 // wait that's only 4 bytes. Recount: "5T2A" = 5(1) T(1) 2(1) A(1) = 4 bytes
375 let s2: i64 = sam_append_tag_str(str_buf2, s1, 128, 0x4D, 0x44, md_str, 4)
376 // Append adds 1(\t) + 2(MD) + 1(:) + 1(Z) + 1(:) + 4(5T2A) = 10 bytes
377 // Total = 9 + 10 = 19
378 if s2 != 19 { return s2 + 980 }
379 if (str_buf2[9] & 0xff) != 0x09 { return 260 } // \t
380 if (str_buf2[10] & 0xff) != 0x4D { return 261 } // M
381 if (str_buf2[11] & 0xff) != 0x44 { return 262 } // D
382 if (str_buf2[12] & 0xff) != 0x3A { return 263 } // :
383 if (str_buf2[13] & 0xff) != 0x5A { return 264 } // Z
384 if (str_buf2[14] & 0xff) != 0x3A { return 265 } // :
385 if (str_buf2[15] & 0xff) != 0x35 { return 266 } // 5
386 if (str_buf2[18] & 0xff) != 0x41 { return 267 } // A (end)
387
388 // Empty value not allowed (spec).
389 // Actually the spec is ambiguous; some tools accept it. G6.0c.2
390 // permits empty string (str_len=0); test that the 6-byte prefix
391 // emits cleanly.
392 let str_buf3: *u8 = sys_mmap(128)
393 let s_empty: i64 = sam_append_tag_str(str_buf3, 0, 128, 0x58, 0x58, rg_id, 0)
394 if s_empty != 6 { return s_empty + 990 } // "\tXX:Z:" = 6 bytes
395
396 // Capacity overflow.
397 if sam_append_tag_str(str_buf2, 0, 5, 0x52, 0x47, rg_id, 3) != -1 { return 270 }
398
399 // Negative str_len rejected.
400 if sam_append_tag_str(str_buf2, 0, 128, 0x52, 0x47, rg_id, -1) != -1 { return 271 }
401
402 // ============================================================
403 // Section G -- paired-end SAM line writer (G6.0b).
404 //
405 // Canonical proper-pair FR read:
406 // QNAME=r1, FLAG=99, RNAME=chr1, POS=100, MAPQ=60, CIGAR=3M,
407 // RNEXT="=", PNEXT=250, TLEN=153, SEQ=ACG, QUAL=III
408 //
409 // Expected: "r1\t99\tchr1\t100\t60\t3M\t=\t250\t153\tACG\tIII\n"
410 // Length: r1(2)+\t+99(2)+\t+chr1(4)+\t+100(3)+\t+60(2)+\t+3M(2)
411 // +\t+=(1)+\t+250(3)+\t+153(3)+\t+ACG(3)+\t+III(3)+\n
412 // = 2+1+2+1+4+1+3+1+2+1+2+1+1+1+3+1+3+1+3+1+3+1 = 39
413 // ============================================================
414
415 // str_buf_pe = "r1" + "chr1" + "=" + "3M" + "ACG" + "III"
416 // = 2 + 4 + 1 + 2 + 3 + 3 = 15 bytes
417 let str_buf_pe: *u8 = sys_mmap(64)
418 str_buf_pe[0]=0x72; str_buf_pe[1]=0x31 // r1
419 str_buf_pe[2]=0x63; str_buf_pe[3]=0x68; str_buf_pe[4]=0x72; str_buf_pe[5]=0x31 // chr1
420 str_buf_pe[6]=0x3D // =
421 str_buf_pe[7]=0x33; str_buf_pe[8]=0x4D // 3M
422 str_buf_pe[9]=0x41; str_buf_pe[10]=0x43; str_buf_pe[11]=0x47 // ACG
423 str_buf_pe[12]=0x49; str_buf_pe[13]=0x49; str_buf_pe[14]=0x49 // III
424
425 let str_lens_pe: *i64 = sys_mmap(8 * 5) as *i64
426 str_lens_pe[0] = 2 // qname
427 str_lens_pe[1] = 4 // rname
428 str_lens_pe[2] = 1 // rnext "="
429 str_lens_pe[3] = 2 // cigar
430 str_lens_pe[4] = 3 // seq (qual implicit)
431
432 let fields_pe: *i64 = sys_mmap(8 * 5) as *i64
433 fields_pe[0] = 99 // flag
434 fields_pe[1] = 100 // pos
435 fields_pe[2] = 60 // mapq
436 fields_pe[3] = 250 // pnext
437 fields_pe[4] = 153 // tlen
438
439 let out_pe: *u8 = sys_mmap(128)
440 let pe_n: i64 = sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128)
441 if pe_n != 39 { return pe_n + 800 }
442
443 // Byte-by-byte:
444 // 0:r 1:1 2:\t 3:9 4:9 5:\t 6:c 7:h 8:r 9:1 10:\t
445 // 11:1 12:0 13:0 14:\t 15:6 16:0 17:\t 18:3 19:M 20:\t
446 // 21:= 22:\t 23:2 24:5 25:0 26:\t 27:1 28:5 29:3 30:\t
447 // 31:A 32:C 33:G 34:\t 35:I 36:I 37:I (then \n at 38)
448 if (out_pe[0] & 0xff) != 0x72 { return 300 } // r
449 if (out_pe[1] & 0xff) != 0x31 { return 301 } // 1
450 if (out_pe[2] & 0xff) != 0x09 { return 302 } // \t
451 if (out_pe[3] & 0xff) != 0x39 { return 303 } // 9
452 if (out_pe[4] & 0xff) != 0x39 { return 304 } // 9
453 if (out_pe[5] & 0xff) != 0x09 { return 305 } // \t
454 if (out_pe[6] & 0xff) != 0x63 { return 306 } // c
455 if (out_pe[10] & 0xff) != 0x09 { return 307 } // \t
456 if (out_pe[11] & 0xff) != 0x31 { return 308 } // 1 (pos)
457 if (out_pe[13] & 0xff) != 0x30 { return 309 } // 0
458 if (out_pe[14] & 0xff) != 0x09 { return 310 } // \t
459 if (out_pe[15] & 0xff) != 0x36 { return 311 } // 6
460 if (out_pe[16] & 0xff) != 0x30 { return 312 } // 0
461 if (out_pe[18] & 0xff) != 0x33 { return 313 } // 3 (cigar)
462 if (out_pe[19] & 0xff) != 0x4D { return 314 } // M
463 if (out_pe[20] & 0xff) != 0x09 { return 315 } // \t
464 if (out_pe[21] & 0xff) != 0x3D { return 316 } // = (rnext)
465 if (out_pe[22] & 0xff) != 0x09 { return 317 } // \t
466 if (out_pe[23] & 0xff) != 0x32 { return 318 } // 2 (pnext)
467 if (out_pe[24] & 0xff) != 0x35 { return 319 } // 5
468 if (out_pe[25] & 0xff) != 0x30 { return 320 } // 0
469 if (out_pe[26] & 0xff) != 0x09 { return 321 } // \t
470 if (out_pe[27] & 0xff) != 0x31 { return 322 } // 1 (tlen)
471 if (out_pe[28] & 0xff) != 0x35 { return 323 } // 5
472 if (out_pe[29] & 0xff) != 0x33 { return 324 } // 3
473 if (out_pe[30] & 0xff) != 0x09 { return 325 } // \t
474 if (out_pe[31] & 0xff) != 0x41 { return 326 } // A
475 if (out_pe[34] & 0xff) != 0x09 { return 327 } // \t
476 if (out_pe[37] & 0xff) != 0x49 { return 328 } // I
477
478 // Negative TLEN (rightmost mate of pair) -- replace pos/pnext for clarity.
479 fields_pe[1] = 253 // pos (rightmost mate)
480 fields_pe[3] = 100 // pnext (back to leftmost)
481 fields_pe[4] = -153 // tlen negative
482 let pe_n2: i64 = sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128)
483 // "r1\t99\tchr1\t253\t60\t3M\t=\t100\t-153\tACG\tIII\n"
484 // = 2+1+2+1+4+1+3+1+2+1+2+1+1+1+3+1+4+1+3+1+3+1 = 40
485 if pe_n2 != 40 { return pe_n2 + 850 }
486 if (out_pe[27] & 0xff) != 0x2D { return 340 } // '-' for negative tlen
487 if (out_pe[28] & 0xff) != 0x31 { return 341 } // 1
488 if (out_pe[29] & 0xff) != 0x35 { return 342 } // 5
489 if (out_pe[30] & 0xff) != 0x33 { return 343 } // 3
490
491 // TLEN=0 (mate unmapped / different chr).
492 fields_pe[4] = 0
493 let pe_n3: i64 = sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128)
494 // tlen "0" = 1 byte (instead of 3-char "153") → 39 - 2 = 37
495 if pe_n3 != 37 { return pe_n3 + 870 }
496
497 // Capacity overflow.
498 fields_pe[1] = 100
499 fields_pe[3] = 250
500 fields_pe[4] = 153
501 if sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 10) != -1 { return 360 }
502
503 // Reject invalid: pnext < 0.
504 fields_pe[3] = -1
505 if sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128) != -1 { return 361 }
506 fields_pe[3] = 250
507
508 // Reject invalid: pos < 1.
509 fields_pe[1] = 0
510 if sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128) != -1 { return 362 }
511 fields_pe[1] = 100
512
513 // Reject invalid: empty rnext.
514 str_lens_pe[2] = 0
515 if sam_write_line_paired(str_buf_pe, str_lens_pe, fields_pe, out_pe, 128) != -1 { return 363 }
516 str_lens_pe[2] = 1
517
518 return 0
519}