NuriKit v0.1.0b2
Loading...
Searching...
No Matches
cif.h
Go to the documentation of this file.
1//
2// Project NuriKit - Copyright 2024 SNU Compbio Lab.
3// SPDX-License-Identifier: Apache-2.0
4//
5
6#ifndef NURI_FMT_CIF_H_
7#define NURI_FMT_CIF_H_
8
10#include <cmath>
11#include <cstddef>
12#include <cstdint>
13#include <istream>
14#include <ostream>
15#include <string>
16#include <string_view>
17#include <type_traits>
18#include <utility>
19#include <vector>
20
21#include <absl/base/attributes.h>
22#include <absl/base/optimization.h>
23#include <absl/container/btree_map.h>
24#include <absl/log/absl_check.h>
25#include <absl/strings/str_cat.h>
26#include <absl/strings/str_format.h>
27#include <boost/range/iterator_range.hpp>
29
30#include "nuri/utils.h"
31
32namespace nuri {
33namespace internal {
34 enum class CifToken : std::uint32_t {
35 kEOF = 0U,
36 kError = 1U,
37
38 kData = 2U,
39 kLoop = 3U,
40 kGlobal = 4U,
41 kSave = 5U,
42 kStop = 6U,
43
44 // 7-15 reserved for future use
45
46 kTag = 1U << 4,
47 kValue = 1U << 5,
48
49 // Flags
50 kIsQuoted = 1U << 31,
51
52 // Compound tokens
53 kSimpleValue = kValue,
54 kQuotedValue = kValue | kIsQuoted,
55 };
56
57 constexpr bool is_value_token(CifToken token) {
58 return static_cast<bool>(token & CifToken::kValue);
59 }
60
61 extern std::ostream &operator<<(std::ostream &os, CifToken type);
62
63 using SIter = std::string::const_iterator;
64
65 class CifLexer {
66 public:
67 explicit CifLexer(std::istream &is): is_(&is) {
68 static_cast<void>(advance_line<true>());
69 }
70
71 std::pair<std::string_view, CifToken> next();
72
73 // state observers
74
75 size_t row() const { return row_; }
76 size_t col() const { return it_ - begin_ + 1; }
77
78 SIter begin() const { return begin_; }
79
80 SIter p() const { return it_; }
81
82 char c() const {
83 ABSL_DCHECK(it_ < end());
84 return *it_;
85 }
86
87 SIter end() const { return end_; }
88
89 std::string_view line() const { return line_; }
90
91 template <bool kSkipEmpty>
92 ABSL_MUST_USE_RESULT bool advance_line() {
93 do {
94 if (!std::getline(*is_, line_)) {
95 // reentrant-safe
96 begin_ = it_ = end();
97 return false;
98 }
99
100 ++row_;
101 begin_ = it_ = line_.begin();
102 end_ = line_.end();
103 } while (kSkipEmpty && begin_ == end_);
104
105 return true;
106 }
107
108 // for implementation, do not use
109
110 std::pair<std::string_view, CifToken> produce(std::string_view data,
111 CifToken type, SIter after) {
112 it_ = after;
113 return { data, type };
114 }
115
116 template <class... Args, std::enable_if_t<sizeof...(Args) != 0, int> = 0>
117 std::pair<std::string_view, CifToken> error(Args &&...args) {
118 // found by fuzzing, argument can point to internal buffer so we need to
119 // create a string first then move it
120 std::string err = absl::StrCat(std::forward<Args>(args)...);
121 buf_ = std::move(err);
122 return { buf_, CifToken::kError };
123 }
124
125 static std::pair<std::string_view, CifToken> done() {
126 return { {}, CifToken::kEOF };
127 }
128
129 private:
130 Nonnull<std::istream *> is_;
131 std::string line_;
132 std::string buf_;
133
134 SIter it_;
135 SIter begin_;
136 SIter end_;
137 std::size_t row_ = 0;
138 };
139
140 class CifValue {
141 public:
142 enum class Type : std::uint32_t {
143 kGeneric = 1U, // Unquoted literals
144 kString = 1U << 1, // Quoted literals
145
146 // Reserved for future use
147
148 // Nurikit-specific: non-finite float encountered
149 kFloatNonfinite = 1U << 2,
150
151 kUnknown = 1U << 30, // ?
152 kInapplicable = 1U << 31, // .
153 };
154
155 CifValue(std::string_view value, internal::CifToken type): value_(value) {
156 if (type == internal::CifToken::kQuotedValue) {
157 type_ = Type::kString;
158 return;
159 }
160
161 ABSL_DCHECK_EQ(type, internal::CifToken::kSimpleValue);
162
163 if (value == "?") {
164 value_.clear();
165 type_ = Type::kUnknown;
166 } else if (value == ".") {
167 value_.clear();
168 type_ = Type::kInapplicable;
169 }
170 }
171
172 // An unquoted literal (number, boolean, or standard uncertainty). Written
173 // verbatim when the content permits.
174 template <class T>
175 static CifValue generic(T &&value) {
176 return CifValue(std::forward<T>(value), Type::kGeneric);
177 }
178
179 // A string value. Quoted only when its content requires it.
180 template <class T>
181 static CifValue string(T &&value) {
182 return CifValue(std::forward<T>(value), Type::kString);
183 }
184
185 static CifValue unknown() { return CifValue("", Type::kUnknown); }
186
187 static CifValue inapplicable() { return CifValue("", Type::kInapplicable); }
188
189 static CifValue null(bool is_unk) {
190 return CifValue("", is_unk ? Type::kUnknown : Type::kInapplicable);
191 }
192
193 template <class T>
194 static CifValue float_nonfinite(T &&repr) {
195 return CifValue(std::forward<T>(repr), Type::kFloatNonfinite);
196 }
197
198 std::string_view operator*() const { return value_; }
199 std::string_view value() const { return value_; }
200
201 const std::string *operator->() const { return &value_; }
202
203 Type type() const { return type_; }
204
205 constexpr bool is_null() const {
206 return static_cast<bool>(type_ & (Type::kUnknown | Type::kInapplicable));
207 }
208
209 constexpr operator bool() const { return !is_null(); }
210
211 bool operator==(std::string_view other) const;
212
213 private:
214 template <class T>
215 CifValue(T &&value, Type type)
216 : value_(std::forward<T>(value)), type_(type) { }
217
218 std::string value_;
219 Type type_ = Type::kGeneric;
220 };
221
222 extern std::ostream &operator<<(std::ostream &os, const CifValue &value);
223
224 class CifTableColumn;
225
226 class CifTable {
227 public:
228 CifTable() = default;
229
230 const std::vector<std::string> &keys() const { return keys_; }
231 void add_key(std::string_view key) { keys_.push_back(std::string { key }); }
232
233 const std::vector<std::vector<CifValue>> &data() const { return rows_; }
234 void add_data(CifValue &&value);
235
236 const std::vector<CifValue> &operator[](size_t i) const { return rows_[i]; }
237 size_t size() const { return rows_.size(); }
238
239 auto begin() const { return rows_.begin(); }
240 auto end() const { return rows_.end(); }
241
242 const std::vector<CifValue> &row(size_t i) const { return rows_[i]; }
243 size_t rows() const { return rows_.size(); }
244
245 CifTableColumn col(size_t i) const;
246 size_t cols() const { return keys_.size(); }
247
248 bool empty() const { return keys_.empty() || rows_.empty(); }
249
250 std::string validate() const;
251
252 private:
253 std::vector<std::string> keys_;
254 std::vector<std::vector<CifValue>> rows_;
255 };
256
257 class CifTableColumn {
258 public:
259 CifTableColumn(Nonnull<const CifTable *> table, size_t col)
260 : table_(table), col_(col) { }
261
262 std::string_view key() const { return table_->keys()[col_]; }
263 size_t idx() const { return col_; }
264
265 const CifValue &operator[](size_t i) const { return table_->row(i)[col_]; }
266 size_t size() const { return table_->rows(); }
267
268 private:
269 Nonnull<const CifTable *> table_;
270 size_t col_;
271 };
272
273 inline CifTableColumn CifTable::col(size_t i) const {
274 return { this, i };
275 }
276
277 class CifFrame {
278 public:
279 enum class Type : int {
280 kGlobal = static_cast<int>(CifToken::kGlobal), // global_
281 kData = static_cast<int>(CifToken::kData), // data_[<name>]
282 kSave = static_cast<int>(CifToken::kSave), // save_[<name>]
283 };
284
285 CifFrame(std::vector<CifTable> &&tables, std::string &&name);
286
287 CifFrame() noexcept = default;
288 CifFrame(CifFrame &&) noexcept = default;
289 CifFrame &operator=(CifFrame &&) noexcept = default;
290 ~CifFrame() = default;
291
292 // Not copyable due to index
293 CifFrame(const CifFrame &) = delete;
294 CifFrame &operator=(const CifFrame &) = delete;
295
296 std::string_view name() const { return name_; }
297
298 const std::vector<CifTable> &tables() const { return tables_; }
299
300 const CifTable &operator[](size_t i) const { return tables_[i]; }
301 size_t size() const { return tables_.size(); }
302
303 auto begin() const { return tables_.begin(); }
304 auto end() const { return tables_.end(); }
305
306 size_t total_cols() const { return index_.size(); }
307
308 auto prefix_search(std::string_view prefix) const {
309 return boost::make_iterator_range(index_.lower_bound(prefix),
310 index_.end());
311 }
312
313 std::pair<int, int> find(std::string_view key) const {
314 auto it = index_.find(key);
315 if (it == index_.end())
316 return { -1, -1 };
317 return it->second;
318 }
319
320 CifTableColumn get(size_t tbl, size_t col) const {
321 return tables_[tbl].col(col);
322 }
323
324 std::string validate(bool recursive = true) const;
325
326 private:
327 std::string name_;
328 std::vector<CifTable> tables_;
329 absl::btree_map<std::string_view, std::pair<int, int>> index_;
330 };
331
332 class CifBlock {
333 public:
334 enum class Type : int {
335 kEOF = static_cast<int>(CifToken::kEOF), // sentinel, end of file
336 kError = static_cast<int>(CifToken::kError), // sentinel, error state
337 kGlobal = static_cast<int>(CifToken::kGlobal), // global_
338 kData = static_cast<int>(CifToken::kData), // data_[<name>]
339 };
340
341 CifBlock(CifFrame &&frame, std::vector<CifFrame> &&save, Type type) noexcept
342 : frame_(std::move(frame)), save_(std::move(save)), type_(type) {
343 ABSL_DCHECK(*this);
344 }
345
346 static CifBlock eof() noexcept { return {}; }
347
348 static CifBlock error(std::string_view reason) { return { reason }; }
349 std::string_view error_msg() const {
350 ABSL_DCHECK(type_ == Type::kError);
351 return frame_.name();
352 }
353
354 std::string_view name() const {
355 ABSL_DCHECK(type_ != Type::kError);
356 return frame_.name();
357 }
358
359 const CifFrame &data() const { return frame_; }
360
361 const std::vector<CifFrame> &save_frames() const { return save_; }
362
363 Type type() const { return type_; }
364
365 std::string validate(bool recursive = true) const;
366
367 constexpr operator bool() const {
368 return type_ != Type::kEOF && type_ != Type::kError;
369 }
370
371 private:
372 CifBlock() noexcept: type_(Type::kEOF) { }
373
374 CifBlock(std::string_view error_msg)
375 : frame_({}, std::string { error_msg }), type_(Type::kError) { }
376
377 CifFrame frame_;
378 std::vector<CifFrame> save_;
379 Type type_;
380 };
381} // namespace internal
382
384public:
385 explicit CifParser(std::istream &is);
386
390 internal::CifBlock next();
391
393 internal::CifBlock error(std::string_view reason);
394
395private:
396 internal::CifLexer lexer_;
397
398 std::string name_;
399 internal::CifBlock::Type block_ = internal::CifBlock::Type::kEOF;
400};
401
407inline internal::CifValue cif_value(std::string_view value) {
408 return internal::CifValue::string(value);
409}
410
421template <class T, std::enable_if_t<std::is_same_v<T, bool>, int> = 0>
422internal::CifValue cif_value(T value, bool short_form = false) {
423 return internal::CifValue::generic(value ? (short_form ? "y" : "yes")
424 : (short_form ? "n" : "no"));
425}
426
433template <
434 class T,
435 std::enable_if_t<std::is_integral_v<T> && !std::is_same_v<T, bool>, int> = 0>
436internal::CifValue cif_value(T value, int width = 0) {
437 if (width <= 0)
438 return internal::CifValue::generic(absl::StrCat(value));
439
440 return internal::CifValue::generic(
441 absl::StrFormat("%0*d", width, static_cast<std::int64_t>(value)));
442}
443
444namespace internal {
445 extern CifValue cif_float_nonfinite(double value, bool coerce_nonfinite,
446 bool is_unk);
447}
448
465template <class T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
466internal::CifValue cif_value(T value, int precision = -1,
467 bool coerce_nonfinite = false,
468 bool is_unk = true) {
469 if (ABSL_PREDICT_FALSE(!std::isfinite(value))) {
470 return internal::cif_float_nonfinite(static_cast<double>(value),
471 coerce_nonfinite, is_unk);
472 }
473
474 if (precision < 0)
475 return internal::CifValue::generic(absl::StrCat(value));
476
477 return internal::CifValue::generic(
478 absl::StrFormat("%.*f", precision, static_cast<double>(value)));
479}
480
489template <class T,
490 std::enable_if_t<!std::is_arithmetic_v<std::decay_t<T>>
491 && !std::is_convertible_v<T, std::string_view>,
492 int> = 0>
493internal::CifValue cif_value(T &&value) {
494 return internal::CifValue::string(absl::StrCat(std::forward<T>(value)));
495}
496
497namespace internal {
498 enum class CifValueKind { kError, kInline, kTextField };
499
500 extern CifValueKind write_cif_value(std::string &out,
501 const internal::CifValue &value);
502} // namespace internal
503
517extern bool write_cif_table(std::string &out, const internal::CifTable &table,
518 bool align = false);
519
535extern bool
536write_cif_frame(std::string &out, const internal::CifFrame &frame,
537 internal::CifFrame::Type type = internal::CifFrame::Type::kData,
538 bool align = false);
539
555extern bool write_cif_block(std::string &out, const internal::CifBlock &block,
556 bool align = false);
557
558// Test helpers
559
560namespace internal {
561 enum class CifGlobalCtx {
562 kBlock,
563 kSave,
564 };
565
566 extern std::pair<std::string_view, CifToken>
567 parse_data(CifGlobalCtx ctx, std::vector<CifTable> &tables, CifLexer &lexer,
568 std::string_view name);
569
570 extern CifBlock next_block(CifParser &parser, CifLexer &lexer,
571 std::string &next_name,
572 internal::CifBlock::Type &next_block);
573} // namespace internal
574} // namespace nuri
575
576#endif /* NURI_FMT_CIF_H_ */
internal::CifBlock next()
Parse the next block in the CIF file.
CifParser(std::istream &is)
Definition crdgen.h:16
bool write_cif_table(std::string &out, const internal::CifTable &table, bool align=false)
Serialize a CIF table (key-value pairs or a loop_), appending to out.
bool write_cif_frame(std::string &out, const internal::CifFrame &frame, internal::CifFrame::Type type=internal::CifFrame::Type::kData, bool align=false)
Serialize a CIF frame, appending to out.
@ kUnknown
Definition molecule.h:588
internal::CifValue cif_value(std::string_view value)
Create a string CIF value from text.
Definition cif.h:407
constexpr bool operator==(const Isotope &lhs, const Isotope &rhs) noexcept
Definition element.h:34
bool write_cif_block(std::string &out, const internal::CifBlock &block, bool align=false)
Serialize a CIF block (a data or global block plus its save frames), appending to out.