OILS / mycpp / gc_mylib.cc View on Github | oils.pub

387 lines, 210 significant
1#include "mycpp/gc_mylib.h"
2
3#include <errno.h>
4#include <fcntl.h>
5#include <math.h>
6#include <stdio.h>
7#include <unistd.h> // isatty
8
9#include "mycpp/gc_iolib.h"
10
11namespace mylib {
12
13// Note: isinf() and isnan() MAY be macros, because we included <math.h> rather
14// than <cmath>. The latter defines std::isinf()
15
16bool isinf_(double f) {
17 return isinf(f);
18}
19
20bool isnan_(double f) {
21 return isnan(f);
22}
23
24void InitCppOnly() {
25 // We don't seem need this now that we have ctx_FlushStdout().
26 // setvbuf(stdout, 0, _IONBF, 0);
27
28 // Arbitrary threshold of 50K objects based on eyeballing
29 // benchmarks/osh-runtime 10K or 100K aren't too bad either.
30 gHeap.Init(50000);
31}
32
33void print_stderr(BigStr* s) {
34 fputs(s->data_, stderr); // prints until first NUL
35 fputc('\n', stderr);
36}
37
38#if 0
39void writeln(BigStr* s, int fd) {
40 // TODO: handle errors and write in a loop, like posix::write(). If possible,
41 // use posix::write directly, but that introduces some dependency problems.
42
43 if (write(fd, s->data_, len(s)) < 0) {
44 assert(0);
45 }
46 if (write(fd, "\n", 1) < 0) {
47 assert(0);
48 }
49}
50#endif
51
52BigStr* JoinBytes(List<int>* byte_list) {
53 int n = len(byte_list);
54 BigStr* result = NewStr(n);
55 for (int i = 0; i < n; ++i) {
56 result->data_[i] = byte_list->at(i);
57 }
58 return result;
59}
60
61// For BashArray
62void BigIntSort(List<mops::BigInt>* keys) {
63 keys->sort();
64}
65
66class MutableStr : public BigStr {};
67
68MutableStr* NewMutableStr(int n) {
69 // In order for everything to work, MutableStr must be identical in layout to
70 // BigStr. One easy way to achieve this is for MutableStr to have no members
71 // and to inherit from BigStr.
72 static_assert(sizeof(MutableStr) == sizeof(BigStr),
73 "BigStr and MutableStr must have same size");
74 return reinterpret_cast<MutableStr*>(NewStr(n));
75}
76
77Tuple2<BigStr*, BigStr*> split_once(BigStr* s, BigStr* delim) {
78 DCHECK(len(delim) == 1);
79
80 const char* start = s->data_; // note: this pointer may move
81 char c = delim->data_[0];
82 int length = len(s);
83
84 const char* p = static_cast<const char*>(memchr(start, c, length));
85
86 if (p) {
87 int len1 = p - start;
88 int len2 = length - len1 - 1; // -1 for delim
89
90 BigStr* s1 = nullptr;
91 BigStr* s2 = nullptr;
92 // Allocate together to avoid 's' moving in between
93 s1 = NewStr(len1);
94 s2 = NewStr(len2);
95
96 memcpy(s1->data_, s->data_, len1);
97 memcpy(s2->data_, s->data_ + len1 + 1, len2);
98
99 return Tuple2<BigStr*, BigStr*>(s1, s2);
100 } else {
101 return Tuple2<BigStr*, BigStr*>(s, nullptr);
102 }
103}
104
105LineReader* gStdin;
106
107LineReader* open(BigStr* path) {
108 int fd = ::open(path->data_, O_RDONLY);
109
110 if (fd < 0) {
111 throw Alloc<IOError>(errno);
112 }
113
114 return reinterpret_cast<LineReader*>(Alloc<CFile>(fd));
115}
116
117void CFile::read_into_buffer() {
118 ssize_t len = 0;
119
120 while (end_ < (BUF_SIZE-1)) {
121 len = ::read(fd_, buffer_+end_, BUF_SIZE-end_);
122
123 if (len < 0) {
124 if (errno == EINTR && iolib::gSignalSafe->PollUntrappedSigInt()) {
125 // Raise KeyboardInterrupt like mylib.Stdin().readline() does in Python!
126 // This affects _PlainPromptInput() in frontend/reader.py.
127 throw Alloc<KeyboardInterrupt>();
128 } else if (errno == EINTR) {
129 continue;
130 } else if (end_ > 0) {
131 eof_ = true;
132 break;
133 } else {
134 throw Alloc<IOError>(errno);
135 }
136 } else if (len == 0) {
137 eof_ = true;
138 break;
139 }
140 end_ += len;
141 }
142}
143
144BigStr* CFile::readline() {
145 // Reset errno because we turn the EOF error into empty string (like Python).
146 errno = 0;
147
148 // Fill the buffer if it's empty
149 // Check if any more data came in since the last EOF
150 if (end_ == 0 || eof_) {
151 eof_ = false;
152 read_into_buffer();
153 }
154
155 size_t line_len = 0;
156 char* newline_found = static_cast<char*>(memchr(buffer_ + pos_, '\n', end_ - pos_));
157 if (newline_found != NULL) {
158 line_len = newline_found - buffer_ - pos_ + 1;
159 } else if (eof_) {
160 line_len = end_ - pos_;
161 } else if (pos_ > 0) {
162
163 size_t new_pos = end_ - pos_;
164
165 if (pos_ > 0 && pos_ < end_) {
166 // Move the leftover data to the beginning of the buffer
167 // [ ....\n|XXXXX] -> [XXXXXYYYYY]
168 // ^ ^
169 // pos_ pos_
170
171 // Check if there will be an overlap during the move
172 if (pos_ >= (end_ - pos_)) {
173 // No overlap, safe to use memcpy
174 memcpy(buffer_, &(buffer_[pos_]), new_pos);
175 } else {
176 // Overlap, need to use memmove
177 memmove(buffer_, &(buffer_[pos_]), new_pos);
178 }
179 }
180 pos_ = 0;
181
182 // Read more data to fill the rest of the buffer
183 end_ = new_pos;
184 read_into_buffer();
185 line_len = end_;
186 if (!eof_) {
187 newline_found = static_cast<char*>(memchr(buffer_ + new_pos, '\n', end_ - new_pos));
188 if (newline_found != NULL) {
189 line_len = newline_found - buffer_ + 1;
190 } else {
191 throw Alloc<IOError>(90); // Line too long, didn't reach the newline char
192 }
193 }
194 } else {
195 throw Alloc<IOError>(90); // Line too long, didn't reach the newline char
196 }
197
198 BigStr* result = ::StrFromC(buffer_ + pos_, line_len);
199 pos_ += line_len;
200
201 return result;
202}
203
204bool CFile::isatty() {
205 return ::isatty(fd_);
206}
207
208// Problem: most BigStr methods like index() and slice() COPY so they have a
209// NUL terminator.
210// log("%s") falls back on sprintf, so it expects a NUL terminator.
211// It would be easier for us to just share.
212BigStr* BufLineReader::readline() {
213 BigStr* line = nullptr;
214
215 int str_len = len(s_);
216 if (pos_ == str_len) {
217 return kEmptyString;
218 }
219
220 int orig_pos = pos_;
221 const char* p = strchr(s_->data_ + pos_, '\n');
222 // log("pos_ = %s", pos_);
223 int line_len;
224 if (p) {
225 int new_pos = p - s_->data_;
226 line_len = new_pos - pos_ + 1; // past newline char
227 pos_ = new_pos + 1;
228 } else { // leftover line
229 if (pos_ == 0) { // The string has no newlines at all -- just return it
230 pos_ = str_len; // advance to the end
231 return s_;
232 } else {
233 line_len = str_len - pos_;
234 pos_ = str_len; // advance to the end
235 }
236 }
237
238 line = NewStr(line_len);
239 memcpy(line->data_, s_->data_ + orig_pos, line_len);
240 DCHECK(line->data_[line_len] == '\0');
241 return line;
242}
243
244Writer* gStdout;
245Writer* gStderr;
246
247//
248// CFileWriter
249//
250
251void CFile::write(BigStr* s) {
252 // Writes can be short!
253 int n = len(s);
254 int num_written = ::write(fd_, s->data_, n);
255 // Similar to CPython fileobject.c
256 if (num_written != n) {
257 throw Alloc<IOError>(errno);
258 }
259}
260
261void CFile::flush() {
262 // no-op for now
263}
264
265void CFile::close() {
266 if (::close(fd_) != 0) {
267 throw Alloc<IOError>(errno);
268 }
269}
270
271//
272// BufWriter
273//
274
275void BufWriter::EnsureMoreSpace(int n) {
276 if (str_ == nullptr) {
277 // TODO: we could make the default capacity big enough for a line, e.g. 128
278 // capacity: 128 -> 256 -> 512
279 str_ = NewMutableStr(n);
280 return;
281 }
282
283 int current_cap = len(str_);
284 DCHECK(current_cap >= len_);
285
286 int new_cap = len_ + n;
287
288 if (current_cap < new_cap) {
289 auto* s = NewMutableStr(std::max(current_cap * 2, new_cap));
290 memcpy(s->data_, str_->data_, len_);
291 s->data_[len_] = '\0';
292 str_ = s;
293 }
294}
295
296uint8_t* BufWriter::LengthPointer() {
297 // start + len
298 return reinterpret_cast<uint8_t*>(str_->data_) + len_;
299}
300
301uint8_t* BufWriter::CapacityPointer() {
302 // start + capacity
303 return reinterpret_cast<uint8_t*>(str_->data_) + str_->len_;
304}
305
306void BufWriter::SetLengthFrom(uint8_t* length_ptr) {
307 uint8_t* begin = reinterpret_cast<uint8_t*>(str_->data_);
308 DCHECK(length_ptr >= begin); // we should have written some data
309
310 // Set the length, e.g. so we know where to resume writing from
311 len_ = length_ptr - begin;
312 // printf("SET LEN to %d\n", len_);
313}
314
315void BufWriter::Truncate(int length) {
316 len_ = length;
317}
318
319void BufWriter::WriteRaw(char* s, int n) {
320 DCHECK(is_valid_); // Can't write() after getvalue()
321
322 // write('') is a no-op, so don't create Buf if we don't need to
323 if (n == 0) {
324 return;
325 }
326
327 EnsureMoreSpace(n);
328
329 // Append the contents to the buffer
330 memcpy(str_->data_ + len_, s, n);
331 len_ += n;
332 str_->data_[len_] = '\0';
333}
334
335void BufWriter::WriteConst(const char* c_string) {
336 // meant for short strings like '"'
337 WriteRaw(const_cast<char*>(c_string), strlen(c_string));
338}
339
340void BufWriter::write(BigStr* s) {
341 WriteRaw(s->data_, len(s));
342}
343
344void BufWriter::write_spaces(int n) {
345 DCHECK(n >= 0);
346 if (n == 0) {
347 return;
348 }
349
350 EnsureMoreSpace(n);
351
352 char* dest = str_->data_ + len_;
353 for (int i = 0; i < n; ++i) {
354 dest[i] = ' ';
355 }
356 len_ += n;
357 str_->data_[len_] = '\0';
358}
359
360BigStr* BufWriter::getvalue() {
361 DCHECK(is_valid_); // Check for two INVALID getvalue() in a row
362 is_valid_ = false;
363
364 if (str_ == nullptr) { // if no write() methods are called, the result is ""
365 return kEmptyString;
366 } else {
367 BigStr* s = str_;
368 s->MaybeShrink(len_);
369 str_ = nullptr;
370 len_ = -1;
371 return s;
372 }
373}
374
375bool StatResult::isreg() {
376 return S_ISREG(stat_result_.st_mode);
377}
378
379StatResult* stat(BigStr* filename) {
380 auto* st = Alloc<StatResult>();
381 if (::stat(filename->data_, &st->stat_result_) < 0) {
382 return nullptr;
383 }
384 return st;
385}
386
387} // namespace mylib