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