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