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

384 lines, 209 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 if (end_ == 0) {
149 read_into_buffer();
150 }
151
152 size_t line_len = 0;
153 char* newline_found = static_cast<char*>(memchr(buffer_ + pos_, '\n', end_ - pos_));
154 if (newline_found != NULL) {
155 line_len = newline_found - buffer_ - pos_ + 1;
156 } else if (eof_) {
157 line_len = end_ - pos_;
158 } else if (pos_ > 0) {
159
160 size_t new_pos = end_ - pos_;
161
162 if (pos_ > 0 && pos_ < end_) {
163 // Move the leftover data to the beginning of the buffer
164 // [ ....\n|XXXXX] -> [XXXXXYYYYY]
165 // ^ ^
166 // pos_ pos_
167
168 // Check if there will be an overlap during the move
169 if (pos_ >= (end_ - pos_)) {
170 // No overlap, safe to use memcpy
171 memcpy(buffer_, &(buffer_[pos_]), new_pos);
172 } else {
173 // Overlap, need to use memmove
174 memmove(buffer_, &(buffer_[pos_]), new_pos);
175 }
176 }
177 pos_ = 0;
178
179 // Read more data to fill the rest of the buffer
180 end_ = new_pos;
181 read_into_buffer();
182 line_len = end_;
183 if (!eof_) {
184 newline_found = static_cast<char*>(memchr(buffer_ + new_pos, '\n', end_ - new_pos));
185 if (newline_found != NULL) {
186 line_len = newline_found - buffer_ + 1;
187 } else {
188 throw Alloc<IOError>(90); // Line too long, didn't reach the newline char
189 }
190 }
191 } else {
192 throw Alloc<IOError>(90); // Line too long, didn't reach the newline char
193 }
194
195 BigStr* result = ::StrFromC(buffer_ + pos_, line_len);
196 pos_ += line_len;
197
198 return result;
199}
200
201bool CFile::isatty() {
202 return ::isatty(fd_);
203}
204
205// Problem: most BigStr methods like index() and slice() COPY so they have a
206// NUL terminator.
207// log("%s") falls back on sprintf, so it expects a NUL terminator.
208// It would be easier for us to just share.
209BigStr* BufLineReader::readline() {
210 BigStr* line = nullptr;
211
212 int str_len = len(s_);
213 if (pos_ == str_len) {
214 return kEmptyString;
215 }
216
217 int orig_pos = pos_;
218 const char* p = strchr(s_->data_ + pos_, '\n');
219 // log("pos_ = %s", pos_);
220 int line_len;
221 if (p) {
222 int new_pos = p - s_->data_;
223 line_len = new_pos - pos_ + 1; // past newline char
224 pos_ = new_pos + 1;
225 } else { // leftover line
226 if (pos_ == 0) { // The string has no newlines at all -- just return it
227 pos_ = str_len; // advance to the end
228 return s_;
229 } else {
230 line_len = str_len - pos_;
231 pos_ = str_len; // advance to the end
232 }
233 }
234
235 line = NewStr(line_len);
236 memcpy(line->data_, s_->data_ + orig_pos, line_len);
237 DCHECK(line->data_[line_len] == '\0');
238 return line;
239}
240
241Writer* gStdout;
242Writer* gStderr;
243
244//
245// CFileWriter
246//
247
248void CFile::write(BigStr* s) {
249 // Writes can be short!
250 int n = len(s);
251 int num_written = ::write(fd_, s->data_, n);
252 // Similar to CPython fileobject.c
253 if (num_written != n) {
254 throw Alloc<IOError>(errno);
255 }
256}
257
258void CFile::flush() {
259 // no-op for now
260}
261
262void CFile::close() {
263 if (::close(fd_) != 0) {
264 throw Alloc<IOError>(errno);
265 }
266}
267
268//
269// BufWriter
270//
271
272void BufWriter::EnsureMoreSpace(int n) {
273 if (str_ == nullptr) {
274 // TODO: we could make the default capacity big enough for a line, e.g. 128
275 // capacity: 128 -> 256 -> 512
276 str_ = NewMutableStr(n);
277 return;
278 }
279
280 int current_cap = len(str_);
281 DCHECK(current_cap >= len_);
282
283 int new_cap = len_ + n;
284
285 if (current_cap < new_cap) {
286 auto* s = NewMutableStr(std::max(current_cap * 2, new_cap));
287 memcpy(s->data_, str_->data_, len_);
288 s->data_[len_] = '\0';
289 str_ = s;
290 }
291}
292
293uint8_t* BufWriter::LengthPointer() {
294 // start + len
295 return reinterpret_cast<uint8_t*>(str_->data_) + len_;
296}
297
298uint8_t* BufWriter::CapacityPointer() {
299 // start + capacity
300 return reinterpret_cast<uint8_t*>(str_->data_) + str_->len_;
301}
302
303void BufWriter::SetLengthFrom(uint8_t* length_ptr) {
304 uint8_t* begin = reinterpret_cast<uint8_t*>(str_->data_);
305 DCHECK(length_ptr >= begin); // we should have written some data
306
307 // Set the length, e.g. so we know where to resume writing from
308 len_ = length_ptr - begin;
309 // printf("SET LEN to %d\n", len_);
310}
311
312void BufWriter::Truncate(int length) {
313 len_ = length;
314}
315
316void BufWriter::WriteRaw(char* s, int n) {
317 DCHECK(is_valid_); // Can't write() after getvalue()
318
319 // write('') is a no-op, so don't create Buf if we don't need to
320 if (n == 0) {
321 return;
322 }
323
324 EnsureMoreSpace(n);
325
326 // Append the contents to the buffer
327 memcpy(str_->data_ + len_, s, n);
328 len_ += n;
329 str_->data_[len_] = '\0';
330}
331
332void BufWriter::WriteConst(const char* c_string) {
333 // meant for short strings like '"'
334 WriteRaw(const_cast<char*>(c_string), strlen(c_string));
335}
336
337void BufWriter::write(BigStr* s) {
338 WriteRaw(s->data_, len(s));
339}
340
341void BufWriter::write_spaces(int n) {
342 DCHECK(n >= 0);
343 if (n == 0) {
344 return;
345 }
346
347 EnsureMoreSpace(n);
348
349 char* dest = str_->data_ + len_;
350 for (int i = 0; i < n; ++i) {
351 dest[i] = ' ';
352 }
353 len_ += n;
354 str_->data_[len_] = '\0';
355}
356
357BigStr* BufWriter::getvalue() {
358 DCHECK(is_valid_); // Check for two INVALID getvalue() in a row
359 is_valid_ = false;
360
361 if (str_ == nullptr) { // if no write() methods are called, the result is ""
362 return kEmptyString;
363 } else {
364 BigStr* s = str_;
365 s->MaybeShrink(len_);
366 str_ = nullptr;
367 len_ = -1;
368 return s;
369 }
370}
371
372bool StatResult::isreg() {
373 return S_ISREG(stat_result_.st_mode);
374}
375
376StatResult* stat(BigStr* filename) {
377 auto* st = Alloc<StatResult>();
378 if (::stat(filename->data_, &st->stat_result_) < 0) {
379 return nullptr;
380 }
381 return st;
382}
383
384} // namespace mylib