/home/uke/oil/mycpp/gc_mylib.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "mycpp/gc_mylib.h" |
2 | | |
3 | | #include <errno.h> |
4 | | #include <stdio.h> |
5 | | #include <unistd.h> // isatty |
6 | | |
7 | | namespace mylib { |
8 | | |
9 | 1 | void InitCppOnly() { |
10 | | // We don't seem need this now that we have ctx_FlushStdout(). |
11 | | // setvbuf(stdout, 0, _IONBF, 0); |
12 | | |
13 | | // Arbitrary threshold of 50K objects based on eyeballing |
14 | | // benchmarks/osh-runtime 10K or 100K aren't too bad either. |
15 | 1 | gHeap.Init(50000); |
16 | 1 | } |
17 | | |
18 | 5 | void print_stderr(BigStr* s) { |
19 | 5 | fputs(s->data_, stderr); // prints until first NUL |
20 | 5 | fputc('\n', stderr); |
21 | 5 | } |
22 | | |
23 | | #if 0 |
24 | | void writeln(BigStr* s, int fd) { |
25 | | // TODO: handle errors and write in a loop, like posix::write(). If possible, |
26 | | // use posix::write directly, but that introduces some dependency problems. |
27 | | |
28 | | if (write(fd, s->data_, len(s)) < 0) { |
29 | | assert(0); |
30 | | } |
31 | | if (write(fd, "\n", 1) < 0) { |
32 | | assert(0); |
33 | | } |
34 | | } |
35 | | #endif |
36 | | |
37 | 0 | BigStr* JoinBytes(List<int>* byte_list) { |
38 | 0 | int n = len(byte_list); |
39 | 0 | BigStr* result = NewStr(n); |
40 | 0 | for (int i = 0; i < n; ++i) { |
41 | 0 | result->data_[i] = byte_list->at(i); |
42 | 0 | } |
43 | 0 | return result; |
44 | 0 | } |
45 | | |
46 | | class MutableStr : public BigStr {}; |
47 | | |
48 | 5 | MutableStr* NewMutableStr(int n) { |
49 | | // In order for everything to work, MutableStr must be identical in layout to |
50 | | // BigStr. One easy way to achieve this is for MutableStr to have no members |
51 | | // and to inherit from BigStr. |
52 | 5 | static_assert(sizeof(MutableStr) == sizeof(BigStr), |
53 | 5 | "BigStr and MutableStr must have same size"); |
54 | 5 | return reinterpret_cast<MutableStr*>(NewStr(n)); |
55 | 5 | } |
56 | | |
57 | 5 | Tuple2<BigStr*, BigStr*> split_once(BigStr* s, BigStr* delim) { |
58 | 5 | DCHECK(len(delim) == 1); |
59 | | |
60 | 0 | const char* start = s->data_; // note: this pointer may move |
61 | 5 | char c = delim->data_[0]; |
62 | 5 | int length = len(s); |
63 | | |
64 | 5 | const char* p = static_cast<const char*>(memchr(start, c, length)); |
65 | | |
66 | 5 | if (p) { |
67 | 3 | int len1 = p - start; |
68 | 3 | int len2 = length - len1 - 1; // -1 for delim |
69 | | |
70 | 3 | BigStr* s1 = nullptr; |
71 | 3 | BigStr* s2 = nullptr; |
72 | | // Allocate together to avoid 's' moving in between |
73 | 3 | s1 = NewStr(len1); |
74 | 3 | s2 = NewStr(len2); |
75 | | |
76 | 3 | memcpy(s1->data_, s->data_, len1); |
77 | 3 | memcpy(s2->data_, s->data_ + len1 + 1, len2); |
78 | | |
79 | 3 | return Tuple2<BigStr*, BigStr*>(s1, s2); |
80 | 3 | } else { |
81 | 2 | return Tuple2<BigStr*, BigStr*>(s, nullptr); |
82 | 2 | } |
83 | 5 | } |
84 | | |
85 | | LineReader* gStdin; |
86 | | |
87 | 2 | LineReader* open(BigStr* path) { |
88 | | // TODO: Don't use C I/O; use POSIX I/O! |
89 | 2 | FILE* f = fopen(path->data_, "r"); |
90 | 2 | if (f == nullptr) { |
91 | 0 | throw Alloc<IOError>(errno); |
92 | 0 | } |
93 | | |
94 | 2 | return reinterpret_cast<LineReader*>(Alloc<CFile>(f)); |
95 | 2 | } |
96 | | |
97 | 318 | BigStr* CFile::readline() { |
98 | 318 | char* line = nullptr; |
99 | 318 | size_t allocated_size = 0; // unused |
100 | | |
101 | | // Reset errno because we turn the EOF error into empty string (like Python). |
102 | 318 | errno = 0; |
103 | 318 | ssize_t len = getline(&line, &allocated_size, f_); |
104 | 318 | if (len < 0) { |
105 | | // man page says the buffer should be freed even if getline fails |
106 | 1 | free(line); |
107 | 1 | if (errno != 0) { // Unexpected error |
108 | 0 | log("getline() error: %s", strerror(errno)); |
109 | 0 | throw Alloc<IOError>(errno); |
110 | 0 | } |
111 | 1 | return kEmptyString; // EOF indicated by by empty string, like Python |
112 | 1 | } |
113 | | |
114 | | // Note: getline() NUL-terminates the buffer |
115 | 317 | BigStr* result = ::StrFromC(line, len); |
116 | 317 | free(line); |
117 | 317 | return result; |
118 | 318 | } |
119 | | |
120 | 2 | bool CFile::isatty() { |
121 | 2 | return ::isatty(fileno(f_)); |
122 | 2 | } |
123 | | |
124 | | // Problem: most BigStr methods like index() and slice() COPY so they have a |
125 | | // NUL terminator. |
126 | | // log("%s") falls back on sprintf, so it expects a NUL terminator. |
127 | | // It would be easier for us to just share. |
128 | 8 | BigStr* BufLineReader::readline() { |
129 | 8 | BigStr* line = nullptr; |
130 | | |
131 | 8 | int str_len = len(s_); |
132 | 8 | if (pos_ == str_len) { |
133 | 4 | return kEmptyString; |
134 | 4 | } |
135 | | |
136 | 4 | int orig_pos = pos_; |
137 | 4 | const char* p = strchr(s_->data_ + pos_, '\n'); |
138 | | // log("pos_ = %s", pos_); |
139 | 4 | int line_len; |
140 | 4 | if (p) { |
141 | 2 | int new_pos = p - s_->data_; |
142 | 2 | line_len = new_pos - pos_ + 1; // past newline char |
143 | 2 | pos_ = new_pos + 1; |
144 | 2 | } else { // leftover line |
145 | 2 | if (pos_ == 0) { // The string has no newlines at all -- just return it |
146 | 1 | pos_ = str_len; // advance to the end |
147 | 1 | return s_; |
148 | 1 | } else { |
149 | 1 | line_len = str_len - pos_; |
150 | 1 | pos_ = str_len; // advance to the end |
151 | 1 | } |
152 | 2 | } |
153 | | |
154 | 3 | line = NewStr(line_len); |
155 | 3 | memcpy(line->data_, s_->data_ + orig_pos, line_len); |
156 | 3 | DCHECK(line->data_[line_len] == '\0'); |
157 | 0 | return line; |
158 | 4 | } |
159 | | |
160 | | Writer* gStdout; |
161 | | Writer* gStderr; |
162 | | |
163 | | // |
164 | | // CFileWriter |
165 | | // |
166 | | |
167 | 2 | void CFile::write(BigStr* s) { |
168 | | // Writes can be short! |
169 | 2 | int n = len(s); |
170 | 2 | int num_written = ::fwrite(s->data_, sizeof(char), n, f_); |
171 | | // Similar to CPython fileobject.c |
172 | 2 | if (num_written != n) { |
173 | 0 | throw Alloc<IOError>(errno); |
174 | 0 | } |
175 | 2 | } |
176 | | |
177 | 1 | void CFile::flush() { |
178 | 1 | if (::fflush(f_) != 0) { |
179 | 0 | throw Alloc<IOError>(errno); |
180 | 0 | } |
181 | 1 | } |
182 | | |
183 | 1 | void CFile::close() { |
184 | 1 | if (::fclose(f_) != 0) { |
185 | 0 | throw Alloc<IOError>(errno); |
186 | 0 | } |
187 | 1 | } |
188 | | |
189 | | // |
190 | | // BufWriter |
191 | | // |
192 | | |
193 | 5 | void BufWriter::EnsureMoreSpace(int n) { |
194 | 5 | if (str_ == nullptr) { |
195 | | // TODO: we could make the default capacity big enough for a line, e.g. 128 |
196 | | // capacity: 128 -> 256 -> 512 |
197 | 3 | str_ = NewMutableStr(n); |
198 | 3 | return; |
199 | 3 | } |
200 | | |
201 | 2 | int current_cap = len(str_); |
202 | 2 | DCHECK(current_cap >= len_); |
203 | | |
204 | 0 | int new_cap = len_ + n; |
205 | | |
206 | 2 | if (current_cap < new_cap) { |
207 | 2 | auto* s = NewMutableStr(std::max(current_cap * 2, new_cap)); |
208 | 2 | memcpy(s->data_, str_->data_, len_); |
209 | 2 | s->data_[len_] = '\0'; |
210 | 2 | str_ = s; |
211 | 2 | } |
212 | 2 | } |
213 | | |
214 | 0 | uint8_t* BufWriter::LengthPointer() { |
215 | | // start + len |
216 | 0 | return reinterpret_cast<uint8_t*>(str_->data_) + len_; |
217 | 0 | } |
218 | | |
219 | 0 | uint8_t* BufWriter::CapacityPointer() { |
220 | | // start + capacity |
221 | 0 | return reinterpret_cast<uint8_t*>(str_->data_) + str_->len_; |
222 | 0 | } |
223 | | |
224 | 0 | void BufWriter::SetLengthFrom(uint8_t* length_ptr) { |
225 | 0 | uint8_t* begin = reinterpret_cast<uint8_t*>(str_->data_); |
226 | 0 | DCHECK(length_ptr >= begin); // we should have written some data |
227 | | |
228 | | // Set the length, e.g. so we know where to resume writing from |
229 | 0 | len_ = length_ptr - begin; |
230 | | // printf("SET LEN to %d\n", len_); |
231 | 0 | } |
232 | | |
233 | 0 | void BufWriter::Truncate(int length) { |
234 | 0 | len_ = length; |
235 | 0 | } |
236 | | |
237 | 4 | void BufWriter::WriteRaw(char* s, int n) { |
238 | 4 | DCHECK(is_valid_); // Can't write() after getvalue() |
239 | | |
240 | | // write('') is a no-op, so don't create Buf if we don't need to |
241 | 4 | if (n == 0) { |
242 | 0 | return; |
243 | 0 | } |
244 | | |
245 | 4 | EnsureMoreSpace(n); |
246 | | |
247 | | // Append the contents to the buffer |
248 | 4 | memcpy(str_->data_ + len_, s, n); |
249 | 4 | len_ += n; |
250 | 4 | str_->data_[len_] = '\0'; |
251 | 4 | } |
252 | | |
253 | 0 | void BufWriter::WriteConst(const char* c_string) { |
254 | | // meant for short strings like '"' |
255 | 0 | WriteRaw(const_cast<char*>(c_string), strlen(c_string)); |
256 | 0 | } |
257 | | |
258 | 4 | void BufWriter::write(BigStr* s) { |
259 | 4 | WriteRaw(s->data_, len(s)); |
260 | 4 | } |
261 | | |
262 | 2 | void BufWriter::write_spaces(int n) { |
263 | 2 | if (n == 0) { |
264 | 1 | return; |
265 | 1 | } |
266 | | |
267 | 1 | EnsureMoreSpace(n); |
268 | | |
269 | 1 | char* dest = str_->data_ + len_; |
270 | 4 | for (int i = 0; i < n; ++i) { |
271 | 3 | dest[i] = ' '; |
272 | 3 | } |
273 | 1 | len_ += n; |
274 | 1 | str_->data_[len_] = '\0'; |
275 | 1 | } |
276 | | |
277 | 4 | BigStr* BufWriter::getvalue() { |
278 | 4 | DCHECK(is_valid_); // Check for two INVALID getvalue() in a row |
279 | 0 | is_valid_ = false; |
280 | | |
281 | 4 | if (str_ == nullptr) { // if no write() methods are called, the result is "" |
282 | 1 | return kEmptyString; |
283 | 3 | } else { |
284 | 3 | BigStr* s = str_; |
285 | 3 | s->MaybeShrink(len_); |
286 | 3 | str_ = nullptr; |
287 | 3 | len_ = -1; |
288 | 3 | return s; |
289 | 3 | } |
290 | 4 | } |
291 | | |
292 | | } // namespace mylib |