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