]> git.sur5r.net Git - cc65/blob - src/dbginfo/dbginfo.c
First working version with complete API for line information.
[cc65] / src / dbginfo / dbginfo.c
1 /*****************************************************************************/
2 /*                                                                           */
3 /*                                 dbginfo.h                                 */
4 /*                                                                           */
5 /*                         cc65 debug info handling                          */
6 /*                                                                           */
7 /*                                                                           */
8 /*                                                                           */
9 /* (C) 2010,      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 <stdlib.h>
38 #include <stdarg.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <limits.h>
42 #include <assert.h>
43 #include <errno.h>
44
45 #include "dbginfo.h"
46
47
48
49 /*****************************************************************************/
50 /*                                   Data                                    */
51 /*****************************************************************************/
52
53
54
55 /* Dynamic strings */
56 typedef struct StrBuf StrBuf;
57 struct StrBuf {
58     char*       Buf;                    /* Pointer to buffer */
59     unsigned    Len;                    /* Length of the string */
60     unsigned    Allocated;              /* Size of allocated memory */
61 };
62
63 /* Initializer for a string buffer */
64 #define STRBUF_INITIALIZER      { 0, 0, 0 }
65
66 /* An empty string buf */
67 static const StrBuf EmptyStrBuf = STRBUF_INITIALIZER;
68
69 /* An array of pointers that grows if needed */
70 typedef struct Collection Collection;
71 struct Collection {
72     unsigned            Count;          /* Number of items in the list */
73     unsigned            Size;           /* Size of allocated array */
74     void**              Items;          /* Array with dynamic size */
75 };
76
77 /* Initializer for static collections */
78 #define COLLECTION_INITIALIZER  { 0, 0, 0 }
79
80 /* An empty collection */
81 static const Collection EmptyCollection = COLLECTION_INITIALIZER;
82
83
84
85 /* Data structure containing information from the debug info file. A pointer
86  * to this structure is passed as handle to callers from the outside.
87  */
88 typedef struct DbgInfo DbgInfo;
89 struct DbgInfo {
90     Collection          FileInfos;      /* Collection with file infos */
91 };
92
93 /* Input tokens */
94 typedef enum Token Token;
95 enum Token {
96
97     TOK_INVALID,                        /* Invalid token */
98     TOK_EOF,                            /* End of file reached */
99
100     TOK_INTCON,                         /* Integer constant */
101     TOK_STRCON,                         /* String constant */
102
103     TOK_EQUAL,                          /* = */
104     TOK_COMMA,                          /* , */
105     TOK_MINUS,                          /* - */
106     TOK_PLUS,                           /* + */
107     TOK_EOL,                            /* \n */
108
109     TOK_ABSOLUTE,                       /* ABSOLUTE keyword */
110     TOK_ADDRSIZE,                       /* ADDRSIZE keyword */
111     TOK_EQUATE,                         /* EQUATE keyword */
112     TOK_FILE,                           /* FILE keyword */
113     TOK_LABEL,                          /* LABEL keyword */
114     TOK_LINE,                           /* LINE keyword */
115     TOK_LONG,                           /* LONG_keyword */
116     TOK_MAJOR,                          /* MAJOR keyword */
117     TOK_MINOR,                          /* MINOR keyword */
118     TOK_MTIME,                          /* MTIME keyword */
119     TOK_RANGE,                          /* RANGE keyword */
120     TOK_RO,                             /* RO keyword */
121     TOK_RW,                             /* RW keyword */
122     TOK_SEGMENT,                        /* SEGMENT keyword */
123     TOK_SIZE,                           /* SIZE keyword */
124     TOK_START,                          /* START keyword */
125     TOK_SYM,                            /* SYM keyword */
126     TOK_TYPE,                           /* TYPE keyword */
127     TOK_VALUE,                          /* VALUE keyword */
128     TOK_VERSION,                        /* VERSION keyword */
129     TOK_ZEROPAGE,                       /* ZEROPAGE keyword */
130
131     TOK_IDENT,                          /* To catch unknown keywords */
132 };
133
134 /* Data used when parsing the debug info file */
135 typedef struct InputData InputData;
136 struct InputData {
137     const char*         FileName;       /* Name of input file */
138     cc65_line           Line;           /* Current line number */
139     unsigned            Col;            /* Current column number */
140     cc65_line           SLine;          /* Line number at start of token */
141     unsigned            SCol;           /* Column number at start of token */
142     unsigned            Errors;         /* Number of errors */
143     FILE*               F;              /* Input file */
144     int                 C;              /* Input character */
145     Token               Tok;            /* Token from input stream */
146     unsigned long       IVal;           /* Integer constant */
147     StrBuf              SVal;           /* String constant */
148     cc65_errorfunc      Error;          /* Function called in case of errors */
149     unsigned            MajorVersion;   /* Major version number */
150     unsigned            MinorVersion;   /* Minor version number */
151     Collection          LineInfos;      /* Line information */
152     DbgInfo*            Info;           /* Pointer to debug info */
153 };
154
155 /* Internally used file info struct */
156 typedef struct FileInfo FileInfo;
157 struct FileInfo {
158     unsigned long       Size;           /* Size of file */
159     unsigned long       MTime;          /* Modification time */
160     cc65_addr           Start;          /* Start address of line infos */
161     cc65_addr           End;            /* End address of line infos */
162     Collection          LineInfos;      /* Line infos for this file */
163     char                FileName[1];    /* Name of file with full path */
164 };
165
166 /* Internally used line info struct */
167 typedef struct LineInfo LineInfo;
168 struct LineInfo {
169     cc65_addr           Start;          /* Start of data range */
170     cc65_addr           End;            /* End of data range */
171     cc65_line           Line;           /* Line number */
172     FileInfo*           FileInfo;       /* Pointer to file info */
173     char                FileName[1];    /* Name of file */
174 };
175
176
177
178 /*****************************************************************************/
179 /*                                 Forwards                                  */
180 /*****************************************************************************/
181
182
183
184 static void NextToken (InputData* D);
185 /* Read the next token from the input stream */
186
187
188
189 /*****************************************************************************/
190 /*                             Memory allocation                             */
191 /*****************************************************************************/
192
193
194
195 static void* xmalloc (size_t Size)
196 /* Allocate memory, check for out of memory condition. Do some debugging */
197 {
198     void* P = 0;
199
200     /* Allow zero sized requests and return NULL in this case */
201     if (Size) {
202
203         /* Allocate memory */
204         P = malloc (Size);
205
206         /* Check for errors */
207         assert (P != 0);
208     }
209
210     /* Return a pointer to the block */
211     return P;
212 }
213
214
215
216 static void* xrealloc (void* P, size_t Size)
217 /* Reallocate a memory block, check for out of memory */
218 {
219     /* Reallocate the block */
220     void* N = realloc (P, Size);
221
222     /* Check for errors */
223     assert (N != 0 || Size == 0);
224
225     /* Return the pointer to the new block */
226     return N;
227 }
228
229
230
231 static void xfree (void* Block)
232 /* Free the block, do some debugging */
233 {
234     free (Block);
235 }
236
237
238
239 /*****************************************************************************/
240 /*                              Dynamic strings                              */
241 /*****************************************************************************/
242
243
244
245 static void SB_Done (StrBuf* B)
246 /* Free the data of a string buffer (but not the struct itself) */
247 {
248     if (B->Allocated) {
249         xfree (B->Buf);
250     }
251 }
252
253
254
255 static void SB_Realloc (StrBuf* B, unsigned NewSize)
256 /* Reallocate the string buffer space, make sure at least NewSize bytes are
257  * available.
258  */
259 {
260     /* Get the current size, use a minimum of 8 bytes */
261     unsigned NewAllocated = B->Allocated;
262     if (NewAllocated == 0) {
263         NewAllocated = 8;
264     }
265
266     /* Round up to the next power of two */
267     while (NewAllocated < NewSize) {
268         NewAllocated *= 2;
269     }
270
271     /* Reallocate the buffer. Beware: The allocated size may be zero while the
272      * length is not. This means that we have a buffer that wasn't allocated
273      * on the heap.
274      */
275     if (B->Allocated) {
276         /* Just reallocate the block */
277         B->Buf   = xrealloc (B->Buf, NewAllocated);
278     } else {
279         /* Allocate a new block and copy */
280         B->Buf   = memcpy (xmalloc (NewAllocated), B->Buf, B->Len);
281     }
282
283     /* Remember the new block size */
284     B->Allocated = NewAllocated;
285 }
286
287
288
289 static unsigned SB_GetLen (const StrBuf* B)
290 /* Return the length of the buffer contents */
291 {
292     return B->Len;
293 }
294
295
296
297 static const char* SB_GetConstBuf (const StrBuf* B)
298 /* Return a buffer pointer */
299 {
300     return B->Buf;
301 }
302
303
304
305 static void SB_Terminate (StrBuf* B)
306 /* Zero terminate the given string buffer. NOTE: The terminating zero is not
307  * accounted for in B->Len, if you want that, you have to use AppendChar!
308  */
309 {
310     unsigned NewLen = B->Len + 1;
311     if (NewLen > B->Allocated) {
312         SB_Realloc (B, NewLen);
313     }
314     B->Buf[B->Len] = '\0';
315 }
316
317
318
319 static void SB_Clear (StrBuf* B)
320 /* Clear the string buffer (make it empty) */
321 {
322     B->Len = 0;
323 }
324
325
326
327 static void SB_AppendChar (StrBuf* B, int C)
328 /* Append a character to a string buffer */
329 {
330     unsigned NewLen = B->Len + 1;
331     if (NewLen > B->Allocated) {
332         SB_Realloc (B, NewLen);
333     }
334     B->Buf[B->Len] = (char) C;
335     B->Len = NewLen;
336 }
337
338
339
340 /*****************************************************************************/
341 /*                                Collections                                */
342 /*****************************************************************************/
343
344
345
346 static Collection* InitCollection (Collection* C)
347 /* Initialize a collection and return it. */
348 {
349     /* Intialize the fields. */
350     C->Count = 0;
351     C->Size  = 0;
352     C->Items = 0;
353
354     /* Return the new struct */
355     return C;
356 }
357
358
359
360 static void DoneCollection (Collection* C)
361 /* Free the data for a collection. This will not free the data contained in
362  * the collection.
363  */
364 {
365     /* Free the pointer array */
366     xfree (C->Items);
367 }
368
369
370
371 static unsigned CollCount (const Collection* C)
372 /* Return the number of items in the collection */
373 {
374     return C->Count;
375 }
376
377
378
379 static void CollGrow (Collection* C, unsigned Size)
380 /* Grow the collection C so it is able to hold Size items without a resize
381  * being necessary. This can be called for performance reasons if the number
382  * of items to be placed in the collection is known in advance.
383  */
384 {
385     void** NewItems;
386
387     /* Ignore the call if the collection is already large enough */
388     if (Size <= C->Size) {
389         return;
390     }
391
392     /* Grow the collection */
393     C->Size = Size;
394     NewItems = xmalloc (C->Size * sizeof (void*));
395     memcpy (NewItems, C->Items, C->Count * sizeof (void*));
396     xfree (C->Items);
397     C->Items = NewItems;
398 }
399
400
401
402 static void CollInsert (Collection* C, void* Item, unsigned Index)
403 /* Insert the data at the given position in the collection */
404 {
405     /* Check for invalid indices */
406     assert (Index <= C->Count);
407
408     /* Grow the array if necessary */
409     if (C->Count >= C->Size) {
410         /* Must grow */
411         CollGrow (C, (C->Size == 0)? 8 : C->Size * 2);
412     }
413
414     /* Move the existing elements if needed */
415     if (C->Count != Index) {
416         memmove (C->Items+Index+1, C->Items+Index, (C->Count-Index) * sizeof (void*));
417     }
418     ++C->Count;
419
420     /* Store the new item */
421     C->Items[Index] = Item;
422 }
423
424
425
426 static void CollAppend (Collection* C, void* Item)
427 /* Append an item to the end of the collection */
428 {
429     /* Insert the item at the end of the current list */
430     CollInsert (C, Item, C->Count);
431 }
432
433
434
435 static void* CollAt (Collection* C, unsigned Index)
436 /* Return the item at the given index */
437 {
438     /* Check the index */
439     assert (Index < C->Count);
440
441     /* Return the element */
442     return C->Items[Index];
443 }
444
445
446
447 static void* CollFirst (Collection* C)
448 /* Return the first item in a collection */
449 {
450     /* We must have at least one entry */
451     assert (C->Count > 0);
452
453     /* Return the element */
454     return C->Items[0];
455 }
456
457
458
459 static void* CollLast (Collection* C)
460 /* Return the last item in a collection */
461 {
462     /* We must have at least one entry */
463     assert (C->Count > 0);
464
465     /* Return the element */
466     return C->Items[C->Count-1];
467 }
468
469
470
471 static void CollDelete (Collection* C, unsigned Index)
472 /* Remove the item with the given index from the collection. This will not
473  * free the item itself, just the pointer. All items with higher indices
474  * will get moved to a lower position.
475  */
476 {
477     /* Check the index */
478     assert (Index < C->Count);
479
480     /* Remove the item pointer */
481     --C->Count;
482     memmove (C->Items+Index, C->Items+Index+1, (C->Count-Index) * sizeof (void*));
483 }
484
485
486
487 static void CollReplace (Collection* C, void* Item, unsigned Index)
488 /* Replace the item at the given position. The old item will not be freed,
489  * just the pointer will get replaced.
490  */
491 {
492     /* Check the index */
493     assert (Index < C->Count);
494
495     /* Replace the item pointer */
496     C->Items[Index] = Item;
497 }
498
499
500
501 static void CollQuickSort (Collection* C, int Lo, int Hi,
502                            int (*Compare) (const void*, const void*))
503 /* Internal recursive sort function. */
504 {
505     /* Get a pointer to the items */
506     void** Items = C->Items;
507
508     /* Quicksort */
509     while (Hi > Lo) {
510         int I = Lo + 1;
511         int J = Hi;
512         while (I <= J) {
513             while (I <= J && Compare (Items[Lo], Items[I]) >= 0) {
514                 ++I;
515             }
516             while (I <= J && Compare (Items[Lo], Items[J]) < 0) {
517                 --J;
518             }
519             if (I <= J) {
520                 /* Swap I and J */
521                 void* Tmp = Items[I];
522                 Items[I]  = Items[J];
523                 Items[J]  = Tmp;
524                 ++I;
525                 --J;
526             }
527         }
528         if (J != Lo) {
529             /* Swap J and Lo */
530             void* Tmp = Items[J];
531             Items[J]  = Items[Lo];
532             Items[Lo] = Tmp;
533         }
534         if (J > (Hi + Lo) / 2) {
535             CollQuickSort (C, J + 1, Hi, Compare);
536             Hi = J - 1;
537         } else {
538             CollQuickSort (C, Lo, J - 1, Compare);
539             Lo = J + 1;
540         }
541     }
542 }
543
544
545
546 void CollSort (Collection* C, int (*Compare) (const void*, const void*))
547 /* Sort the collection using the given compare function. */
548 {
549     if (C->Count > 1) {
550         CollQuickSort (C, 0, C->Count-1, Compare);
551     }
552 }
553
554
555
556 /*****************************************************************************/
557 /*                                 Line info                                 */
558 /*****************************************************************************/
559
560
561
562 static LineInfo* NewLineInfo (const StrBuf* FileName)
563 /* Create a new LineInfo struct and return it */
564 {
565     /* Allocate memory */
566     LineInfo* L = xmalloc (sizeof (LineInfo) + SB_GetLen (FileName));
567
568     /* Initialize it */
569     L->Start    = 0;
570     L->End      = 0;
571     L->Line     = 0;
572     L->FileInfo = 0;
573     memcpy (L->FileName, SB_GetConstBuf (FileName), SB_GetLen (FileName) + 1);
574
575     /* Return it */
576     return L;
577 }
578
579
580
581 static void FreeLineInfo (LineInfo* L)
582 /* Free a LineInfo struct */
583 {
584     xfree (L);
585 }
586
587
588
589 static LineInfo* PreenLineInfo (LineInfo* L, FileInfo* F)
590 /* Replace the name by file information */
591 {
592     /* Shrink the LineInfo struct removing the FfileName field */
593     L = xrealloc (L, sizeof (*L) - 1);
594
595     /* Set the FileInfo pointer instead */
596     L->FileInfo = F;
597
598     /* Return the result */
599     return L;
600 }
601
602
603
604 static int CompareLineInfo (const void* L, const void* R)
605 /* Helper function to sort line infos in a collection */
606 {
607     /* Sort by start of range */
608     if (((const LineInfo*) L)->Start > ((const LineInfo*) R)->Start) {
609         return 1;
610     } else if (((const LineInfo*) L)->Start < ((const LineInfo*) R)->Start) {
611         return -1;
612     } else {
613         return 0;
614     }
615 }
616
617
618
619 /*****************************************************************************/
620 /*                                 File info                                 */
621 /*****************************************************************************/
622
623
624
625 static FileInfo* NewFileInfo (const StrBuf* FileName)
626 /* Create a new FileInfo struct and return it */
627 {
628     /* Allocate memory */
629     FileInfo* F = xmalloc (sizeof (FileInfo) + SB_GetLen (FileName));
630
631     /* Initialize it */
632     F->Size  = 0;
633     F->MTime = 0;
634     F->Start = ~(cc65_addr)0;
635     F->End   = 0;
636     InitCollection (&F->LineInfos);
637     memcpy (F->FileName, SB_GetConstBuf (FileName), SB_GetLen (FileName) + 1);
638
639     /* Return it */
640     return F;
641 }
642
643
644
645 static void FreeFileInfo (FileInfo* F)
646 /* Free a FileInfo struct */
647 {
648     unsigned I;
649
650     /* Walk through the collection with line infos and delete them */
651     for (I = 0; I < CollCount (&F->LineInfos); ++I) {
652         FreeLineInfo (CollAt (&F->LineInfos, I));
653     }
654     DoneCollection (&F->LineInfos);
655
656     /* Free the file info structure itself */
657     xfree (F);
658 }
659
660
661
662 static int CompareFileInfo (const void* L, const void* R)
663 /* Helper function to sort file infos in a collection */
664 {
665     /* Sort by file name */
666     return strcmp (((const FileInfo*) L)->FileName,
667                    ((const FileInfo*) R)->FileName);
668 }
669
670
671
672 /*****************************************************************************/
673 /*                                Debug info                                 */
674 /*****************************************************************************/
675
676
677
678 static DbgInfo* NewDbgInfo (void)
679 /* Create a new DbgInfo struct and return it */
680 {
681     /* Allocate memory */
682     DbgInfo* Info = xmalloc (sizeof (DbgInfo));
683
684     /* Initialize it */
685     InitCollection (&Info->FileInfos);
686
687     /* Return it */
688     return Info;
689 }
690
691
692
693 static void FreeDbgInfo (DbgInfo* Info)
694 /* Free a DbgInfo struct */
695 {
696     unsigned I;
697
698     /* Free file info */
699     for (I = 0; I < CollCount (&Info->FileInfos); ++I) {
700         FreeFileInfo (CollAt (&Info->FileInfos, I));
701     }
702     DoneCollection (&Info->FileInfos);
703
704     /* Free the structure itself */
705     xfree (Info);
706 }
707
708
709
710 /*****************************************************************************/
711 /*                             Helper functions                              */
712 /*****************************************************************************/
713
714
715
716 static void ParseError (InputData* D, cc65_error_severity Type, const char* Msg, ...)
717 /* Call the user supplied parse error function */
718 {
719     va_list             ap;
720     int                 MsgSize;
721     cc65_parseerror*    E;
722
723     /* Test-format the error message so we know how much space to allocate */
724     va_start (ap, Msg);
725     MsgSize = vsnprintf (0, 0, Msg, ap);
726     va_end (ap);
727
728     /* Allocate memory */
729     E = xmalloc (sizeof (*E) + MsgSize);
730
731     /* Write data to E */
732     E->type   = Type;
733     E->name   = D->FileName;
734     E->line   = D->SLine;
735     E->column = D->SCol;
736     va_start (ap, Msg);
737     vsnprintf (E->errormsg, MsgSize+1, Msg, ap);
738     va_end (ap);
739
740     /* Call the caller:-) */
741     D->Error (E);
742
743     /* Free the data structure */
744     xfree (E);
745
746     /* Count errors */
747     if (Type == CC65_ERROR) {
748         ++D->Errors;
749     }
750 }
751
752
753
754 static void SkipLine (InputData* D)
755 /* Error recovery routine. Skip tokens until EOL or EOF is reached */
756 {
757     while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) {
758         NextToken (D);
759     }
760 }
761
762
763
764 static void UnexpectedToken (InputData* D)
765 /* Call ParseError with a message about an unexpected input token */
766 {
767     ParseError (D, CC65_ERROR, "Unexpected input token %d", D->Tok);
768     SkipLine (D);
769 }
770
771
772
773 static void MissingAttribute (InputData* D, const char* AttrName)
774 /* Print an error about a missing attribute */
775 {
776     ParseError (D, CC65_ERROR, "Attribute \"%s\" is mising", AttrName);
777 }
778
779
780
781 /*****************************************************************************/
782 /*                            Scanner and parser                             */
783 /*****************************************************************************/
784
785
786
787 static int DigitVal (int C)
788 /* Return the value for a numeric digit. Return -1 if C is invalid */
789 {
790     if (isdigit (C)) {
791         return C - '0';
792     } else if (isxdigit (C)) {
793         return toupper (C) - 'A' + 10;
794     } else {
795         return -1;
796     }
797 }
798
799
800
801 static void NextChar (InputData* D)
802 /* Read the next character from the input. Count lines and columns */
803 {
804     /* Check if we've encountered EOF before */
805     if (D->C >= 0) {
806         D->C = fgetc (D->F);
807         if (D->C == '\n') {
808             ++D->Line;
809             D->Col = 0;
810         } else {
811             ++D->Col;
812         }
813     }
814 }
815
816
817
818 static void NextToken (InputData* D)
819 /* Read the next token from the input stream */
820 {
821     static const struct KeywordEntry  {
822         const char      Keyword[10];
823         Token           Tok;
824     } KeywordTable[] = {
825         { "absolute",   TOK_ABSOLUTE    },
826         { "addrsize",   TOK_ADDRSIZE    },
827         { "equate",     TOK_EQUATE      },
828         { "file",       TOK_FILE        },
829         { "label",      TOK_LABEL       },
830         { "line",       TOK_LINE        },
831         { "long",       TOK_LONG        },
832         { "major",      TOK_MAJOR       },
833         { "minor",      TOK_MINOR       },
834         { "mtime",      TOK_MTIME       },
835         { "range",      TOK_RANGE       },
836         { "ro",         TOK_RO          },
837         { "rw",         TOK_RW          },
838         { "segment",    TOK_SEGMENT     },
839         { "size",       TOK_SIZE        },
840         { "start",      TOK_START       },
841         { "sym",        TOK_SYM         },
842         { "type",       TOK_TYPE        },
843         { "value",      TOK_VALUE       },
844         { "version",    TOK_VERSION     },
845         { "zeropage",   TOK_ZEROPAGE    },
846     };
847
848
849     /* Skip whitespace */
850     while (D->C == ' ' || D->C == '\t') {
851         NextChar (D);
852     }
853
854     /* Remember the current position as start of the next token */
855     D->SLine = D->Line;
856     D->SCol  = D->Col;
857
858     /* Identifier? */
859     if (D->C == '_' || isalpha (D->C)) {
860
861         const struct KeywordEntry* Entry;
862
863         /* Read the identifier */
864         SB_Clear (&D->SVal);
865         while (D->C == '_' || isalnum (D->C)) {
866             SB_AppendChar (&D->SVal, D->C);
867             NextChar (D);
868         }
869         SB_Terminate (&D->SVal);
870
871         /* Search the identifier in the keyword table */
872         Entry = bsearch (SB_GetConstBuf (&D->SVal),
873                          KeywordTable,
874                          sizeof (KeywordTable) / sizeof (KeywordTable[0]),
875                          sizeof (KeywordTable[0]),
876                          (int (*)(const void*, const void*)) strcmp);
877         if (Entry == 0) {
878             D->Tok = TOK_IDENT;
879         } else {
880             D->Tok = Entry->Tok;
881         }
882         return;
883     }
884
885     /* Number? */
886     if (isdigit (D->C)) {
887         int Base = 10;
888         int Val;
889         if (D->C == '0') {
890             NextChar (D);
891             if (toupper (D->C) == 'X') {
892                 NextChar (D);
893                 Base = 16;
894             } else {
895                 Base = 8;
896             }
897         } else {
898             Base = 10;
899         }
900         D->IVal = 0;
901         while ((Val = DigitVal (D->C)) >= 0 && Val < Base) {
902             D->IVal = D->IVal * Base + Val;
903             NextChar (D);
904         }
905         D->Tok = TOK_INTCON;
906         return;
907     }
908
909     /* Other characters */
910     switch (D->C) {
911
912         case '-':
913             NextChar (D);
914             D->Tok = TOK_MINUS;
915             break;
916
917         case '+':
918             NextChar (D);
919             D->Tok = TOK_PLUS;
920             break;
921
922         case ',':
923             NextChar (D);
924             D->Tok = TOK_COMMA;
925             break;
926
927         case '=':
928             NextChar (D);
929             D->Tok = TOK_EQUAL;
930             break;
931
932         case '\"':
933             SB_Clear (&D->SVal);
934             NextChar (D);
935             while (1) {
936                 if (D->C == '\n' || D->C == EOF) {
937                     ParseError (D, CC65_ERROR, "Unterminated string constant");
938                     break;
939                 }
940                 if (D->C == '\"') {
941                     NextChar (D);
942                     break;
943                 }
944                 SB_AppendChar (&D->SVal, D->C);
945                 NextChar (D);
946             }
947             SB_Terminate (&D->SVal);
948             D->Tok = TOK_STRCON;
949             break;
950
951         case '\n':
952             NextChar (D);
953             D->Tok = TOK_EOL;
954             break;
955
956         case EOF:
957             D->Tok = TOK_EOF;
958             break;
959
960         default:
961             ParseError (D, CC65_ERROR, "Invalid input character `%c'", D->C);
962
963     }
964 }
965
966
967
968 static int TokenFollows (InputData* D, Token Tok, const char* Name)
969 /* Check for a comma */
970 {
971     if (D->Tok != Tok) {
972         ParseError (D, CC65_ERROR, "%s expected", Name);
973         SkipLine (D);
974         return 0;
975     } else {
976         return 1;
977     }
978 }
979
980
981
982 static int IntConstFollows (InputData* D)
983 /* Check for an integer constant */
984 {
985     return TokenFollows (D, TOK_INTCON, "Integer constant");
986 }
987
988
989
990 static int StringConstFollows (InputData* D)
991 /* Check for a string literal */
992 {
993     return TokenFollows (D, TOK_STRCON, "String literal");
994 }
995
996
997
998 static int Consume (InputData* D, Token Tok, const char* Name)
999 /* Check for a token and consume it. Return true if the token was comsumed,
1000  * return false otherwise.
1001  */
1002 {
1003     if (TokenFollows (D, Tok, Name)) {
1004         NextToken (D);
1005         return 1;
1006     } else {
1007         return 0;
1008     }
1009 }
1010
1011
1012
1013 static int ConsumeComma (InputData* D)
1014 /* Consume a comma */
1015 {
1016     return Consume (D, TOK_COMMA, "','");
1017 }
1018
1019
1020
1021 static int ConsumeEqual (InputData* D)
1022 /* Consume an equal sign */
1023 {
1024     return Consume (D, TOK_EQUAL, "'='");
1025 }
1026
1027
1028
1029 static int ConsumeMinus (InputData* D)
1030 /* Consume a minus sign */
1031 {
1032     return Consume (D, TOK_MINUS, "'-'");
1033 }
1034
1035
1036
1037 static void ParseFile (InputData* D)
1038 /* Parse a FILE line */
1039 {
1040     FileInfo* F;
1041     enum { None = 0x00, Size = 0x01, MTime = 0x02 } InfoBits = None;
1042
1043     /* Skip the FILE token */
1044     NextToken (D);
1045
1046     /* Name follows */
1047     if (!StringConstFollows (D)) {
1048         return;
1049     }
1050
1051     /* Allocate a new file info */
1052     F = NewFileInfo (&D->SVal);
1053
1054     /* Skip the file name */
1055     NextToken (D);
1056
1057     /* More stuff follows */
1058     while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) {
1059
1060         /* Comma follows before next attribute */
1061         if (!ConsumeComma (D)) {
1062             goto ErrorExit;
1063         }
1064
1065         switch (D->Tok) {
1066
1067             case TOK_SIZE:
1068                 NextToken (D);
1069                 if (!ConsumeEqual (D)) {
1070                     goto ErrorExit;
1071                 }
1072                 if (!IntConstFollows (D)) {
1073                     goto ErrorExit;
1074                 }
1075                 F->Size = D->IVal;
1076                 NextToken (D);
1077                 InfoBits |= Size;
1078                 break;
1079
1080             case TOK_MTIME:
1081                 NextToken (D);
1082                 if (!ConsumeEqual (D)) {
1083                     goto ErrorExit;
1084                 }
1085                 if (!IntConstFollows (D)) {
1086                     goto ErrorExit;
1087                 }
1088                 F->MTime = D->IVal;
1089                 NextToken (D);
1090                 InfoBits |= MTime;
1091                 break;
1092
1093             default:
1094                 UnexpectedToken (D);
1095                 SkipLine (D);
1096                 goto ErrorExit;
1097         }
1098
1099     }
1100
1101     /* Check for required information */
1102     if ((InfoBits & Size) == None) {
1103         MissingAttribute (D, "size");
1104         goto ErrorExit;
1105     }
1106     if ((InfoBits & MTime) == None) {
1107         MissingAttribute (D, "mtime");
1108         goto ErrorExit;
1109     }
1110
1111     /* Remember the file info */
1112     CollAppend (&D->Info->FileInfos, F);
1113
1114     /* Done */
1115     return;
1116
1117 ErrorExit:
1118     /* Entry point in case of errors */
1119     FreeFileInfo (F);
1120 }
1121
1122
1123
1124 static void ParseLine (InputData* D)
1125 /* Parse a LINE line */
1126 {
1127     LineInfo* L;
1128     enum { None = 0x00, Line = 0x01, Range = 0x02 } InfoBits = None;
1129
1130     /* Skip the LINE token */
1131     NextToken (D);
1132
1133     /* File name follows */
1134     if (!StringConstFollows (D)) {
1135         return;
1136     }
1137
1138     /* Allocate a new line info */
1139     L = NewLineInfo (&D->SVal);
1140
1141     /* Skip the file name */
1142     NextToken (D);
1143
1144     /* More stuff follows */
1145     while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) {
1146
1147         /* Comma follows before next attribute */
1148         if (!ConsumeComma (D)) {
1149             goto ErrorExit;
1150         }
1151
1152         switch (D->Tok) {
1153
1154             case TOK_LINE:
1155                 NextToken (D);
1156                 if (!ConsumeEqual (D)) {
1157                     goto ErrorExit;
1158                 }
1159                 if (!IntConstFollows (D)) {
1160                     goto ErrorExit;
1161                 }
1162                 L->Line = D->IVal;
1163                 NextToken (D);
1164                 InfoBits |= Line;
1165                 break;
1166
1167             case TOK_RANGE:
1168                 NextToken (D);
1169                 if (!ConsumeEqual (D)) {
1170                     goto ErrorExit;
1171                 }
1172                 if (!IntConstFollows (D)) {
1173                     goto ErrorExit;
1174                 }
1175                 L->Start = (cc65_addr) D->IVal;
1176                 NextToken (D);
1177                 if (!ConsumeMinus (D)) {
1178                     goto ErrorExit;
1179                 }
1180                 if (!IntConstFollows (D)) {
1181                     goto ErrorExit;
1182                 }
1183                 L->End = (cc65_addr) D->IVal;
1184                 NextToken (D);
1185                 InfoBits |= Range;
1186                 break;
1187
1188             default:
1189                 UnexpectedToken (D);
1190                 SkipLine (D);
1191                 goto ErrorExit;
1192         }
1193
1194     }
1195
1196     /* Check for required information */
1197     if ((InfoBits & Line) == None) {
1198         MissingAttribute (D, "line");
1199         goto ErrorExit;
1200     }
1201     if ((InfoBits & Range) == None) {
1202         MissingAttribute (D, "range");
1203         goto ErrorExit;
1204     }
1205
1206     /* Remember the line info */
1207     CollAppend (&D->LineInfos, L);
1208
1209     /* Done */
1210     return;
1211
1212 ErrorExit:
1213     /* Entry point in case of errors */
1214     FreeLineInfo (L);
1215 }
1216
1217
1218
1219 static void ParseSegment (InputData* D)
1220 /* Parse a SEGMENT line */
1221 {
1222     /* Skip the SEGMENT token */
1223     NextToken (D);
1224
1225     /* ### */
1226     SkipLine (D);
1227 }
1228
1229
1230
1231 static void ParseSym (InputData* D)
1232 /* Parse a SYM line */
1233 {
1234     /* Skip the SYM token */
1235     NextToken (D);
1236
1237     /* ### */
1238     SkipLine (D);
1239 }
1240
1241
1242
1243 static void ParseVersion (InputData* D)
1244 /* Parse a VERSION line */
1245 {
1246     enum { None = 0x00, Major = 0x01, Minor = 0x02 } InfoBits = None;
1247
1248     /* Skip the VERSION token */
1249     NextToken (D);
1250
1251     /* More stuff follows */
1252     while (D->Tok != TOK_EOL && D->Tok != TOK_EOF) {
1253
1254         switch (D->Tok) {
1255
1256             case TOK_MAJOR:
1257                 NextToken (D);
1258                 if (!ConsumeEqual (D)) {
1259                     goto ErrorExit;
1260                 }
1261                 if (!IntConstFollows (D)) {
1262                     goto ErrorExit;
1263                 }
1264                 D->MajorVersion = D->IVal;
1265                 NextToken (D);
1266                 InfoBits |= Major;
1267                 break;
1268
1269             case TOK_MINOR:
1270                 NextToken (D);
1271                 if (!ConsumeEqual (D)) {
1272                     goto ErrorExit;
1273                 }
1274                 if (!IntConstFollows (D)) {
1275                     goto ErrorExit;
1276                 }
1277                 D->MinorVersion = D->IVal;
1278                 NextToken (D);
1279                 InfoBits |= Minor;
1280                 break;
1281
1282             default:
1283                 UnexpectedToken (D);
1284                 SkipLine (D);
1285                 goto ErrorExit;
1286         }
1287
1288         /* Comma follows before next attribute */
1289         if (D->Tok == TOK_COMMA) {
1290             NextToken (D);
1291         } else if (D->Tok == TOK_EOL || D->Tok == TOK_EOF) {
1292             break;
1293         } else {
1294             UnexpectedToken (D);
1295             goto ErrorExit;
1296         }
1297     }
1298
1299     /* Check for required information */
1300     if ((InfoBits & Major) == None) {
1301         MissingAttribute (D, "major");
1302         goto ErrorExit;
1303     }
1304     if ((InfoBits & Minor) == None) {
1305         MissingAttribute (D, "minor");
1306         goto ErrorExit;
1307     }
1308
1309 ErrorExit:
1310     /* Entry point in case of errors */
1311     return;
1312 }
1313
1314
1315
1316 /*****************************************************************************/
1317 /*                              Data processing                              */
1318 /*****************************************************************************/
1319
1320
1321
1322 static FileInfo* FindFileInfo (InputData* D, const char* FileName)
1323 /* Find the FileInfo for a given file name */
1324 {
1325     /* Get a pointer to the file info collection */
1326     Collection* FileInfos = &D->Info->FileInfos;
1327
1328     /* Do a binary search */
1329     int Lo = 0;
1330     int Hi = (int) CollCount (FileInfos) - 1;
1331     while (Lo <= Hi) {
1332
1333         /* Mid of range */
1334         int Cur = (Lo + Hi) / 2;
1335
1336         /* Get item */
1337         FileInfo* CurItem = CollAt (FileInfos, Cur);
1338
1339         /* Compare */
1340         int Res = strcmp (CurItem->FileName, FileName);
1341
1342         /* Found? */
1343         if (Res < 0) {
1344             Lo = Cur + 1;
1345         } else if (Res > 0) {
1346             Hi = Cur - 1;
1347         } else {
1348             /* Found! */
1349             return CurItem;
1350         }
1351     }
1352
1353     /* Not found */
1354     return 0;
1355 }
1356
1357
1358
1359 static void ProcessFileInfo (InputData* D)
1360 /* Postprocess file infos */
1361 {
1362     /* Get a pointer to the file info collection */
1363     Collection* FileInfos = &D->Info->FileInfos;
1364
1365     /* First, sort the file infos, so we can check for duplicates and do
1366      * binary search.
1367      */
1368     CollSort (FileInfos, CompareFileInfo);
1369
1370     /* Cannot work on an empty collection */
1371     if (CollCount (FileInfos) > 0) {
1372
1373         /* Walk through the file infos and check for duplicates. If we find
1374          * some, warn and remove them, so the file infos are unique after
1375          * that step.
1376          */
1377         FileInfo* F = CollAt (FileInfos, 0);
1378         unsigned I = 1;
1379         while (I < CollCount (FileInfos)) {
1380             FileInfo* Next = CollAt (FileInfos, I);
1381             if (strcmp (F->FileName, Next->FileName) == 0) {
1382                 /* Warn only if time stamp and/or size is different */
1383                 if (F->Size != Next->Size || F->MTime != Next->MTime) {
1384                     ParseError (D,
1385                                 CC65_WARNING,
1386                                 "Duplicate file entry for \"%s\"",
1387                                 F->FileName);
1388                 }
1389                 /* Remove the duplicate entry */
1390                 FreeFileInfo (Next);
1391                 CollDelete (FileInfos, I);
1392             } else {
1393                 /* This one is ok, check the next entry */
1394                 F = Next;
1395                 ++I;
1396             }
1397         }
1398     }
1399 }
1400
1401
1402
1403 static void ProcessLineInfo (InputData* D)
1404 /* Postprocess line infos */
1405 {
1406     /* Get pointers to the collections */
1407     Collection* LineInfos = &D->LineInfos;
1408     Collection* FileInfos = &D->Info->FileInfos;
1409
1410     /* Walk over the line infos and replace the name by a pointer to the
1411      * corresponding file info struct. The LineInfo structs will get shrinked
1412      * in this process. Add the line info to each file where it is defined.
1413      */
1414     unsigned I = 0;
1415     while (I < CollCount (LineInfos)) {
1416
1417         /* Get LineInfo struct */
1418         LineInfo* L = CollAt (LineInfos, I);
1419
1420         /* Find FileInfo that corresponds to name */
1421         FileInfo* F = FindFileInfo (D, L->FileName);
1422
1423         /* If we have no corresponding file info, print a warning and remove
1424          * the line info.
1425          */
1426         if (F == 0) {
1427             ParseError (D,
1428                         CC65_ERROR,
1429                         "No file info for file \"%s\"",
1430                         L->FileName);
1431             FreeLineInfo (L);
1432             CollDelete (LineInfos, I);
1433             continue;
1434         }
1435
1436         /* Shrink the line info struct effectively removing the file name
1437          * but set the pointer to the file info now.
1438          */
1439         L = PreenLineInfo (L, F);
1440         CollReplace (LineInfos, L, I);
1441
1442         /* Add this line info to the file where it is defined */
1443         CollAppend (&F->LineInfos, L);
1444
1445         /* Next one */
1446         ++I;
1447     }
1448
1449     /* Walk over all files and sort the line infos for each file by ascending
1450      * start address of the range, so we can do a binary search later.
1451      */
1452     for (I = 0; I < CollCount (FileInfos); ++I) {
1453
1454         /* Get a pointer to this file info */
1455         FileInfo* F = CollAt (FileInfos, I);
1456
1457         /* Sort the line infos for this file */
1458         CollSort (&F->LineInfos, CompareLineInfo);
1459
1460         /* If there are line info entries, place the first and last address
1461          * of into the FileInfo struct itself, so we can rule out a FileInfo
1462          * quickly when mapping an address to a line info.
1463          */
1464         if (CollCount (&F->LineInfos) > 0) {
1465             F->Start = ((const LineInfo*) CollFirst (&F->LineInfos))->Start;
1466             F->End   = ((const LineInfo*) CollLast (&F->LineInfos))->End;
1467         }
1468
1469     }
1470 }
1471
1472
1473
1474 static LineInfo* FindLineInfo (FileInfo* F, cc65_addr Addr)
1475 /* Find the LineInfo for a given address */
1476 {
1477     Collection* LineInfos;
1478     int         Hi;
1479     int         Lo;
1480
1481
1482     /* Each file info contains the first and last address for which line
1483      * info is available, so we can rule out non matching ones quickly.
1484      */
1485     if (Addr < F->Start || Addr > F->End) {
1486         return 0;
1487     }
1488
1489     /* Get a pointer to the line info collection for this file */
1490     LineInfos = &F->LineInfos;
1491
1492     /* Do a binary search */
1493     Lo = 0;
1494     Hi = (int) CollCount (LineInfos) - 1;
1495     while (Lo <= Hi) {
1496
1497         /* Mid of range */
1498         int Cur = (Lo + Hi) / 2;
1499
1500         /* Get item */
1501         LineInfo* CurItem = CollAt (LineInfos, Cur);
1502
1503         /* Found? */
1504         if (Addr < CurItem->Start) {
1505             Hi = Cur - 1;
1506         } else if (Addr > CurItem->End) {
1507             Lo = Cur + 1;
1508         } else {
1509             /* Found! */
1510             return CurItem;
1511         }
1512     }
1513
1514     /* Not found */
1515     return 0;
1516 }
1517
1518
1519
1520 /*****************************************************************************/
1521 /*                                   Code                                    */
1522 /*****************************************************************************/
1523
1524
1525
1526 cc65_dbginfo cc65_read_dbginfo (const char* filename, cc65_errorfunc errorfunc)
1527 /* Parse the debug info file with the given name. On success, the function
1528  * will return a pointer to an opaque cc65_dbginfo structure, that must be
1529  * passed to the other functions in this module to retrieve information.
1530  * errorfunc is called in case of warnings and errors. If the file cannot be
1531  * read successfully, NULL is returned.
1532  */
1533 {
1534     /* Data structure used to control scanning and parsing */
1535     InputData D = {
1536         filename,               /* Name of input file */
1537         1,                      /* Line number */
1538         0,                      /* Input file */
1539         0,                      /* Line at start of current token */
1540         0,                      /* Column at start of current token */
1541         0,                      /* Number of errors */
1542         0,                      /* Input file */
1543         ' ',                    /* Input character */
1544         TOK_INVALID,            /* Input token */
1545         0,                      /* Integer constant */
1546         STRBUF_INITIALIZER,     /* String constant */
1547         errorfunc,              /* Function called in case of errors */
1548         0,                      /* Major version number */
1549         0,                      /* Minor version number */
1550         COLLECTION_INITIALIZER, /* Line information */
1551         0,                      /* Pointer to debug info */
1552     };
1553
1554     /* Open the input file */
1555     D.F = fopen (D.FileName, "r");
1556     if (D.F == 0) {
1557         /* Cannot open */
1558         ParseError (&D, CC65_ERROR,
1559                     "Cannot open input file \"%s\": %s",
1560                      D.FileName, strerror (errno));
1561         return 0;
1562     }
1563
1564     /* Create a new debug info struct */
1565     D.Info = NewDbgInfo ();
1566
1567     /* Prime the pump */
1568     NextToken (&D);
1569
1570     /* Parse lines */
1571     while (D.Tok != TOK_EOF) {
1572
1573         switch (D.Tok) {
1574
1575             case TOK_FILE:
1576                 ParseFile (&D);
1577                 break;
1578
1579             case TOK_LINE:
1580                 ParseLine (&D);
1581                 break;
1582
1583             case TOK_SEGMENT:
1584                 ParseSegment (&D);
1585                 break;
1586
1587             case TOK_SYM:
1588                 ParseSym (&D);
1589                 break;
1590
1591             case TOK_VERSION:
1592                 ParseVersion (&D);
1593                 break;
1594
1595             default:
1596                 UnexpectedToken (&D);
1597
1598         }
1599
1600         /* EOL or EOF must follow */
1601         if (D.Tok != TOK_EOF) {
1602             if (D.Tok != TOK_EOL) {
1603                 ParseError (&D, 1, "Extra tokens in line");
1604                 SkipLine (&D);
1605             }
1606             NextToken (&D);
1607         }
1608     }
1609
1610     /* Close the file */
1611     fclose (D.F);
1612
1613     /* Free memory allocated for SVal */
1614     SB_Done (&D.SVal);
1615
1616     /* In case of errors, delete the debug info already allocated and
1617      * return NULL
1618      */
1619     if (D.Errors > 0) {
1620         /* Free allocated stuff */
1621         unsigned I;
1622         for (I = 0; I < CollCount (&D.LineInfos); ++I) {
1623             FreeLineInfo (CollAt (&D.LineInfos, I));
1624         }
1625         DoneCollection (&D.LineInfos);
1626         FreeDbgInfo (D.Info);
1627         return 0;
1628     }
1629
1630     /* We do now have all the information from the file. Do postprocessing. */
1631     ProcessFileInfo (&D);
1632     ProcessLineInfo (&D);
1633
1634     /* Free the collection that contained the line info */
1635     DoneCollection (&D.LineInfos);
1636
1637     /* Return the debug info struct that was created */
1638     return D.Info;
1639 }
1640
1641
1642
1643 void cc65_free_dbginfo (cc65_dbginfo Handle)
1644 /* Free debug information read from a file */
1645 {
1646     if (Handle) {
1647         FreeDbgInfo (Handle);
1648     }
1649 }
1650
1651
1652
1653 cc65_lineinfo* cc65_get_lineinfo (cc65_dbginfo Handle, unsigned long Addr)
1654 /* Return line information for the given address. The function returns 0
1655  * if no line information was found.
1656  */
1657 {
1658     unsigned        I;
1659     Collection*     FileInfos;
1660     cc65_lineinfo*  D = 0;
1661
1662     /* We will place a list of line infos in a collection */
1663     Collection LineInfos = COLLECTION_INITIALIZER;
1664
1665     /* Check the parameter */
1666     assert (Handle != 0);
1667
1668     /* Walk over all files and search for matching line infos */
1669     FileInfos = &((DbgInfo*) Handle)->FileInfos;
1670     for (I = 0; I < CollCount (FileInfos); ++I) {
1671         /* Check if the file contains line info for this address */
1672         LineInfo* L = FindLineInfo (CollAt (FileInfos, I), Addr);
1673         if (L != 0) {
1674             CollAppend (&LineInfos, L);
1675         }
1676     }
1677
1678     /* Do we have line infos? */
1679     if (CollCount (&LineInfos) > 0) {
1680
1681         /* Prepare the struct we will return to the caller */
1682         D = xmalloc (sizeof (*D) +
1683                      (CollCount (&LineInfos) - 1) * sizeof (D->data[0]));
1684         D->count = CollCount (&LineInfos);
1685         for (I = 0; I < D->count; ++I) {
1686
1687             /* Pointer to this info */
1688             LineInfo* L = CollAt (&LineInfos, I);
1689
1690             /* Copy data */
1691             D->data[I].name  = L->FileInfo->FileName;
1692             D->data[I].size  = L->FileInfo->Size;
1693             D->data[I].mtime = L->FileInfo->MTime;
1694             D->data[I].line  = L->Line;
1695             D->data[I].start = L->Start;
1696             D->data[I].end   = L->End;
1697         }
1698     }
1699
1700     /* Free the line info collection */
1701     DoneCollection (&LineInfos);
1702
1703     /* Return the struct we've created */
1704     return D;
1705 }
1706
1707
1708
1709 void cc65_free_lineinfo (cc65_dbginfo Handle, cc65_lineinfo* Info)
1710 /* Free line info returned by cc65_get_lineinfo() */
1711 {
1712     /* Just for completeness, check the handle */
1713     assert (Handle != 0);
1714
1715     /* Just free the memory */
1716     xfree (Info);
1717 }
1718
1719
1720