1 | /*****************************************************************************/ |
2 | /* */ |
3 | /* input.c */ |
4 | /* */ |
5 | /* Input file handling */ |
6 | /* */ |
7 | /* */ |
8 | /* */ |
9 | /* (C) 2000-2012, Ullrich von Bassewitz */ |
10 | /* Roemerstrasse 52 */ |
11 | /* D-70794 Filderstadt */ |
12 | /* EMail: uz@cc65.org */ |
13 | /* */ |
14 | /* */ |
15 | /* This software is provided 'as-is', without any expressed or implied */ |
16 | /* warranty. In no event will the authors be held liable for any damages */ |
17 | /* arising from the use of this software. */ |
18 | /* */ |
19 | /* Permission is granted to anyone to use this software for any purpose, */ |
20 | /* including commercial applications, and to alter it and redistribute it */ |
21 | /* freely, subject to the following restrictions: */ |
22 | /* */ |
23 | /* 1. The origin of this software must not be misrepresented; you must not */ |
24 | /* claim that you wrote the original software. If you use this software */ |
25 | /* in a product, an acknowledgment in the product documentation would be */ |
26 | /* appreciated but is not required. */ |
27 | /* 2. Altered source versions must be plainly marked as such, and must not */ |
28 | /* be misrepresented as being the original software. */ |
29 | /* 3. This notice may not be removed or altered from any source */ |
30 | /* distribution. */ |
31 | /* */ |
32 | /*****************************************************************************/ |
33 | |
34 | |
35 | |
36 | #include <stdio.h> |
37 | #include <string.h> |
38 | #include <errno.h> |
39 | |
40 | /* common */ |
41 | #include "check.h" |
42 | #include "coll.h" |
43 | #include "filestat.h" |
44 | #include "fname.h" |
45 | #include "print.h" |
46 | #include "strbuf.h" |
47 | #include "xmalloc.h" |
48 | |
49 | /* cc65 */ |
50 | #include "codegen.h" |
51 | #include "error.h" |
52 | #include "global.h" |
53 | #include "incpath.h" |
54 | #include "input.h" |
55 | #include "lineinfo.h" |
56 | #include "output.h" |
57 | |
58 | |
59 | |
60 | /*****************************************************************************/ |
61 | /* Data */ |
62 | /*****************************************************************************/ |
63 | |
64 | |
65 | |
66 | /* The current input line */ |
67 | StrBuf* Line; |
68 | |
69 | /* Current and next input character */ |
70 | char CurC = '\0'; |
71 | char NextC = '\0'; |
72 | |
73 | /* Maximum count of nested includes */ |
74 | #define MAX_INC_NESTING 16 |
75 | |
76 | /* Struct that describes an input file */ |
77 | typedef struct IFile IFile; |
78 | struct IFile { |
79 | unsigned Index; /* File index */ |
80 | unsigned Usage; /* Usage counter */ |
81 | unsigned long Size; /* File size */ |
82 | unsigned long MTime; /* Time of last modification */ |
83 | InputType Type; /* Type of input file */ |
84 | char Name[1]; /* Name of file (dynamically allocated) */ |
85 | }; |
86 | |
87 | /* Struct that describes an active input file */ |
88 | typedef struct AFile AFile; |
89 | struct AFile { |
90 | unsigned Line; /* Line number for this file */ |
91 | FILE* F; /* Input file stream */ |
92 | IFile* Input; /* Points to corresponding IFile */ |
93 | int SearchPath; /* True if we've added a path for this file */ |
94 | }; |
95 | |
96 | /* List of all input files */ |
97 | static Collection IFiles = STATIC_COLLECTION_INITIALIZER; |
98 | |
99 | /* List of all active files */ |
100 | static Collection AFiles = STATIC_COLLECTION_INITIALIZER; |
101 | |
102 | /* Input stack used when preprocessing. */ |
103 | static Collection InputStack = STATIC_COLLECTION_INITIALIZER; |
104 | |
105 | |
106 | |
107 | /*****************************************************************************/ |
108 | /* struct IFile */ |
109 | /*****************************************************************************/ |
110 | |
111 | |
112 | |
113 | static IFile* NewIFile (const char* Name, InputType Type) |
114 | /* Create and return a new IFile */ |
115 | { |
116 | /* Get the length of the name */ |
117 | unsigned Len = strlen (Name); |
118 | |
119 | /* Allocate a IFile structure */ |
120 | IFile* IF = (IFile*) xmalloc (sizeof (IFile) + Len); |
121 | |
122 | /* Initialize the fields */ |
123 | IF->Index = CollCount (&IFiles) + 1; |
124 | IF->Usage = 0; |
125 | IF->Size = 0; |
126 | IF->MTime = 0; |
127 | IF->Type = Type; |
128 | memcpy (IF->Name, Name, Len+1); |
129 | |
130 | /* Insert the new structure into the IFile collection */ |
131 | CollAppend (&IFiles, IF); |
132 | |
133 | /* Return the new struct */ |
134 | return IF; |
135 | } |
136 | |
137 | |
138 | |
139 | /*****************************************************************************/ |
140 | /* struct AFile */ |
141 | /*****************************************************************************/ |
142 | |
143 | |
144 | |
145 | static AFile* NewAFile (IFile* IF, FILE* F) |
146 | /* Create a new AFile, push it onto the stack, add the path of the file to |
147 | ** the path search list, and finally return a pointer to the new AFile struct. |
148 | */ |
149 | { |
150 | StrBuf Path = AUTO_STRBUF_INITIALIZER; |
151 | |
152 | /* Allocate a AFile structure */ |
153 | AFile* AF = (AFile*) xmalloc (sizeof (AFile)); |
154 | |
155 | /* Initialize the fields */ |
156 | AF->Line = 0; |
157 | AF->F = F; |
158 | AF->Input = IF; |
159 | |
160 | /* Increment the usage counter of the corresponding IFile. If this |
161 | ** is the first use, set the file data and output debug info if |
162 | ** requested. |
163 | */ |
164 | if (IF->Usage++ == 0) { |
165 | |
166 | /* Get file size and modification time. There a race condition here, |
167 | ** since we cannot use fileno() (non standard identifier in standard |
168 | ** header file), and therefore not fstat. When using stat with the |
169 | ** file name, there's a risk that the file was deleted and recreated |
170 | ** while it was open. Since mtime and size are only used to check |
171 | ** if a file has changed in the debugger, we will ignore this problem |
172 | ** here. |
173 | */ |
174 | struct stat Buf; |
175 | if (FileStat (IF->Name, &Buf) != 0) { |
176 | /* Error */ |
177 | Fatal ("Cannot stat '%s': %s" , IF->Name, strerror (errno)); |
178 | } |
179 | IF->Size = (unsigned long) Buf.st_size; |
180 | IF->MTime = (unsigned long) Buf.st_mtime; |
181 | |
182 | /* Set the debug data */ |
183 | g_fileinfo (IF->Name, IF->Size, IF->MTime); |
184 | } |
185 | |
186 | /* Insert the new structure into the AFile collection */ |
187 | CollAppend (&AFiles, AF); |
188 | |
189 | /* Get the path of this file and add it as an extra search path. |
190 | ** To avoid file search overhead, we will add one path only once. |
191 | ** This is checked by the PushSearchPath function. |
192 | */ |
193 | SB_CopyBuf (&Path, IF->Name, FindName (IF->Name) - IF->Name); |
194 | SB_Terminate (&Path); |
195 | AF->SearchPath = PushSearchPath (UsrIncSearchPath, SB_GetConstBuf (&Path)); |
196 | SB_Done (&Path); |
197 | |
198 | /* Return the new struct */ |
199 | return AF; |
200 | } |
201 | |
202 | |
203 | |
204 | static void FreeAFile (AFile* AF) |
205 | /* Free an AFile structure */ |
206 | { |
207 | xfree (AF); |
208 | } |
209 | |
210 | |
211 | |
212 | /*****************************************************************************/ |
213 | /* Code */ |
214 | /*****************************************************************************/ |
215 | |
216 | |
217 | |
218 | static IFile* FindFile (const char* Name) |
219 | /* Find the file with the given name in the list of all files. Since the list |
220 | ** is not large (usually less than 10), I don't care about using hashes or |
221 | ** similar things and do a linear search. |
222 | */ |
223 | { |
224 | unsigned I; |
225 | for (I = 0; I < CollCount (&IFiles); ++I) { |
226 | /* Get the file struct */ |
227 | IFile* IF = (IFile*) CollAt (&IFiles, I); |
228 | /* Check the name */ |
229 | if (strcmp (Name, IF->Name) == 0) { |
230 | /* Found, return the struct */ |
231 | return IF; |
232 | } |
233 | } |
234 | |
235 | /* Not found */ |
236 | return 0; |
237 | } |
238 | |
239 | |
240 | |
241 | void OpenMainFile (const char* Name) |
242 | /* Open the main file. Will call Fatal() in case of failures. */ |
243 | { |
244 | AFile* MainFile; |
245 | |
246 | |
247 | /* Setup a new IFile structure for the main file */ |
248 | IFile* IF = NewIFile (Name, IT_MAIN); |
249 | |
250 | /* Open the file for reading */ |
251 | FILE* F = fopen (Name, "r" ); |
252 | if (F == 0) { |
253 | /* Cannot open */ |
254 | Fatal ("Cannot open input file '%s': %s" , Name, strerror (errno)); |
255 | } |
256 | |
257 | /* Allocate a new AFile structure for the file */ |
258 | MainFile = NewAFile (IF, F); |
259 | |
260 | /* Allocate the input line buffer */ |
261 | Line = NewStrBuf (); |
262 | |
263 | /* Update the line infos, so we have a valid line info even at start of |
264 | ** the main file before the first line is read. |
265 | */ |
266 | UpdateLineInfo (MainFile->Input, MainFile->Line, Line); |
267 | } |
268 | |
269 | |
270 | |
271 | void OpenIncludeFile (const char* Name, InputType IT) |
272 | /* Open an include file and insert it into the tables. */ |
273 | { |
274 | char* N; |
275 | FILE* F; |
276 | IFile* IF; |
277 | |
278 | /* Check for the maximum include nesting */ |
279 | if (CollCount (&AFiles) > MAX_INC_NESTING) { |
280 | PPError ("Include nesting too deep" ); |
281 | return; |
282 | } |
283 | |
284 | /* Search for the file */ |
285 | N = SearchFile ((IT == IT_SYSINC)? SysIncSearchPath : UsrIncSearchPath, Name); |
286 | if (N == 0) { |
287 | PPError ("Include file '%s' not found" , Name); |
288 | return; |
289 | } |
290 | |
291 | /* Search the list of all input files for this file. If we don't find |
292 | ** it, create a new IFile object. |
293 | */ |
294 | IF = FindFile (N); |
295 | if (IF == 0) { |
296 | IF = NewIFile (N, IT); |
297 | } |
298 | |
299 | /* We don't need N any longer, since we may now use IF->Name */ |
300 | xfree (N); |
301 | |
302 | /* Open the file */ |
303 | F = fopen (IF->Name, "r" ); |
304 | if (F == 0) { |
305 | /* Error opening the file */ |
306 | PPError ("Cannot open include file '%s': %s" , IF->Name, strerror (errno)); |
307 | return; |
308 | } |
309 | |
310 | /* Debugging output */ |
311 | Print (stdout, 1, "Opened include file '%s'\n" , IF->Name); |
312 | |
313 | /* Allocate a new AFile structure */ |
314 | (void) NewAFile (IF, F); |
315 | } |
316 | |
317 | |
318 | |
319 | static void CloseIncludeFile (void) |
320 | /* Close an include file and switch to the higher level file. Set Input to |
321 | ** NULL if this was the main file. |
322 | */ |
323 | { |
324 | AFile* Input; |
325 | |
326 | /* Get the number of active input files */ |
327 | unsigned AFileCount = CollCount (&AFiles); |
328 | |
329 | /* Must have an input file when called */ |
330 | PRECONDITION (AFileCount > 0); |
331 | |
332 | /* Get the current active input file */ |
333 | Input = (AFile*) CollLast (&AFiles); |
334 | |
335 | /* Close the current input file (we're just reading so no error check) */ |
336 | fclose (Input->F); |
337 | |
338 | /* Delete the last active file from the active file collection */ |
339 | CollDelete (&AFiles, AFileCount-1); |
340 | |
341 | /* If we had added an extra search path for this AFile, remove it */ |
342 | if (Input->SearchPath) { |
343 | PopSearchPath (UsrIncSearchPath); |
344 | } |
345 | |
346 | /* Delete the active file structure */ |
347 | FreeAFile (Input); |
348 | } |
349 | |
350 | |
351 | |
352 | static void GetInputChar (void) |
353 | /* Read the next character from the input stream and make CurC and NextC |
354 | ** valid. If end of line is reached, both are set to NUL, no more lines |
355 | ** are read by this function. |
356 | */ |
357 | { |
358 | /* Drop all pushed fragments that don't have data left */ |
359 | while (SB_GetIndex (Line) >= SB_GetLen (Line)) { |
360 | /* Cannot read more from this line, check next line on stack if any */ |
361 | if (CollCount (&InputStack) == 0) { |
362 | /* This is THE line */ |
363 | break; |
364 | } |
365 | FreeStrBuf (Line); |
366 | Line = CollPop (&InputStack); |
367 | } |
368 | |
369 | /* Now get the next characters from the line */ |
370 | if (SB_GetIndex (Line) >= SB_GetLen (Line)) { |
371 | CurC = NextC = '\0'; |
372 | } else { |
373 | CurC = SB_AtUnchecked (Line, SB_GetIndex (Line)); |
374 | if (SB_GetIndex (Line) + 1 < SB_GetLen (Line)) { |
375 | /* NextC comes from this fragment */ |
376 | NextC = SB_AtUnchecked (Line, SB_GetIndex (Line) + 1); |
377 | } else { |
378 | /* NextC comes from next fragment */ |
379 | if (CollCount (&InputStack) > 0) { |
380 | NextC = ' '; |
381 | } else { |
382 | NextC = '\0'; |
383 | } |
384 | } |
385 | } |
386 | } |
387 | |
388 | |
389 | |
390 | void NextChar (void) |
391 | /* Skip the current input character and read the next one from the input |
392 | ** stream. CurC and NextC are valid after the call. If end of line is |
393 | ** reached, both are set to NUL, no more lines are read by this function. |
394 | */ |
395 | { |
396 | /* Skip the last character read */ |
397 | SB_Skip (Line); |
398 | |
399 | /* Read the next one */ |
400 | GetInputChar (); |
401 | } |
402 | |
403 | |
404 | |
405 | void ClearLine (void) |
406 | /* Clear the current input line */ |
407 | { |
408 | unsigned I; |
409 | |
410 | /* Remove all pushed fragments from the input stack */ |
411 | for (I = 0; I < CollCount (&InputStack); ++I) { |
412 | FreeStrBuf (CollAtUnchecked (&InputStack, I)); |
413 | } |
414 | CollDeleteAll (&InputStack); |
415 | |
416 | /* Clear the contents of Line */ |
417 | SB_Clear (Line); |
418 | CurC = '\0'; |
419 | NextC = '\0'; |
420 | } |
421 | |
422 | |
423 | |
424 | StrBuf* InitLine (StrBuf* Buf) |
425 | /* Initialize Line from Buf and read CurC and NextC from the new input line. |
426 | ** The function returns the old input line. |
427 | */ |
428 | { |
429 | StrBuf* OldLine = Line; |
430 | Line = Buf; |
431 | CurC = SB_LookAt (Buf, SB_GetIndex (Buf)); |
432 | NextC = SB_LookAt (Buf, SB_GetIndex (Buf) + 1); |
433 | return OldLine; |
434 | } |
435 | |
436 | |
437 | |
438 | int NextLine (void) |
439 | /* Get a line from the current input. Returns 0 on end of file. */ |
440 | { |
441 | AFile* Input; |
442 | |
443 | /* Clear the current line */ |
444 | ClearLine (); |
445 | |
446 | /* If there is no file open, bail out, otherwise get the current input file */ |
447 | if (CollCount (&AFiles) == 0) { |
448 | return 0; |
449 | } |
450 | Input = CollLast (&AFiles); |
451 | |
452 | /* Read characters until we have one complete line */ |
453 | while (1) { |
454 | |
455 | /* Read the next character */ |
456 | int C = fgetc (Input->F); |
457 | |
458 | /* Check for EOF */ |
459 | if (C == EOF) { |
460 | |
461 | /* Accept files without a newline at the end */ |
462 | if (SB_NotEmpty (Line)) { |
463 | ++Input->Line; |
464 | break; |
465 | } |
466 | |
467 | /* Leave the current file */ |
468 | CloseIncludeFile (); |
469 | |
470 | /* If there is no file open, bail out, otherwise get the |
471 | ** previous input file and start over. |
472 | */ |
473 | if (CollCount (&AFiles) == 0) { |
474 | return 0; |
475 | } |
476 | Input = CollLast (&AFiles); |
477 | continue; |
478 | } |
479 | |
480 | /* Check for end of line */ |
481 | if (C == '\n') { |
482 | |
483 | /* We got a new line */ |
484 | ++Input->Line; |
485 | |
486 | /* If the \n is preceeded by a \r, remove the \r, so we can read |
487 | ** DOS/Windows files under *nix. |
488 | */ |
489 | if (SB_LookAtLast (Line) == '\r') { |
490 | SB_Drop (Line, 1); |
491 | } |
492 | |
493 | /* If we don't have a line continuation character at the end, |
494 | ** we're done with this line. Otherwise replace the character |
495 | ** by a newline and continue reading. |
496 | */ |
497 | if (SB_LookAtLast (Line) == '\\') { |
498 | Line->Buf[Line->Len-1] = '\n'; |
499 | } else { |
500 | break; |
501 | } |
502 | |
503 | } else if (C != '\0') { /* Ignore embedded NULs */ |
504 | |
505 | /* Just some character, add it to the line */ |
506 | SB_AppendChar (Line, C); |
507 | |
508 | } |
509 | } |
510 | |
511 | /* Add a termination character to the string buffer */ |
512 | SB_Terminate (Line); |
513 | |
514 | /* Initialize the current and next characters. */ |
515 | InitLine (Line); |
516 | |
517 | /* Create line information for this line */ |
518 | UpdateLineInfo (Input->Input, Input->Line, Line); |
519 | |
520 | /* Done */ |
521 | return 1; |
522 | } |
523 | |
524 | |
525 | |
526 | const char* GetInputFile (const struct IFile* IF) |
527 | /* Return a filename from an IFile struct */ |
528 | { |
529 | return IF->Name; |
530 | } |
531 | |
532 | |
533 | |
534 | const char* GetCurrentFile (void) |
535 | /* Return the name of the current input file */ |
536 | { |
537 | unsigned AFileCount = CollCount (&AFiles); |
538 | if (AFileCount > 0) { |
539 | const AFile* AF = (const AFile*) CollAt (&AFiles, AFileCount-1); |
540 | return AF->Input->Name; |
541 | } else { |
542 | /* No open file. Use the main file if we have one. */ |
543 | unsigned IFileCount = CollCount (&IFiles); |
544 | if (IFileCount > 0) { |
545 | const IFile* IF = (const IFile*) CollAt (&IFiles, 0); |
546 | return IF->Name; |
547 | } else { |
548 | return "(outside file scope)" ; |
549 | } |
550 | } |
551 | } |
552 | |
553 | |
554 | |
555 | unsigned GetCurrentLine (void) |
556 | /* Return the line number in the current input file */ |
557 | { |
558 | unsigned AFileCount = CollCount (&AFiles); |
559 | if (AFileCount > 0) { |
560 | const AFile* AF = (const AFile*) CollAt (&AFiles, AFileCount-1); |
561 | return AF->Line; |
562 | } else { |
563 | /* No open file */ |
564 | return 0; |
565 | } |
566 | } |
567 | |
568 | |
569 | |
570 | static void WriteEscaped (FILE* F, const char* Name) |
571 | /* Write a file name to a dependency file escaping spaces */ |
572 | { |
573 | while (*Name) { |
574 | if (*Name == ' ') { |
575 | /* Escape spaces */ |
576 | fputc ('\\', F); |
577 | } |
578 | fputc (*Name, F); |
579 | ++Name; |
580 | } |
581 | } |
582 | |
583 | |
584 | |
585 | static void WriteDep (FILE* F, InputType Types) |
586 | /* Helper function. Writes all file names that match Types to the output */ |
587 | { |
588 | unsigned I; |
589 | |
590 | /* Loop over all files */ |
591 | unsigned FileCount = CollCount (&IFiles); |
592 | for (I = 0; I < FileCount; ++I) { |
593 | |
594 | /* Get the next input file */ |
595 | const IFile* IF = (const IFile*) CollAt (&IFiles, I); |
596 | |
597 | /* Ignore it if it is not of the correct type */ |
598 | if ((IF->Type & Types) == 0) { |
599 | continue; |
600 | } |
601 | |
602 | /* If this is not the first file, add a space */ |
603 | if (I > 0) { |
604 | fputc (' ', F); |
605 | } |
606 | |
607 | /* Print the dependency escaping spaces */ |
608 | WriteEscaped (F, IF->Name); |
609 | } |
610 | } |
611 | |
612 | |
613 | |
614 | static void CreateDepFile (const char* Name, InputType Types) |
615 | /* Create a dependency file with the given name and place dependencies for |
616 | ** all files with the given types there. |
617 | */ |
618 | { |
619 | /* Open the file */ |
620 | FILE* F = fopen (Name, "w" ); |
621 | if (F == 0) { |
622 | Fatal ("Cannot open dependency file '%s': %s" , Name, strerror (errno)); |
623 | } |
624 | |
625 | /* If a dependency target was given, use it, otherwise use the output |
626 | ** file name as target, followed by a tab character. |
627 | */ |
628 | if (SB_IsEmpty (&DepTarget)) { |
629 | WriteEscaped (F, OutputFilename); |
630 | } else { |
631 | WriteEscaped (F, SB_GetConstBuf (&DepTarget)); |
632 | } |
633 | fputs (":\t" , F); |
634 | |
635 | /* Write out the dependencies for the output file */ |
636 | WriteDep (F, Types); |
637 | fputs ("\n\n" , F); |
638 | |
639 | /* Write out a phony dependency for the included files */ |
640 | WriteDep (F, Types); |
641 | fputs (":\n\n" , F); |
642 | |
643 | /* Close the file, check for errors */ |
644 | if (fclose (F) != 0) { |
645 | remove (Name); |
646 | Fatal ("Cannot write to dependeny file (disk full?)" ); |
647 | } |
648 | } |
649 | |
650 | |
651 | |
652 | void CreateDependencies (void) |
653 | /* Create dependency files requested by the user */ |
654 | { |
655 | if (SB_NotEmpty (&DepName)) { |
656 | CreateDepFile (SB_GetConstBuf (&DepName), |
657 | IT_MAIN | IT_USRINC); |
658 | } |
659 | if (SB_NotEmpty (&FullDepName)) { |
660 | CreateDepFile (SB_GetConstBuf (&FullDepName), |
661 | IT_MAIN | IT_SYSINC | IT_USRINC); |
662 | } |
663 | } |
664 | |