cuSBF
Loading...
Searching...
No Matches
fastx_buffer_reader.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <optional>
5#include <string>
6#include <string_view>
7#include <utility>
8
9#include <cusbf/error.hpp>
10#include <cusbf/Fastx.hpp>
11
12namespace cusbf::detail {
13
16 public:
23 explicit FastxBufferReader(std::string_view data, std::string_view source_name = "<buffer>")
24 : data_(data), source_name_(source_name) {}
25
33 record.header.clear();
34 record.sequence.clear();
35
36 const auto header = readHeaderLine();
37 if (!header) {
38 return Err(header.error());
39 }
40 if (header->empty()) {
41 return false;
42 }
43
44 const char header_tag = header->front();
45 if (format_ == FastxFormat::unknown) {
46 if (header_tag == '>') {
47 format_ = FastxFormat::fasta;
48 } else if (header_tag == '@') {
49 format_ = FastxFormat::fastq;
50 } else {
51 return Err(
52 parseError("expected FASTA or FASTQ header", fastx_column_at(*header, 0))
53 );
54 }
55 }
56
57 const char expected_header = format_ == FastxFormat::fasta ? '>' : '@';
59 return Err(parseError(
60 "mixed FASTA and FASTQ records are not supported", fastx_column_at(*header, 0)
61 ));
62 }
63
64 record.header.assign(header->substr(1));
65 if (format_ == FastxFormat::fasta) {
66 CUSBF_TRY(readFastaSequence(record.sequence));
67 } else {
68 CUSBF_TRY(readFastqSequence(record.sequence));
69 }
70 return true;
71 }
72
74 [[nodiscard]] std::string_view buffer() const noexcept {
75 return data_;
76 }
77
91 appendNextRecord(FastxRecord& record, std::string& sequence, std::string_view& buffer) {
92 record.header.clear();
93 record.sequence.clear();
94
95 const auto header = readHeaderLine();
96 if (!header) {
97 return Err(header.error());
98 }
99 if (header->empty()) {
100 return std::nullopt;
101 }
102
103 const char header_tag = header->front();
104 if (format_ == FastxFormat::unknown) {
105 if (header_tag == '>') {
106 format_ = FastxFormat::fasta;
107 } else if (header_tag == '@') {
108 format_ = FastxFormat::fastq;
109 } else {
110 return Err(
111 parseError("expected FASTA or FASTQ header", fastx_column_at(*header, 0))
112 );
113 }
114 }
115
116 const char expected_header = format_ == FastxFormat::fasta ? '>' : '@';
118 return Err(parseError(
119 "mixed FASTA and FASTQ records are not supported", fastx_column_at(*header, 0)
120 ));
121 }
122
123 record.header.assign(header->substr(1));
124 if (format_ == FastxFormat::fasta) {
125 const uint64_t sequence_offset = static_cast<uint64_t>(position_);
126 const std::string_view line = readLine();
127 if (line.empty()) {
128 return Err(parseError("FASTA record missing sequence", fastx_column_at(line, 0)));
129 }
130 if (!line.empty() && line.front() == '>') {
131 pending_header_.assign(line);
132 return Err(parseError("FASTA record missing sequence", fastx_column_at(line, 0)));
133 }
134
135 if (position_ < data_.size() && data_[position_] != '>') {
136 const uint64_t owned_offset = static_cast<uint64_t>(sequence.size());
137 sequence.append(line.data(), line.size());
138 CUSBF_TRY(readFastaSequence(sequence));
139 return RecordRange{
141 static_cast<uint64_t>(sequence.size()) - owned_offset,
142 };
143 }
144
145 if (position_ < data_.size() && data_[position_] == '>') {
146 pending_header_.assign(readLine());
147 }
148
149 if (buffer.empty()) {
150 buffer = data_;
151 }
152 return RecordRange{sequence_offset, static_cast<uint64_t>(line.size())};
153 }
154
155 const uint64_t sequence_offset = static_cast<uint64_t>(sequence.size());
156 CUSBF_TRY(readFastqSequence(sequence));
157 return RecordRange{
159 static_cast<uint64_t>(sequence.size()) - sequence_offset,
160 };
161 }
162
163 private:
164 std::string_view data_;
165 std::string_view source_name_;
166 size_t position_{0};
167 std::string pending_header_;
168 std::string header_line_;
170 uint64_t line_number_{};
171
172 [[nodiscard]] Error parseError(std::string_view message, uint32_t column) const {
173 return Error::fastx_parse(
174 SourceLocation::fastx(source_name_, static_cast<uint32_t>(line_number_), column),
175 message
176 );
177 }
178
179 [[nodiscard]] std::string_view readLine() {
180 if (position_ >= data_.size()) {
181 return {};
182 }
183
184 size_t end = position_;
185 while (end < data_.size() && data_[end] != '\n' && data_[end] != '\r') {
186 ++end;
187 }
188
189 const std::string_view line = data_.substr(position_, end - position_);
190 position_ = end;
191 if (position_ < data_.size() && data_[position_] == '\r') {
192 ++position_;
193 }
194 if (position_ < data_.size() && data_[position_] == '\n') {
195 ++position_;
196 }
197 ++line_number_;
198 return line;
199 }
200
201 [[nodiscard]] Result<std::string_view> readHeaderLine() {
202 if (!pending_header_.empty()) {
203 header_line_ = std::move(pending_header_);
204 pending_header_.clear();
205 return std::string_view{header_line_};
206 }
207
208 while (position_ < data_.size()) {
209 const std::string_view line = readLine();
210 if (!line.empty()) {
211 header_line_.assign(line.data(), line.size());
212 return std::string_view{header_line_};
213 }
214 }
215 return std::string_view{};
216 }
217
218 [[nodiscard]] Result<void> readFastaSequence(std::string& sequence) {
219 while (position_ < data_.size()) {
220 const std::string_view line = readLine();
221 if (!line.empty() && line.front() == '>') {
222 pending_header_.assign(line);
223 return {};
224 }
225 sequence.append(line.data(), line.size());
226 }
227 return {};
228 }
229
230 [[nodiscard]] Result<void> readFastqSequence(std::string& sequence) {
231 std::string_view last_line;
232 while (position_ < data_.size()) {
233 const std::string_view line = readLine();
234 last_line = line;
235 if (!line.empty() && line.front() == '+') {
236 CUSBF_TRY(readFastqQualities(sequence.size()));
237 return {};
238 }
239 sequence.append(line.data(), line.size());
240 }
241 return Err(parseError(
242 "unterminated FASTQ record: missing '+' separator",
243 fastx_column_at(last_line, last_line.size() > 0 ? last_line.size() - 1 : 0)
244 ));
245 }
246
247 [[nodiscard]] Result<void> readFastqQualities(uint64_t expected_length) {
249 std::string_view last_line;
250 while (quality_length < expected_length && position_ < data_.size()) {
251 const std::string_view line = readLine();
252 last_line = line;
253 quality_length += line.size();
255 return Err(parseError(
256 "FASTQ quality length exceeds sequence length",
258 ));
259 }
260 }
262 return Err(parseError(
263 "FASTQ quality length does not match sequence length",
265 ));
266 }
267 return {};
268 }
269};
270
271} // namespace cusbf::detail
FASTA/FASTQ parser over a contiguous in-memory buffer.
FastxBufferReader(std::string_view data, std::string_view source_name="<buffer>")
Constructs a reader over a contiguous in-memory FASTA/FASTQ buffer.
Result< bool > nextRecord(FastxRecord &record)
Reads the next record into record.
Result< std::optional< RecordRange > > appendNextRecord(FastxRecord &record, std::string &sequence, std::string_view &buffer)
Parses one record with optional zero-copy sequence views for single-line FASTA.
std::string_view buffer() const noexcept
Entire mmap or owned buffer backing this reader.
#define CUSBF_TRY(expr)
Propagates a cusbf::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:246
uint32_t fastx_column_at(std::string_view line, size_t byte_index)
1-based column at byte_index within line (clamped to the line end).
Definition Fastx.hpp:268
uint32_t fastx_quality_short_column(std::string_view line)
1-based column where a quality run ends too short (position after the last byte).
Definition Fastx.hpp:287
uint32_t fastx_quality_excess_column(uint64_t quality_length, uint64_t expected_length, std::string_view line)
1-based column of the first quality byte that exceeds expected_length.
Definition Fastx.hpp:276
FastxFormat
Detected file format for a FASTA/FASTQ stream.
Definition Fastx.hpp:243
@ fasta
FASTA (> headers).
@ fastq
FASTQ (@ headers).
@ unknown
Format not yet determined from the first header.
consteval bool separatorPositionAlwaysEncodesInvalid(char *input, uint64_t separatorPosition, uint64_t index)
Recursively tests whether placing the separator byte at any position in an input of valid bytes alway...
Definition Alphabet.cuh:37
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:219
static Error fastx_parse(SourceLocation site, std::string_view detail)
Definition error.hpp:123
Ordered non-overlapping byte range for one record inside a dense sequence batch.
Definition Fastx.hpp:24
Fallible API result: cuda::std::expected<T, Error> with cuSBF factories.
Definition error.hpp:152
static SourceLocation fastx(std::string_view file, uint32_t line, uint32_t column)
Definition error.hpp:34
A single sequence record extracted from a FASTA/FASTQ stream.
Definition Fastx.hpp:253