1 #include <stdlib.h>
2 #include <string.h>
3 #include <stdio.h>
4 #include <gc.h>
5 #include <errno.h>
6 #include <dirent.h>
7 #include "ml_file.h"
8 #include "ml_macros.h"
9
10 #define new(T) ((T *)GC_MALLOC(sizeof(T)))
11 #define anew(T, N) ((T *)GC_MALLOC((N) * sizeof(T)))
12 #define snew(N) ((char *)GC_MALLOC_ATOMIC(N))
13 #define xnew(T, N, U) ((T *)GC_MALLOC(sizeof(T) + (N) * sizeof(U)))
14
15 typedef struct ml_file_t {
16 ml_type_t *Type;
17 FILE *Handle;
18 } ml_file_t;
19
20 extern ml_cfunction_t MLFileOpen[];
21
22 ML_TYPE(MLFileT, (), "file",
23 .Constructor = (ml_value_t *)MLFileOpen
24 );
25
ml_file_finalize(ml_file_t * File,void * Data)26 static void ml_file_finalize(ml_file_t *File, void *Data) {
27 if (File->Handle) {
28 fclose(File->Handle);
29 File->Handle = NULL;
30 }
31 }
32
ML_FUNCTION(MLFileOpen)33 ML_FUNCTION(MLFileOpen) {
34 //!file
35 //@file
36 //<Path
37 //<Mode
38 //>file
39 ML_CHECK_ARG_COUNT(2);
40 ML_CHECK_ARG_TYPE(0, MLStringT);
41 ML_CHECK_ARG_TYPE(1, MLStringT);
42 const char *Path = ml_string_value(Args[0]);
43 const char *Mode = ml_string_value(Args[1]);
44 FILE *Handle = fopen(Path, Mode);
45 if (!Handle) return ml_error("FileError", "failed to open %s in mode %s: %s", Path, Mode, strerror(errno));
46 ml_file_t *File = new(ml_file_t);
47 File->Type = MLFileT;
48 File->Handle = Handle;
49 GC_register_finalizer(File, (void *)ml_file_finalize, 0, 0, 0);
50 return (ml_value_t *)File;
51 }
52
ml_file_handle(ml_value_t * Value)53 FILE *ml_file_handle(ml_value_t *Value) {
54 return ((ml_file_t *)Value)->Handle;
55 }
56
57 #ifdef __MINGW32__
ml_read_line(FILE * File,ssize_t Offset,char ** Result)58 static ssize_t ml_read_line(FILE *File, ssize_t Offset, char **Result) {
59 char Buffer[129];
60 if (fgets(Buffer, 129, File) == NULL) return -1;
61 int Length = strlen(Buffer);
62 if (Length == 128) {
63 ssize_t Total = ml_read_line(File, Offset + 128, Result);
64 memcpy(*Result + Offset, Buffer, 128);
65 return Total;
66 } else {
67 *Result = GC_MALLOC_ATOMIC(Offset + Length + 1);
68 strcpy(*Result + Offset, Buffer);
69 return Offset + Length;
70 }
71 }
72 #endif
73
74 ML_METHOD("read", MLFileT) {
75 //<File
76 //>string
77 ml_file_t *File = (ml_file_t *)Args[0];
78 if (!File->Handle) return ml_error("FileError", "file closed");
79 char *Line = 0;
80 size_t Length = 0;
81 #ifdef __MINGW32__
82 ssize_t Read = ml_read_line(File->Handle, 0, &Line);
83 #else
84 ssize_t Read = getline(&Line, &Length, File->Handle);
85 #endif
86 if (Read < 0) return feof(File->Handle) ? MLNil : ml_error("FileError", "error reading from file: %s", strerror(errno));
87 return ml_string(Line, Read);
88 }
89
90 ML_METHOD("read", MLFileT, MLIntegerT) {
91 //<File
92 //<Length
93 //>string
94 ml_file_t *File = (ml_file_t *)Args[0];
95 if (!File->Handle) return ml_error("FileError", "file closed");
96 if (feof(File->Handle)) return MLNil;
97 ssize_t Requested = ml_integer_value_fast(Args[1]);
98 ml_stringbuffer_t Final[1] = {ML_STRINGBUFFER_INIT};
99 char Buffer[ML_STRINGBUFFER_NODE_SIZE];
100 while (Requested >= ML_STRINGBUFFER_NODE_SIZE) {
101 ssize_t Actual = fread(Buffer, 1, ML_STRINGBUFFER_NODE_SIZE, File->Handle);
102 if (Actual < 0) return ml_error("FileError", "error reading from file: %s", strerror(errno));
103 if (Actual == 0) return ml_stringbuffer_value(Final);
104 ml_stringbuffer_add(Final, Buffer, Actual);
105 Requested -= Actual;
106 }
107 while (Requested > 0) {
108 ssize_t Actual = fread(Buffer, 1, Requested, File->Handle);
109 if (Actual < 0) return ml_error("FileError", "error reading from file: %s", strerror(errno));
110 if (Actual == 0) return ml_stringbuffer_value(Final);
111 ml_stringbuffer_add(Final, Buffer, Actual);
112 Requested -= Actual;
113 }
114 return ml_stringbuffer_value(Final);
115 }
116
117 ML_METHODV("write", MLFileT, MLStringT) {
118 //<File
119 //<String
120 //>File
121 ml_file_t *File = (ml_file_t *)Args[0];
122 if (!File->Handle) return ml_error("FileError", "file closed");
123 for (int I = 1; I < Count; ++I) {
124 const char *Chars = ml_string_value(Args[I]);
125 ssize_t Remaining = ml_string_length(Args[I]);
126 while (Remaining > 0) {
127 ssize_t Actual = fwrite(Chars, 1, Remaining, File->Handle);
128 if (Actual < 0) return ml_error("FileError", "error writing to file: %s", strerror(errno));
129 Chars += Actual;
130 Remaining -= Actual;
131 }
132 }
133 return Args[0];
134 }
135
ml_file_write_buffer_chars(ml_file_t * File,const char * Chars,size_t Remaining)136 static int ml_file_write_buffer_chars(ml_file_t *File, const char *Chars, size_t Remaining) {
137 while (Remaining > 0) {
138 ssize_t Actual = fwrite(Chars, 1, Remaining, File->Handle);
139 if (Actual < 0) return 1;
140 Chars += Actual;
141 Remaining -= Actual;
142 }
143 return 0;
144 }
145
146 ML_METHOD("write", MLFileT, MLStringBufferT) {
147 //<File
148 //<Buffer
149 //>File
150 ml_file_t *File = (ml_file_t *)Args[0];
151 if (!File->Handle) return ml_error("FileError", "file closed");
152 ml_stringbuffer_t *Buffer = (ml_stringbuffer_t *)Args[1];
153 if (ml_stringbuffer_foreach(Buffer, File, (void *)ml_file_write_buffer_chars)) return ml_error("FileError", "error writing to file: %s", strerror(errno));
154 return Args[0];
155 }
156
157 ML_METHOD("eof", MLFileT) {
158 //<File
159 //>File | nil
160 ml_file_t *File = (ml_file_t *)Args[0];
161 if (!File->Handle) return ml_error("FileError", "file closed");
162 if (feof(File->Handle)) return Args[0];
163 return MLNil;
164 }
165
166 ML_METHOD("close", MLFileT) {
167 //<File
168 //>nil
169 ml_file_t *File = (ml_file_t *)Args[0];
170 if (File->Handle) {
171 fclose(File->Handle);
172 File->Handle = 0;
173 }
174 return MLNil;
175 }
176
ml_file_new(FILE * Handle)177 ml_value_t *ml_file_new(FILE *Handle) {
178 ml_file_t *File = new(ml_file_t);
179 File->Type = MLFileT;
180 File->Handle = Handle;
181 GC_register_finalizer(File, (void *)ml_file_finalize, 0, 0, 0);
182 return (ml_value_t *)File;
183 }
184
ML_FUNCTION(MLFileRename)185 ML_FUNCTION(MLFileRename) {
186 //!file
187 //@file::rename
188 //<Old
189 //<New
190 //>nil
191 ML_CHECK_ARG_COUNT(2);
192 ML_CHECK_ARG_TYPE(0, MLStringT);
193 ML_CHECK_ARG_TYPE(1, MLStringT);
194 const char *OldName = ml_string_value(Args[0]);
195 const char *NewName = ml_string_value(Args[1]);
196 if (rename(OldName, NewName)) {
197 return ml_error("FileError", "failed to rename %s to %s: %s", OldName, NewName, strerror(errno));
198 }
199 return MLNil;
200 }
201
ML_FUNCTION(MLFileUnlink)202 ML_FUNCTION(MLFileUnlink) {
203 //!file
204 //@file::unlink
205 //<Path
206 ML_CHECK_ARG_COUNT(1);
207 ML_CHECK_ARG_TYPE(0, MLStringT);
208 const char *Name = ml_string_value(Args[0]);
209 if (unlink(Name)) {
210 return ml_error("FileError", "failed to unlink %s: %s", Name, strerror(errno));
211 }
212 return MLNil;
213 }
214
215 typedef struct {
216 ml_type_t *Type;
217 DIR *Handle;
218 ml_value_t *Entry;
219 int Index;
220 } ml_dir_t;
221
222 extern ml_cfunction_t MLDirOpen[];
223
224 ML_TYPE(MLDirT, (MLIteratableT), "directory",
225 .Constructor = (ml_value_t *)MLDirOpen
226 );
227
ml_dir_finalize(ml_dir_t * Dir,void * Data)228 static void ml_dir_finalize(ml_dir_t *Dir, void *Data) {
229 if (Dir->Handle) {
230 closedir(Dir->Handle);
231 Dir->Handle = NULL;
232 }
233 }
234
ML_FUNCTION(MLDirOpen)235 ML_FUNCTION(MLDirOpen) {
236 //@dir
237 //<Path
238 //>dir
239 ML_CHECK_ARG_COUNT(1);
240 ML_CHECK_ARG_TYPE(0, MLStringT);
241 const char *Path = ml_string_value(Args[0]);
242 DIR *Handle = opendir(Path);
243 if (!Handle) return ml_error("FileError", "failed to open %s: %s", Path, strerror(errno));
244 ml_dir_t *Dir = new(ml_dir_t);
245 Dir->Type = MLDirT;
246 Dir->Handle = Handle;
247 GC_register_finalizer(Dir, (void *)ml_dir_finalize, 0, 0, 0);
248 return (ml_value_t *)Dir;
249 }
250
251 ML_METHOD("read", MLDirT) {
252 //<Dir
253 //>string
254 ml_dir_t *Dir = (ml_dir_t *)Args[0];
255 struct dirent *Entry = readdir(Dir->Handle);
256 if (!Entry) return MLNil;
257 return ml_string(GC_strdup(Entry->d_name), -1);
258 }
259
ML_TYPED_FN(ml_iter_key,MLDirT,ml_state_t * Caller,ml_dir_t * Dir)260 static void ML_TYPED_FN(ml_iter_key, MLDirT, ml_state_t *Caller, ml_dir_t *Dir) {
261 ML_RETURN(ml_integer(Dir->Index));
262 }
263
ML_TYPED_FN(ml_iter_value,MLDirT,ml_state_t * Caller,ml_dir_t * Dir)264 static void ML_TYPED_FN(ml_iter_value, MLDirT, ml_state_t *Caller, ml_dir_t *Dir) {
265 ML_RETURN(Dir->Entry);
266 }
267
ML_TYPED_FN(ml_iter_next,MLDirT,ml_state_t * Caller,ml_dir_t * Dir)268 static void ML_TYPED_FN(ml_iter_next, MLDirT, ml_state_t *Caller, ml_dir_t *Dir) {
269 struct dirent *Entry = readdir(Dir->Handle);
270 if (!Entry) {
271 closedir(Dir->Handle);
272 Dir->Handle = NULL;
273 ML_RETURN(MLNil);
274 }
275 ++Dir->Index;
276 Dir->Entry = ml_string(GC_strdup(Entry->d_name), -1);
277 ML_RETURN(Dir);
278 }
279
ML_TYPED_FN(ml_iterate,MLDirT,ml_state_t * Caller,ml_dir_t * Dir)280 static void ML_TYPED_FN(ml_iterate, MLDirT, ml_state_t *Caller, ml_dir_t *Dir) {
281 struct dirent *Entry = readdir(Dir->Handle);
282 if (!Entry) {
283 closedir(Dir->Handle);
284 Dir->Handle = NULL;
285 ML_RETURN(MLNil);
286 }
287 Dir->Index = 1;
288 Dir->Entry = ml_string(GC_strdup(Entry->d_name), -1);
289 ML_RETURN(Dir);
290 }
291
ml_file_init(stringmap_t * Globals)292 void ml_file_init(stringmap_t *Globals) {
293 #include "ml_file_init.c"
294 stringmap_insert(MLFileT->Exports, "rename", MLFileRename);
295 stringmap_insert(MLFileT->Exports, "unlink", MLFileUnlink);
296 if (Globals) {
297 stringmap_insert(Globals, "file", MLFileT);
298 stringmap_insert(Globals, "dir", MLDirT);
299 }
300 }
301