1#pragma once
2
3#include <string>
4#include <sstream>
5#include <algorithm>
6#include <cstdint>
7#include <cstring>
8
9namespace jinja {
10
11static void string_replace_all(std::string & s, const std::string & search, const std::string & replace) {
12 if (search.empty()) {
13 return;
14 }
15 std::string builder;
16 builder.reserve(s.length());
17 size_t pos = 0;
18 size_t last_pos = 0;
19 while ((pos = s.find(search, last_pos)) != std::string::npos) {
20 builder.append(s, last_pos, pos - last_pos);
21 builder.append(replace);
22 last_pos = pos + search.length();
23 }
24 builder.append(s, last_pos, std::string::npos);
25 s = std::move(builder);
26}
27
28// for displaying source code around error position
29static std::string peak_source(const std::string & source, size_t pos, size_t max_peak_chars = 40) {
30 if (source.empty()) {
31 return "(no source available)";
32 }
33 std::string output;
34 size_t start = (pos >= max_peak_chars) ? (pos - max_peak_chars) : 0;
35 size_t end = std::min(pos + max_peak_chars, source.length());
36 std::string substr = source.substr(start, end - start);
37 string_replace_all(substr, "\n", "↵");
38 output += "..." + substr + "...\n";
39 std::string spaces(pos - start + 3, ' ');
40 output += spaces + "^";
41 return output;
42}
43
44static std::string fmt_error_with_source(const std::string & tag, const std::string & msg, const std::string & source, size_t pos) {
45 std::ostringstream oss;
46 oss << tag << ": " << msg << "\n";
47 oss << peak_source(source, pos);
48 return oss.str();
49}
50
51// Note: this is a simple hasher, not cryptographically secure, just for hash table usage
52struct hasher {
53 static constexpr auto size_t_digits = sizeof(size_t) * 8;
54 static constexpr size_t prime = size_t_digits == 64 ? 0x100000001b3 : 0x01000193;
55 static constexpr size_t seed = size_t_digits == 64 ? 0xcbf29ce484222325 : 0x811c9dc5;
56 static constexpr auto block_size = sizeof(size_t); // in bytes; allowing the compiler to vectorize the computation
57
58 static_assert(size_t_digits == 64 || size_t_digits == 32);
59 static_assert(block_size == 8 || block_size == 4);
60
61 uint8_t buffer[block_size];
62 size_t idx = 0; // current index in buffer
63 size_t state = seed;
64
65 hasher() = default;
66 hasher(const std::type_info & type_inf) noexcept {
67 const auto type_hash = type_inf.hash_code();
68 update(&type_hash, sizeof(type_hash));
69 }
70
71 // Properties:
72 // - update is not associative: update(a).update(b) != update(b).update(a)
73 // - update(a ~ b) == update(a).update(b) with ~ as concatenation operator --> useful for streaming
74 // - update("", 0) --> state unchanged with empty input
75 hasher& update(void const * bytes, size_t len) noexcept {
76 const uint8_t * c = static_cast<uint8_t const *>(bytes);
77 if (len == 0) {
78 return *this;
79 }
80 size_t processed = 0;
81
82 // first, fill the existing buffer if it's partial
83 if (idx > 0) {
84 size_t to_fill = block_size - idx;
85 if (to_fill > len) {
86 to_fill = len;
87 }
88 std::memcpy(buffer + idx, c, to_fill);
89 idx += to_fill;
90 processed += to_fill;
91 if (idx == block_size) {
92 update_block(buffer);
93 idx = 0;
94 }
95 }
96
97 // process full blocks from the remaining input
98 for (; processed + block_size <= len; processed += block_size) {
99 update_block(c + processed);
100 }
101
102 // buffer any remaining bytes
103 size_t remaining = len - processed;
104 if (remaining > 0) {
105 std::memcpy(buffer, c + processed, remaining);
106 idx = remaining;
107 }
108 return *this;
109 }
110
111 // convenience function for testing only
112 hasher& update(const std::string & s) noexcept {
113 return update(s.data(), s.size());
114 }
115
116 // finalize and get the hash value
117 // note: after calling digest, the hasher state is modified, do not call update() again
118 size_t digest() noexcept {
119 // if there are remaining bytes in buffer, fill the rest with zeros and process
120 if (idx > 0) {
121 for (size_t i = idx; i < block_size; ++i) {
122 buffer[i] = 0;
123 }
124 update_block(buffer);
125 idx = 0;
126 }
127
128 return state;
129 }
130
131private:
132 // IMPORTANT: block must have at least block_size bytes
133 void update_block(const uint8_t * block) noexcept {
134 size_t blk = static_cast<uint32_t>(block[0])
135 | (static_cast<uint32_t>(block[1]) << 8)
136 | (static_cast<uint32_t>(block[2]) << 16)
137 | (static_cast<uint32_t>(block[3]) << 24);
138 if constexpr (block_size == 8) {
139 blk = blk | (static_cast<uint64_t>(block[4]) << 32)
140 | (static_cast<uint64_t>(block[5]) << 40)
141 | (static_cast<uint64_t>(block[6]) << 48)
142 | (static_cast<uint64_t>(block[7]) << 56);
143 }
144 state ^= blk;
145 state *= prime;
146 }
147};
148
149} // namespace jinja