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

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