OILS / mycpp / gc_mylib.cc View on Github | oilshell.org

292 lines, 158 significant
1#include "mycpp/gc_mylib.h"
2
3#include <errno.h>
4#include <stdio.h>
5#include <unistd.h> // isatty
6
7namespace mylib {
8
9void 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 gHeap.Init(50000);
16}
17
18void print_stderr(BigStr* s) {
19 fputs(s->data_, stderr); // prints until first NUL
20 fputc('\n', stderr);
21}
22
23#if 0
24void 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
37BigStr* JoinBytes(List<int>* byte_list) {
38 int n = len(byte_list);
39 BigStr* result = NewStr(n);
40 for (int i = 0; i < n; ++i) {
41 result->data_[i] = byte_list->at(i);
42 }
43 return result;
44}
45
46class MutableStr : public BigStr {};
47
48MutableStr* 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 static_assert(sizeof(MutableStr) == sizeof(BigStr),
53 "BigStr and MutableStr must have same size");
54 return reinterpret_cast<MutableStr*>(NewStr(n));
55}
56
57Tuple2<BigStr*, BigStr*> split_once(BigStr* s, BigStr* delim) {
58 DCHECK(len(delim) == 1);
59
60 const char* start = s->data_; // note: this pointer may move
61 char c = delim->data_[0];
62 int length = len(s);
63
64 const char* p = static_cast<const char*>(memchr(start, c, length));
65
66 if (p) {
67 int len1 = p - start;
68 int len2 = length - len1 - 1; // -1 for delim
69
70 BigStr* s1 = nullptr;
71 BigStr* s2 = nullptr;
72 // Allocate together to avoid 's' moving in between
73 s1 = NewStr(len1);
74 s2 = NewStr(len2);
75
76 memcpy(s1->data_, s->data_, len1);
77 memcpy(s2->data_, s->data_ + len1 + 1, len2);
78
79 return Tuple2<BigStr*, BigStr*>(s1, s2);
80 } else {
81 return Tuple2<BigStr*, BigStr*>(s, nullptr);
82 }
83}
84
85LineReader* gStdin;
86
87LineReader* open(BigStr* path) {
88 // TODO: Don't use C I/O; use POSIX I/O!
89 FILE* f = fopen(path->data_, "r");
90 if (f == nullptr) {
91 throw Alloc<IOError>(errno);
92 }
93
94 return reinterpret_cast<LineReader*>(Alloc<CFile>(f));
95}
96
97BigStr* CFile::readline() {
98 char* line = nullptr;
99 size_t allocated_size = 0; // unused
100
101 // Reset errno because we turn the EOF error into empty string (like Python).
102 errno = 0;
103 ssize_t len = getline(&line, &allocated_size, f_);
104 if (len < 0) {
105 // man page says the buffer should be freed even if getline fails
106 free(line);
107 if (errno != 0) { // Unexpected error
108 log("getline() error: %s", strerror(errno));
109 throw Alloc<IOError>(errno);
110 }
111 return kEmptyString; // EOF indicated by by empty string, like Python
112 }
113
114 // Note: getline() NUL-terminates the buffer
115 BigStr* result = ::StrFromC(line, len);
116 free(line);
117 return result;
118}
119
120bool CFile::isatty() {
121 return ::isatty(fileno(f_));
122}
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.
128BigStr* BufLineReader::readline() {
129 BigStr* line = nullptr;
130
131 int str_len = len(s_);
132 if (pos_ == str_len) {
133 return kEmptyString;
134 }
135
136 int orig_pos = pos_;
137 const char* p = strchr(s_->data_ + pos_, '\n');
138 // log("pos_ = %s", pos_);
139 int line_len;
140 if (p) {
141 int new_pos = p - s_->data_;
142 line_len = new_pos - pos_ + 1; // past newline char
143 pos_ = new_pos + 1;
144 } else { // leftover line
145 if (pos_ == 0) { // The string has no newlines at all -- just return it
146 pos_ = str_len; // advance to the end
147 return s_;
148 } else {
149 line_len = str_len - pos_;
150 pos_ = str_len; // advance to the end
151 }
152 }
153
154 line = NewStr(line_len);
155 memcpy(line->data_, s_->data_ + orig_pos, line_len);
156 DCHECK(line->data_[line_len] == '\0');
157 return line;
158}
159
160Writer* gStdout;
161Writer* gStderr;
162
163//
164// CFileWriter
165//
166
167void CFile::write(BigStr* s) {
168 // Writes can be short!
169 int n = len(s);
170 int num_written = ::fwrite(s->data_, sizeof(char), n, f_);
171 // Similar to CPython fileobject.c
172 if (num_written != n) {
173 throw Alloc<IOError>(errno);
174 }
175}
176
177void CFile::flush() {
178 if (::fflush(f_) != 0) {
179 throw Alloc<IOError>(errno);
180 }
181}
182
183void CFile::close() {
184 if (::fclose(f_) != 0) {
185 throw Alloc<IOError>(errno);
186 }
187}
188
189//
190// BufWriter
191//
192
193void BufWriter::EnsureMoreSpace(int n) {
194 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 str_ = NewMutableStr(n);
198 return;
199 }
200
201 int current_cap = len(str_);
202 DCHECK(current_cap >= len_);
203
204 int new_cap = len_ + n;
205
206 if (current_cap < new_cap) {
207 auto* s = NewMutableStr(std::max(current_cap * 2, new_cap));
208 memcpy(s->data_, str_->data_, len_);
209 s->data_[len_] = '\0';
210 str_ = s;
211 }
212}
213
214uint8_t* BufWriter::LengthPointer() {
215 // start + len
216 return reinterpret_cast<uint8_t*>(str_->data_) + len_;
217}
218
219uint8_t* BufWriter::CapacityPointer() {
220 // start + capacity
221 return reinterpret_cast<uint8_t*>(str_->data_) + str_->len_;
222}
223
224void BufWriter::SetLengthFrom(uint8_t* length_ptr) {
225 uint8_t* begin = reinterpret_cast<uint8_t*>(str_->data_);
226 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 len_ = length_ptr - begin;
230 // printf("SET LEN to %d\n", len_);
231}
232
233void BufWriter::Truncate(int length) {
234 len_ = length;
235}
236
237void BufWriter::WriteRaw(char* s, int n) {
238 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 if (n == 0) {
242 return;
243 }
244
245 EnsureMoreSpace(n);
246
247 // Append the contents to the buffer
248 memcpy(str_->data_ + len_, s, n);
249 len_ += n;
250 str_->data_[len_] = '\0';
251}
252
253void BufWriter::WriteConst(const char* c_string) {
254 // meant for short strings like '"'
255 WriteRaw(const_cast<char*>(c_string), strlen(c_string));
256}
257
258void BufWriter::write(BigStr* s) {
259 WriteRaw(s->data_, len(s));
260}
261
262void BufWriter::write_spaces(int n) {
263 if (n == 0) {
264 return;
265 }
266
267 EnsureMoreSpace(n);
268
269 char* dest = str_->data_ + len_;
270 for (int i = 0; i < n; ++i) {
271 dest[i] = ' ';
272 }
273 len_ += n;
274 str_->data_[len_] = '\0';
275}
276
277BigStr* BufWriter::getvalue() {
278 DCHECK(is_valid_); // Check for two INVALID getvalue() in a row
279 is_valid_ = false;
280
281 if (str_ == nullptr) { // if no write() methods are called, the result is ""
282 return kEmptyString;
283 } else {
284 BigStr* s = str_;
285 s->MaybeShrink(len_);
286 str_ = nullptr;
287 len_ = -1;
288 return s;
289 }
290}
291
292} // namespace mylib