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