FE 0.13.1
Header-only C++ frontend library
Loading...
Searching...
No Matches
src.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4
5#include <algorithm>
6#include <filesystem>
7#include <fstream>
8#include <iterator>
9#include <string>
10#include <string_view>
11#include <system_error>
12#include <utility>
13#include <vector>
14
15#ifdef FE_ABSL
16# include <absl/container/node_hash_map.h>
17#else
18# include <unordered_map>
19#endif
20
21#include "fe/loc.h"
22#include "fe/utf8.h"
23
24namespace fe {
25
26/// Hashes a `std::filesystem::path` - consistent with its `operator==`, which compares lexically.
27struct PathHash {
28 size_t operator()(const std::filesystem::path& path) const noexcept { return std::filesystem::hash_value(path); }
29};
30
31/// Maps a `std::filesystem::path` to @p V.
32/// @warning Node-based on purpose: fe::SrcMap stores its Src%s in here and a Loc points to one,
33/// so the values must never move.
34#ifdef FE_ABSL
35template<class V>
36using PathMap = absl::node_hash_map<std::filesystem::path, V, PathHash>;
37#else
38template<class V>
39using PathMap = std::unordered_map<std::filesystem::path, V, PathHash>;
40#endif
41
42/// The content of one source file together with the offsets its rows start at.
43/// This is what turns a Pos back into the row/column a human wants to read.
44class Src {
45public:
46 Src(std::filesystem::path path, std::string buf)
47 : path_(std::move(path))
48 , buf_(std::move(buf)) {
49 if (buf_.starts_with(utf8::Bom)) bom_ = (uint32_t)utf8::Bom.size();
50 rows_.emplace_back(0);
51 for (uint32_t i = 0, e = (uint32_t)buf_.size(); i != e; ++i)
52 if (buf_[i] == '\n') rows_.emplace_back(i + 1);
53 }
54
55 /// @name Getters
56 ///@{
57 const std::filesystem::path& path() const { return path_; }
58 std::string_view buf() const { return buf_; }
59 /// The number of rows the file actually has.
60 /// @note A trailing line terminator does *not* open one more, empty row - it ends the last one.
61 uint32_t num_rows() const { return (uint32_t)rows_.size() - phantom_(); }
62 Pos begin() const { return Pos(0); }
63 Pos end() const { return Pos((uint32_t)buf_.size()); }
64 bool contains(Pos pos) const { return pos && pos.off <= buf_.size(); }
65 ///@}
66
67 /// @name Resolve a Pos
68 ///@{
69 /// 1-based row and column @p pos sits at, or `{0, 0}` if @p pos does not belong to this file.
70 /// The column counts code points, not bytes, and a leading utf8::Bom occupies none.
71 /// @note Never names a row Src::num_rows does not count - see there.
72 std::pair<uint32_t, uint32_t> rowcol(Pos pos) const {
73 if (!contains(pos)) return {0, 0};
74 auto row = (uint32_t)(std::ranges::upper_bound(rows_, pos.off) - rows_.begin());
75
76 // @p pos is at the very end of a file that ends with a terminator. That is no row of its own
77 // but one past the end of the last real one - which is where an `<end of file>` token points.
78 if (row > num_rows()) return {num_rows(), (uint32_t)utf8::num_code_points(line(num_rows())) + 1};
79
80 auto begin = row == 1 ? std::min(bom_, pos.off) : rows_[row - 1];
81 return {row, (uint32_t)utf8::num_code_points(sub(begin, pos.off)) + 1};
82 }
83
84 uint32_t row(Pos pos) const { return rowcol(pos).first; }
85 uint32_t col(Pos pos) const { return rowcol(pos).second; }
86
87 /// Text of the 1-based @p row without its line terminator - or a leading utf8::Bom;
88 /// empty if @p row is out of range.
89 std::string_view line(uint32_t row) const {
90 if (row == 0 || row > num_rows()) return {};
91 auto begin = row == 1 ? bom_ : rows_[row - 1];
92 auto end = row == rows_.size() ? (uint32_t)buf_.size() : rows_[row] - 1;
93 if (end > begin && buf_[end - 1] == '\r') --end;
94 return sub(begin, end);
95 }
96
97 /// Start of the last code point before @p pos - the character a half-open Loc::end points *past*.
98 Pos prev(Pos pos) const {
99 auto end = std::min<size_t>(pos.off, buf_.size());
100 if (end == 0) return Pos(0);
101
102 auto i = end - 1;
103 while (i != 0 && utf8::is_valid234(char8_t(buf_[i])) != char8_t(-1))
104 --i;
105
106 // Only trust the backward scan if that candidate really decodes up to `end`:
107 // utf8::decode resynchronizes malformed input byte by byte and this must not disagree.
108 auto j = i;
109 utf8::decode(buf_, j);
110 return Pos((uint32_t)(j == end ? i : end - 1));
111 }
112 ///@}
113
114private:
115 /// Scanning for `\n` appends one more offset when buf_ ends with a terminator: a row with
116 /// nothing in it and nothing after it. A terminator *ends* its row rather than opening a new
117 /// one, so that entry is an artifact of the scan and not a row the file has.
118 uint32_t phantom_() const { return rows_.size() > 1 && rows_.back() == buf_.size() ? 1 : 0; }
119
120 std::string_view sub(uint32_t begin, uint32_t end) const {
121 return std::string_view(buf_).substr(begin, end - begin);
122 }
123
124 std::filesystem::path path_;
125 std::string buf_;
126 std::vector<uint32_t> rows_; ///< Offset each row starts at; `rows_.front() == 0`.
127 uint32_t bom_ = 0; ///< Byte size of a leading utf8::Bom, which is not a column.
128};
129
130/// Interns the text - and the `std::filesystem::path` - of every file a Loc may point into.
131/// Keep one in your Driver: a Loc is only as good as the SrcMap that keeps its Src alive.
132/// Each file lives here exactly once, so Loc::src identifies it by pointer - see SrcMap::key.
133class SrcMap {
134public:
135 /// @name Register a File
136 ///@{
137 /// Registers @p path with @p buf as its content and reports whether it is fresh.
138 /// A @p path with the same SrcMap::key as an already registered one yields that entry instead.
139 std::pair<const Src*, bool> add(std::filesystem::path path, std::string buf) {
140 auto k = key(path);
141 auto [i, fresh] = path2src_.try_emplace(std::move(k), std::move(path), std::move(buf));
142 return {&i->second, fresh};
143 }
144
145 /// As above, but reads the content from @p path.
146 /// @returns a `nullptr` Src if @p path cannot be opened.
147 std::pair<const Src*, bool> add(std::filesystem::path path) {
148 auto k = key(path);
149 if (auto i = path2src_.find(k); i != path2src_.end()) return {&i->second, false};
150 auto ifs = std::ifstream(path, std::ios::binary);
151 if (!ifs) return {nullptr, false};
152 auto [i, fresh] = path2src_.try_emplace(std::move(k), std::move(path), slurp(ifs));
153 return {&i->second, fresh};
154 }
155
156 /// Reads all of @p is into a `std::string`.
157 static std::string slurp(std::istream& is) {
158 return is ? std::string(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>()) : std::string();
159 }
160 ///@}
161
162 /// @name Lookup
163 ///@{
164 /// @returns `nullptr` if @p path has not been registered.
165 /// @note Compares SrcMap::key%s, so a @p path that merely *spells* a registered file
166 /// differently still finds it.
167 const Src* lookup(const std::filesystem::path& path) const {
168 auto i = path2src_.find(key(path));
169 return i == path2src_.end() ? nullptr : &i->second;
170 }
171
172 /// The key @p path is interned under - absolute, symlink-free, and normalized.
173 /// This is where "do these two paths name the same file?" is decided - once, upon SrcMap::add -
174 /// so that every comparison afterwards is a plain Loc::src pointer comparison.
175 /// @note Resolves symlinks and `.`/`..` as far as @p path exists on disk and normalizes the rest
176 /// lexically. A relative @p path is resolved against the current working directory *now*.
177 static std::filesystem::path key(const std::filesystem::path& path) {
178 std::error_code ec;
179 // Absolute first: weakly_canonical only resolves the prefix of `path` that exists on disk,
180 // and whether `foo` has such a prefix at all depends on it being spelled `./foo` or not.
181 auto abs = std::filesystem::absolute(path, ec);
182 if (ec) return path.lexically_normal();
183 auto res = std::filesystem::weakly_canonical(abs, ec);
184 return ec ? abs.lexically_normal() : res;
185 }
186 ///@}
187
188private:
189 PathMap<Src> path2src_; ///< Keyed by SrcMap::key; node-based, so a Src never moves.
190};
191
192} // namespace fe
Interns the text - and the std::filesystem::path - of every file a Loc may point into.
Definition src.h:133
std::pair< const Src *, bool > add(std::filesystem::path path)
As above, but reads the content from path.
Definition src.h:147
static std::filesystem::path key(const std::filesystem::path &path)
The key path is interned under - absolute, symlink-free, and normalized.
Definition src.h:177
const Src * lookup(const std::filesystem::path &path) const
Definition src.h:167
std::pair< const Src *, bool > add(std::filesystem::path path, std::string buf)
Definition src.h:139
static std::string slurp(std::istream &is)
Reads all of is into a std::string.
Definition src.h:157
The content of one source file together with the offsets its rows start at.
Definition src.h:44
std::string_view buf() const
Definition src.h:58
Pos begin() const
Definition src.h:62
std::string_view line(uint32_t row) const
Text of the 1-based row without its line terminator - or a leading utf8::Bom; empty if row is out of ...
Definition src.h:89
uint32_t num_rows() const
The number of rows the file actually has.
Definition src.h:61
Pos end() const
Definition src.h:63
bool contains(Pos pos) const
Definition src.h:64
uint32_t col(Pos pos) const
Definition src.h:85
Src(std::filesystem::path path, std::string buf)
Definition src.h:46
const std::filesystem::path & path() const
Definition src.h:57
Pos prev(Pos pos) const
Start of the last code point before pos - the character a half-open Loc::end points past.
Definition src.h:98
uint32_t row(Pos pos) const
Definition src.h:84
std::pair< uint32_t, uint32_t > rowcol(Pos pos) const
Definition src.h:72
char32_t decode(std::istream &is)
Decodes the next UTF-8 sequence from is into a single char32_t.
Definition utf8.h:66
constexpr char8_t is_valid234(char8_t c) noexcept
Is the 2nd, 3rd, or 4th byte of an UTF-8 byte sequence valid?
Definition utf8.h:58
size_t num_code_points(std::string_view str) noexcept
Number of UTF-8 code points in str.
Definition utf8.h:119
Definition algo.h:17
std::unordered_map< std::filesystem::path, V, PathHash > PathMap
Maps a std::filesystem::path to V.
Definition src.h:39
Definition span.h:129
Hashes a std::filesystem::path - consistent with its operator==, which compares lexically.
Definition src.h:27
size_t operator()(const std::filesystem::path &path) const noexcept
Definition src.h:28
Byte offset into a Src; pass around as value.
Definition loc.h:18
uint32_t off
Definition loc.h:33
constexpr Pos()=default
Creates an invalid Position.