1 /*
2 Z88DK Z80 Macro Assembler
3
4 Handle reading of source files, normalizing newline sequences, and allowing recursive
5 includes.
6 Allows pushing back of lines, for example to expand macros.
7 Call back interface to declare that a new line has been read.
8
9 Copyright (C) Gunther Strube, InterLogic 1993-99
10 Copyright (C) Paulo Custodio, 2011-2020
11 License: The Artistic License 2.0, http://www.perlfoundation.org/artistic_license_2_0
12 Repository: https://github.com/z88dk/z88dk
13 */
14
15 #include "../errors.h"
16 #include "alloc.h"
17 #include "die.h"
18 #include "fileutil.h"
19 #include "srcfile.h"
20 #include "strutil.h"
21 #include "utstring.h"
22
23 /*-----------------------------------------------------------------------------
24 * Type stored in file_stack
25 *----------------------------------------------------------------------------*/
26 typedef struct FileStackElem
27 {
28 FILE *file; /* open file */
29 const char *filename; /* source file name, held in strpool */
30 const char *line_filename; /* source file name of LINE statement, held in strpool */
31 int line_nr; /* current line number, i.e. last returned */
32 int line_inc; /* increment on each line read */
33 bool is_c_source; /* true if C_LINE was called */
34 } FileStackElem;
35
free_file_stack_elem(void * _elem)36 static void free_file_stack_elem( void *_elem )
37 {
38 FileStackElem *elem = _elem;
39
40 if ( elem->file != NULL )
41 xfclose( elem->file );
42 m_free( elem );
43 }
44
45 /*-----------------------------------------------------------------------------
46 * Call-back interace to declare a new line has been read, telling the
47 * file name and line number
48 *----------------------------------------------------------------------------*/
49 static new_line_cb_t new_line_cb = NULL; /* default handler */
50
51 /* set call-back for input/output error; return old call-back */
set_new_line_cb(new_line_cb_t func)52 new_line_cb_t set_new_line_cb( new_line_cb_t func )
53 {
54 new_line_cb_t old = new_line_cb;
55 new_line_cb = func;
56 return old;
57 }
58
59 /* call callback */
call_new_line_cb(const char * filename,int line_nr,const char * text)60 static void call_new_line_cb(const char *filename, int line_nr, const char *text )
61 {
62 if ( new_line_cb != NULL )
63 new_line_cb( filename, line_nr, text );
64 }
65
66 /*-----------------------------------------------------------------------------
67 * Call-back interace to show error on recursive include files
68 *----------------------------------------------------------------------------*/
69 static incl_recursion_err_cb_t incl_recursion_err_cb = NULL; /* default handler */
70
71 /* set call-back for input/output error; return old call-back */
set_incl_recursion_err_cb(incl_recursion_err_cb_t func)72 incl_recursion_err_cb_t set_incl_recursion_err_cb( incl_recursion_err_cb_t func )
73 {
74 incl_recursion_err_cb_t old = incl_recursion_err_cb;
75 incl_recursion_err_cb = func;
76 return old;
77 }
78
79 /*-----------------------------------------------------------------------------
80 * Class to hold current source file
81 *----------------------------------------------------------------------------*/
82 DEF_CLASS( SrcFile );
83
SrcFile_init(SrcFile * self)84 void SrcFile_init( SrcFile *self )
85 {
86 self->filename = NULL;
87 self->line_filename = NULL;
88
89 self->line = Str_new(STR_SIZE);
90
91 self->line_stack = OBJ_NEW( List );
92 OBJ_AUTODELETE( self->line_stack ) = false;
93 self->line_stack->free_data = m_free_compat;
94
95 self->file_stack = OBJ_NEW( List );
96 OBJ_AUTODELETE( self->file_stack ) = false;
97 self->file_stack->free_data = free_file_stack_elem;
98 }
99
SrcFile_copy(SrcFile * self,SrcFile * other)100 void SrcFile_copy( SrcFile *self, SrcFile *other )
101 {
102 xassert(0);
103 }
104
SrcFile_fini(SrcFile * self)105 void SrcFile_fini( SrcFile *self )
106 {
107 if ( self->file != NULL )
108 xfclose( self->file );
109
110 Str_delete(self->line);
111 OBJ_DELETE( self->line_stack );
112 OBJ_DELETE( self->file_stack );
113 }
114
115 /*-----------------------------------------------------------------------------
116 * SrcFile API
117 *----------------------------------------------------------------------------*/
118
119 /* check for recursive includes, call error callback and return false abort if found
120 returns true if callback not defined */
check_recursive_include(SrcFile * self,const char * filename)121 static bool check_recursive_include( SrcFile *self, const char *filename )
122 {
123 ListElem *iter;
124 FileStackElem *elem;
125
126 if ( incl_recursion_err_cb != NULL )
127 {
128 for ( iter = List_first( self->file_stack ) ; iter != NULL ;
129 iter = List_next( iter ) )
130 {
131 elem = iter->data;
132 if ( elem->filename != NULL &&
133 strcmp( filename, elem->filename ) == 0 )
134 {
135 incl_recursion_err_cb( filename );
136 return false;
137 }
138 }
139 }
140 return true;
141 }
142
143 /* Open the source file for reading, closing any previously open file.
144 If dir_list is not NULL, calls path_search() to search the file in dir_list */
SrcFile_open(SrcFile * self,const char * filename,UT_array * dir_list)145 bool SrcFile_open( SrcFile *self, const char *filename, UT_array *dir_list )
146 {
147 /* close last file */
148 if (self->file != NULL)
149 {
150 xfclose(self->file);
151 self->file = NULL;
152 }
153
154 /* search path, add to strpool */
155 const char *filename_path = path_search(filename, dir_list);
156
157 /* check for recursive includes, return if found */
158 if (!check_recursive_include(self, filename_path))
159 return false;
160
161 self->filename = filename_path;
162 self->line_filename = filename_path;
163
164 /* open new file in binary mode, for cross-platform newline processing */
165 self->file = fopen( self->filename, "rb" );
166 if (!self->file)
167 error_read_file(self->filename);
168
169 /* init current line */
170 Str_clear( self->line );
171 self->line_nr = 0;
172 self->line_inc = 1;
173 self->is_c_source = false;
174
175 if (self->file)
176 return true;
177 else
178 return false; /* error opening file */
179 }
180
181 /* get the next line of input, normalize end of line termination (i.e. convert
182 "\r", "\r\n" and "\n\r" to "\n"
183 Calls the new_line_cb call back and returns the pointer to the null-terminated
184 text data in Str *line, including the final "\n".
185 Returns NULL on end of file. */
SrcFile_getline(SrcFile * self)186 char *SrcFile_getline( SrcFile *self )
187 {
188 int c, c1;
189 bool found_newline;
190 char *line;
191
192 /* clear result string */
193 Str_clear( self->line );
194
195 /* check for line stack */
196 if ( ! List_empty( self->line_stack ) )
197 {
198 line = List_pop( self->line_stack );
199
200 /* we own the string now and need to release memory */
201 Str_set( self->line, line );
202 m_free( line );
203
204 /* dont increment line number as we are still on same file input line */
205 return Str_data(self->line);
206 }
207
208 /* check for EOF condition */
209 if ( self->file == NULL )
210 return NULL;
211
212 /* read characters */
213 found_newline = false;
214 while ( ! found_newline && ( c = getc( self->file ) ) != EOF )
215 {
216 switch ( c )
217 {
218 case '\r':
219 case '\n':
220 c1 = getc( self->file );
221
222 if ( ( c1 == '\r' || c1 == '\n' ) && /* next char also newline */
223 c1 != c ) /* "\r\n" or "\n\r" */
224 {
225 /* c1 will be discarded */
226 }
227 else /* not composite newline - push back */
228 {
229 if ( c1 != EOF )
230 {
231 ungetc( c1, self->file ); /* push back except EOF */
232 }
233 }
234
235 /* normalize newline and fall through to default */
236 found_newline = true;
237 c = '\n';
238
239 default:
240 Str_append_char( self->line, c );
241 }
242 }
243
244 /* terminate string if needed */
245 if ( Str_len(self->line) > 0 && ! found_newline )
246 Str_append_char( self->line, '\n' );
247
248 /* signal new line, even empty one, to show end line in list */
249 self->line_nr += self->line_inc;
250 call_new_line_cb( self->line_filename, self->line_nr, Str_data(self->line) );
251
252 /* check for end of file
253 even if EOF found, we need to return any chars in line first */
254 if ( Str_len(self->line) > 0 )
255 {
256 return Str_data(self->line);
257 }
258 else
259 {
260 /* EOF - close file */
261 xfclose( self->file ); /* close input */
262 self->file = NULL;
263
264 // call_new_line_cb( NULL, 0, NULL );
265 return NULL; /* EOF */
266 }
267 }
268
269 /* Search for the start of the next line in string, i.e. char after '\n' except last
270 Return NULL if only one line */
search_next_line(const char * lines)271 static const char *search_next_line(const char *lines )
272 {
273 char *nl_ptr;
274
275 nl_ptr = strchr( lines, '\n' );
276
277 if ( nl_ptr == NULL || nl_ptr[1] == '\0' )
278 return NULL; /* only one line */
279 else
280 return nl_ptr + 1; /* char after newline */
281 }
282
283 /* push lines to the line_stack, to be read next - they need to be pushed
284 in reverse order, i.e. last pushed is next to be retrieved
285 line may contain multiple lines separated by '\n', they are split and
286 pushed back-to-forth so that first text is first to retrieve from getline() */
SrcFile_ungetline(SrcFile * self,const char * lines)287 void SrcFile_ungetline( SrcFile *self, const char *lines )
288 {
289 char *line;
290 size_t len;
291
292 /* search next line after first '\n' */
293 const char *next_line = search_next_line( lines );
294
295 /* recurse to push this line at the end */
296 if ( next_line )
297 SrcFile_ungetline( self, next_line );
298
299 /* now push this line, add a newline if missing */
300 len = next_line ? next_line - lines : strlen( lines );
301
302 if ( len > 0 && lines[ len - 1 ] == '\n' )
303 len--; /* ignore newline */
304
305 line = m_malloc( len + 2 ); /* 2 bytes extra for '\n' and '\0' */
306 strncpy( line, lines, len );
307 line[ len ] = '\n';
308 line[ len + 1 ] = '\0';
309
310 List_push( & self->line_stack, line );
311 }
312
313 /* return the current file name and line number */
SrcFile_filename(SrcFile * self)314 const char *SrcFile_filename( SrcFile *self ) { return self->line_filename; }
SrcFile_line_nr(SrcFile * self)315 int SrcFile_line_nr( SrcFile *self ) { return self->line_nr; }
316
ScrFile_is_c_source(SrcFile * self)317 bool ScrFile_is_c_source(SrcFile * self)
318 {
319 return self->is_c_source;
320 }
321
SrcFile_set_filename(SrcFile * self,const char * filename)322 void SrcFile_set_filename(SrcFile * self, const char * filename)
323 {
324 self->line_filename = spool_add(filename);
325 }
326
SrcFile_set_line_nr(SrcFile * self,int line_nr,int line_inc)327 void SrcFile_set_line_nr(SrcFile * self, int line_nr, int line_inc)
328 {
329 self->line_nr = line_nr - line_inc;
330 self->line_inc = line_inc;
331 }
332
SrcFile_set_c_source(SrcFile * self)333 void SrcFile_set_c_source(SrcFile * self)
334 {
335 self->is_c_source = true;
336 }
337
338 /* stack of input files manipulation:
339 push saves current file on the stack and prepares for a new open
340 pop returns false if the stack is empty; else retrieves last file from stack
341 and updates current input */
SrcFile_push(SrcFile * self)342 void SrcFile_push( SrcFile *self )
343 {
344 FileStackElem *elem = m_new( FileStackElem );
345
346 elem->file = self->file;
347 elem->filename = self->filename;
348 elem->line_filename = self->line_filename;
349 elem->line_nr = self->line_nr;
350 elem->line_inc = self->line_inc;
351 elem->is_c_source = self->is_c_source;
352
353 List_push( & self->file_stack, elem );
354
355 self->file = NULL;
356 /* keep previous file name and location so that errors detected during
357 * macro expansion are shown on the correct line
358 * self->filename = NULL;
359 * self->line_nr = 0;
360 */
361 }
362
SrcFile_pop(SrcFile * self)363 bool SrcFile_pop( SrcFile *self )
364 {
365 FileStackElem *elem;
366
367 if ( List_empty( self->file_stack ) )
368 return false;
369
370 if ( self->file != NULL )
371 xfclose( self->file );
372
373 elem = List_pop( self->file_stack );
374 self->file = elem->file;
375 self->filename = elem->filename;
376 self->line_filename = elem->line_filename;
377 self->line_nr = elem->line_nr;
378 self->line_inc = elem->line_inc;
379 self->is_c_source = elem->is_c_source;
380
381 m_free( elem );
382 return true;
383 }
384