/home/andy/git/oilshell/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 | 2 | 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 | 2 | gHeap.Init(50000); |
16 | 2 | } |
17 | | |
18 | 311 | void print_stderr(Str* s) { |
19 | 311 | fputs(s->data_, stderr); // prints until first NUL |
20 | 311 | fputc('\n', stderr); |
21 | 311 | } |
22 | | |
23 | | #if 0 |
24 | | void writeln(Str* 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 | | class MutableStr : public Str {}; |
38 | | |
39 | 184 | MutableStr* NewMutableStr(int cap) { |
40 | | // In order for everything to work, MutableStr must be identical in layout to |
41 | | // Str. One easy way to achieve this is for MutableStr to have no members and |
42 | | // to inherit from Str. |
43 | 184 | static_assert(sizeof(MutableStr) == sizeof(Str), |
44 | 184 | "Str and MutableStr must have same size"); |
45 | 184 | return reinterpret_cast<MutableStr*>(NewStr(cap)); |
46 | 184 | } |
47 | | |
48 | 10 | Tuple2<Str*, Str*> split_once(Str* s, Str* delim) { |
49 | 10 | assert(len(delim) == 1); |
50 | | |
51 | 0 | const char* start = s->data_; // note: this pointer may move |
52 | 10 | char c = delim->data_[0]; |
53 | 10 | int length = len(s); |
54 | | |
55 | 10 | const char* p = static_cast<const char*>(memchr(start, c, length)); |
56 | | |
57 | 10 | if (p) { |
58 | 6 | int len1 = p - start; |
59 | 6 | int len2 = length - len1 - 1; // -1 for delim |
60 | | |
61 | 6 | Str* s1 = nullptr; |
62 | 6 | Str* s2 = nullptr; |
63 | | // Allocate together to avoid 's' moving in between |
64 | 6 | s1 = NewStr(len1); |
65 | 6 | s2 = NewStr(len2); |
66 | | |
67 | 6 | memcpy(s1->data_, s->data_, len1); |
68 | 6 | memcpy(s2->data_, s->data_ + len1 + 1, len2); |
69 | | |
70 | 6 | return Tuple2<Str*, Str*>(s1, s2); |
71 | 6 | } else { |
72 | 4 | return Tuple2<Str*, Str*>(s, nullptr); |
73 | 4 | } |
74 | 10 | } |
75 | | |
76 | | LineReader* gStdin; |
77 | | |
78 | 4 | LineReader* open(Str* path) { |
79 | | // TODO: Don't use C I/O; use POSIX I/O! |
80 | 4 | FILE* f = fopen(path->data_, "r"); |
81 | | |
82 | 4 | if (f == nullptr) { |
83 | 0 | throw Alloc<IOError>(errno); |
84 | 0 | } |
85 | 4 | return Alloc<CFileLineReader>(f); |
86 | 4 | } |
87 | | |
88 | 600 | Str* CFileLineReader::readline() { |
89 | 600 | char* line = nullptr; |
90 | 600 | size_t allocated_size = 0; // unused |
91 | | |
92 | | // Reset errno because we turn the EOF error into empty string (like Python). |
93 | 600 | errno = 0; |
94 | 600 | ssize_t len = getline(&line, &allocated_size, f_); |
95 | 600 | if (len < 0) { |
96 | | // man page says the buffer should be freed even if getline fails |
97 | 2 | free(line); |
98 | 2 | if (errno != 0) { // Unexpected error |
99 | 0 | log("getline() error: %s", strerror(errno)); |
100 | 0 | throw Alloc<IOError>(errno); |
101 | 0 | } |
102 | | // Expected EOF |
103 | 2 | return kEmptyString; |
104 | 2 | } |
105 | | |
106 | | // Note: getline() NUL terminates the buffer |
107 | 598 | Str* result = ::StrFromC(line, len); |
108 | 598 | free(line); |
109 | 598 | return result; |
110 | 600 | } |
111 | | |
112 | 2 | bool CFileLineReader::isatty() { |
113 | 2 | return ::isatty(fileno(f_)); |
114 | 2 | } |
115 | | |
116 | | // Problem: most Str methods like index() and slice() COPY so they have a |
117 | | // NUL terminator. |
118 | | // log("%s") falls back on sprintf, so it expects a NUL terminator. |
119 | | // It would be easier for us to just share. |
120 | 16 | Str* BufLineReader::readline() { |
121 | 16 | Str* line = nullptr; |
122 | | |
123 | 16 | int str_len = len(s_); |
124 | 16 | if (pos_ == str_len) { |
125 | 8 | return kEmptyString; |
126 | 8 | } |
127 | | |
128 | 8 | int orig_pos = pos_; |
129 | 8 | const char* p = strchr(s_->data_ + pos_, '\n'); |
130 | | // log("pos_ = %s", pos_); |
131 | 8 | int line_len; |
132 | 8 | if (p) { |
133 | 4 | int new_pos = p - s_->data_; |
134 | 4 | line_len = new_pos - pos_ + 1; // past newline char |
135 | 4 | pos_ = new_pos + 1; |
136 | 4 | } else { // leftover line |
137 | 4 | if (pos_ == 0) { // The string has no newlines at all -- just return it |
138 | 2 | pos_ = str_len; // advance to the end |
139 | 2 | return s_; |
140 | 2 | } else { |
141 | 2 | line_len = str_len - pos_; |
142 | 2 | pos_ = str_len; // advance to the end |
143 | 2 | } |
144 | 4 | } |
145 | | |
146 | 6 | line = NewStr(line_len); |
147 | 6 | memcpy(line->data_, s_->data_ + orig_pos, line_len); |
148 | 6 | assert(line->data_[line_len] == '\0'); |
149 | 0 | return line; |
150 | 8 | } |
151 | | |
152 | | Writer* gStdout; |
153 | | Writer* gStderr; |
154 | | |
155 | | // |
156 | | // CFileWriter |
157 | | // |
158 | | |
159 | 125 | void CFileWriter::write(Str* s) { |
160 | | // note: throwing away the return value |
161 | 125 | fwrite(s->data_, sizeof(char), len(s), f_); |
162 | 125 | } |
163 | | |
164 | 2 | void CFileWriter::flush() { |
165 | 2 | ::fflush(f_); |
166 | 2 | } |
167 | | |
168 | 2 | bool CFileWriter::isatty() { |
169 | 2 | return ::isatty(::fileno(f_)); |
170 | 2 | } |
171 | | |
172 | | // |
173 | | // BufWriter |
174 | | // |
175 | | |
176 | 762 | char* BufWriter::data() { |
177 | 762 | assert(str_); |
178 | 0 | return str_->data_; |
179 | 762 | } |
180 | | |
181 | 762 | char* BufWriter::end() { |
182 | 762 | assert(str_); |
183 | 0 | return str_->data_ + len_; |
184 | 762 | } |
185 | | |
186 | 2.35k | int BufWriter::capacity() { |
187 | 2.35k | return str_ ? len(str_) : 0; |
188 | 2.35k | } |
189 | | |
190 | 762 | void BufWriter::Extend(Str* s) { |
191 | 762 | const int n = len(s); |
192 | | |
193 | 762 | assert(capacity() >= len_ + n); |
194 | | |
195 | 0 | memcpy(end(), s->data_, n); |
196 | 762 | len_ += n; |
197 | 762 | data()[len_] = '\0'; |
198 | 762 | } |
199 | | |
200 | | // TODO: realloc() to new capacity instead of creating NewBuf() |
201 | 724 | void BufWriter::EnsureCapacity(int cap) { |
202 | 724 | assert(capacity() >= len_); |
203 | | |
204 | 724 | if (capacity() < cap) { |
205 | 146 | auto* s = NewMutableStr(std::max(capacity() * 2, cap)); |
206 | 146 | memcpy(s->data_, str_->data_, len_); |
207 | 146 | s->data_[len_] = '\0'; |
208 | 146 | str_ = s; |
209 | 146 | } |
210 | 724 | } |
211 | | |
212 | 771 | void BufWriter::write(Str* s) { |
213 | 771 | assert(is_valid_); // Can't write() after getvalue() |
214 | | |
215 | 0 | int n = len(s); |
216 | | |
217 | | // write('') is a no-op, so don't create Buf if we don't need to |
218 | 771 | if (n == 0) { |
219 | 9 | return; |
220 | 9 | } |
221 | | |
222 | 762 | if (str_ == nullptr) { |
223 | | // TODO: we could make the default capacity big enough for a line, e.g. 128 |
224 | | // capacity: 128 -> 256 -> 512 |
225 | 38 | str_ = NewMutableStr(n); |
226 | 724 | } else { |
227 | 724 | EnsureCapacity(len_ + n); |
228 | 724 | } |
229 | | |
230 | | // Append the contents to the buffer |
231 | 762 | Extend(s); |
232 | 762 | } |
233 | | |
234 | 31 | Str* BufWriter::getvalue() { |
235 | 31 | assert(is_valid_); // Check for two INVALID getvalue() in a row |
236 | 0 | is_valid_ = false; |
237 | | |
238 | 31 | if (str_ == nullptr) { // if no write() methods are called, the result is "" |
239 | 2 | return kEmptyString; |
240 | 29 | } else { |
241 | 29 | Str* s = str_; |
242 | 29 | s->MaybeShrink(len_); |
243 | 29 | str_ = nullptr; |
244 | 29 | len_ = -1; |
245 | 29 | return s; |
246 | 29 | } |
247 | 31 | } |
248 | | |
249 | | } // namespace mylib |