Version bump for last commit; now at v2.0.23.
[rmac] / listing.c
1 //
2 // RMAC - Reboot's Macro Assembler for all Atari computers
3 // LISTING.C - Listing Output
4 // Copyright (C) 199x Landon Dyer, 2011-2020 Reboot and Friends
5 // RMAC derived from MADMAC v1.07 Written by Landon Dyer, 1986
6 // Source utilised with the kind permission of Landon Dyer
7 //
8 // --------------------------------------------------------------------------
9 // 0    0    1    1    2    2    3    3    4    4    5    5    6    6    7    7
10 // 012345678901234567890123456789012345678901234567890123456789012345678901234567
11 // filename....                         Reboot's Macro Assembler N.N.NN (Unknown)
12 // nnnnn  aaaaaaaa  dddddddddddddddddddd T source code
13 // nnnnn  aaaaaaaa  dddddddddddddddd
14 // nnnnn           =vvvvvvvv
15
16 #include "listing.h"
17 #include "error.h"
18 #include "procln.h"
19 #include "sect.h"
20 #include "token.h"
21 #include "version.h"
22
23 char * list_fname;                                      // Listing filename
24 uint8_t subttl[TITLESIZ];                       // Current subtitle
25 int listing;                                            // Listing level
26 int pagelen = 61;                                       // Lines on a page
27 int nlines;                                                     // #lines on page so far
28 LONG lsloc;                                                     // `sloc' at start of line
29
30 // Private
31 static int lcursect;                            // `cursect' at start of line
32 static int llineno;                                     // `curlineno' at start of line
33 static int pageno;                                      // Current page number
34 static int pagewidth;                           // #columns on a page
35 static int subflag;                                     // 0, don't do .eject on subttl (set 1)
36 static char lnimage[IMAGESIZ];          // Image of output line
37 static char title[TITLESIZ];            // Current title
38 static char datestr[20];                        // Current date dd-mon-yyyy
39 static char timestr[20];                        // Current time hh:mm:ss [am|pm]
40 static char buf[IMAGESIZ];                      // Buffer for numbers
41 static long unused;                                     // For supressing 'write' warnings
42
43 static char * month[16] = {
44         "",    "Jan", "Feb", "Mar",
45         "Apr", "May", "Jun", "Jul",
46         "Aug", "Sep", "Oct", "Nov",
47         "Dec", "",    "",    ""
48 };
49
50
51 //
52 // Eject the page, reset the line count and bump the page number
53 //
54 int eject(void)
55 {
56         if (listing > 0)
57         {
58                 println("\f");
59                 nlines = 0;
60         }
61
62         return 0;
63 }
64
65
66 //
67 // Return GEMDOS format date
68 //
69 uint32_t dos_date(void)
70 {
71         uint32_t v;
72         struct tm * tm;
73         time_t tloc;
74
75         time(&tloc);
76         tm = localtime(&tloc);
77         v = ((tm->tm_year - 80) << 9) | ((tm->tm_mon + 1) << 5) | tm->tm_mday;
78
79         return v;
80 }
81
82
83 //
84 // Return GEMDOS format time
85 //
86 uint32_t dos_time(void)
87 {
88         uint32_t v;
89         struct tm * tm;
90         time_t tloc;
91
92         time(&tloc);
93         tm = localtime(&tloc);
94         v = (tm->tm_hour << 11) | (tm->tm_min) << 5 | tm->tm_sec;
95
96         return v;
97 }
98
99
100 //
101 // Generate a time string
102 //
103 void time_string(char * buf, uint32_t time)
104 {
105         int hour;
106         char * ampm;
107
108         hour = (time >> 11);
109
110         if (hour > 12)
111         {
112                 hour -= 12;
113                 ampm = "pm";
114         }
115         else
116                 ampm = "am";
117
118         sprintf(buf, "%d:%02d:%02d %s",
119                 hour, (int)((time >> 5) & 0x3F), (int)((time & 0x1F) << 1), ampm);
120 }
121
122
123 //
124 // Generate a date string
125 //
126 void date_string(char * buf, uint32_t date)
127 {
128         sprintf(buf, "%d-%s-%d",
129                 (int)(date & 0x1F), month[(date >> 5) & 0xF], (int)((date >> 9) + 1980));
130 }
131
132
133 //
134 // Transform letters a-f in the address and data columns of the listing to
135 // uppercase. (People seem  to like uppercase hex better in assembly-language
136 // listings....)
137 //
138 void uc_ln(char * ln)
139 {
140         int i;
141         char j;
142
143         for(i=LOC_COL; i<SRC_COL; ++i)
144         {
145                 if ((j = ln[i]) >= 'a' && j <= 'f')
146                         ln[i] = (char)(j - 0x20);
147         }
148 }
149
150
151 //
152 // Fill region 'dest' with 'len' characters 'c' and null terminate the region
153 //
154 void lnfill(char * dest, int len, char chr)
155 {
156         while (len--)
157                 *dest++ = chr;
158
159         *dest = EOS;
160 }
161
162
163 //
164 // Create listing file with the appropriate name
165 //
166 void list_setup(void)
167 {
168         char fnbuf[FNSIZ];
169
170         strcpy(fnbuf, list_fname);
171
172         if (*fnbuf == EOS)
173         {
174                 strcpy(fnbuf, firstfname);
175                 fext(fnbuf, ".prn", 1);
176         }
177
178         list_fname = NULL;
179
180         if ((list_fd = open(fnbuf, _OPEN_FLAGS, _PERM_MODE)) < 0)
181                 CantCreateFile(fnbuf);
182 }
183
184
185 //
186 // Tag listing with a character, typically for errors or warnings
187 //
188 void taglist(char chr)
189 {
190         lnimage[TAG_COL + 1] = chr;
191 }
192
193
194 //
195 // Print a line to the listing file
196 //
197 void println(const char * ln)
198 {
199         unsigned int length;
200
201         //  Create listing file, if necessary
202         if (list_fname != NULL)
203                 list_setup();
204
205         length = strlen(ln);
206         unused = write(list_fd, ln, length);
207         unused = write(list_fd, "\n", 1L);
208 }
209
210
211 //
212 // Ship line 'ln' out; do page breaks and title stuff
213 //
214 void ship_ln(const char * ln)
215 {
216         // If listing level is <= 0, then don't print anything
217         if (listing <= 0)
218                 return;
219
220         if (list_pag)
221         {
222                 // Notice bottom of page
223                 if (nlines >= pagelen - BOT_MAR)
224                         eject();
225
226                 // Print title, boilerplate, and subtitle at top of page
227                 if (nlines == 0)
228                 {
229                         pageno++;
230                         println("");
231                         date_string(datestr, dos_date());
232                         time_string(timestr, dos_time());
233                         sprintf(buf,
234                                 "%-40s%-20s Page %-4d    %s %s        RMAC %01i.%01i.%02i (%s)",
235                                 title, curfname, pageno, timestr, datestr, MAJOR, MINOR, PATCH,
236                                 PLATFORM);
237                         println(buf);
238                         sprintf(buf, "%s", subttl);
239                         println(buf);
240                         println("");
241                         nlines = 4;
242                 }
243         }
244
245         println(ln);
246         nlines++;
247 }
248
249
250 //
251 // Initialize listing generator
252 //
253 void InitListing(void)
254 {
255         subflag = 0;
256         pageno = 0;
257         nlines = 0;
258         pagelen = 61;
259         pagewidth = 132;
260         strcpy(title, "");
261         strcpy(subttl, "");
262         date_string(datestr, dos_date());
263         time_string(timestr, dos_time());
264 }
265
266
267 //
268 // Listing EOL
269 //
270 void listeol(void)
271 {
272         CHUNK * ch;
273         char * p;
274         int col;
275         LONG count;
276         int fixcount;
277
278         DEBUG printf("~list: lsloc=$%X sloc=$%X\n", lsloc, sloc);
279
280         if (lsloc != sloc)
281         {
282                 sprintf(buf, "%08X", lsloc);
283                 strncpy(lnimage + LOC_COL, buf, 8);
284         }
285
286         if (llineno != curlineno)
287         {
288                 sprintf(buf, "%5d", llineno);
289                 strncpy(lnimage + LN_COL, buf, 5);
290         }
291
292         // List bytes only when section stayed the same and the section is not a
293         // "no-data" (SBSS) section. An extra annoyance is caused by "ds.b" in a
294         // microprocessor mode, which prints out bytes of zero as if they had been
295         // deposited with dcb. The fix (kludge) is an extra variable which records
296         // the fact that a 'ds.x' directive generated all the data, and it
297         // shouldn't be listed
298         SaveSection();          // Update section variables
299
300         if (lcursect == cursect && (sect[lcursect].scattr & SBSS) == 0
301                 && lsloc != sloc && just_bss == 0)
302         {
303                 ch = sect[lcursect].sfcode;
304
305                 if (lcursect != M6502)
306         {
307                 for(; ch!=NULL; ch=ch->chnext)
308                 {
309                         if (lsloc >= ch->chloc && lsloc < (ch->chloc + ch->ch_size))
310                                 break;
311                 }
312         }
313
314                 // Fatal: Can't find chunk holding code
315                 if (ch == NULL)
316                 {
317 nochunk:
318                         interror(6);    // Can't find generated code in section
319                 }
320
321                 p =  ch->chptr + (lsloc - ch->chloc);
322                 col = DATA_COL;
323                 fixcount = 0;
324
325                 for(count=sloc-lsloc; count--; col+=2, ++lsloc)
326                 {
327                         if (col >= DATA_END)
328                         {
329                                 // Ship the line
330                                 col = DATA_COL;
331                                 uc_ln(lnimage);
332                                 ship_ln(lnimage);
333                                 lnfill(lnimage, SRC_COL, SPACE);        // Fill with spaces
334                                 sprintf(buf, "%08X", lsloc);
335                                 strncpy(lnimage + LOC_COL, buf, 8);
336                         }
337
338                         if (lcursect != M6502 &&
339                     lsloc >= (ch->chloc + ch->ch_size))
340                         {
341                                 if ((ch = ch->chnext) == NULL)
342                                         goto nochunk;
343
344                                 p = ch->chptr;
345                         }
346
347                         if (!fixcount)
348                                 fixcount = fixtest(lcursect, lsloc);
349
350                         if (fixcount)
351                         {
352                                 fixcount--;
353                                 strcpy(buf, "xx");
354                                 p++;            // Advance anyway
355                         }
356                         else
357                                 sprintf(buf, "%02x", *p++ & 0xff);
358
359                         strncpy(lnimage + col, buf, 2);
360                 }
361
362                 // Flush partial line
363                 if (col > DATA_COL)
364                 {
365                         uc_ln(lnimage);
366                         ship_ln(lnimage);
367                 }
368         }
369         else
370         {
371                 uc_ln(lnimage);
372                 ship_ln(lnimage);
373         }
374 }
375
376
377 //
378 // Copy current (saved) line to output buffer and tag it with a character
379 //
380 void lstout(char tag)
381 {
382         char * s;
383         char * d;
384
385         lsloc = sloc;
386         lcursect = cursect;
387         llineno = curlineno;
388
389         lnfill(lnimage, SRC_COL, SPACE);        // Fill with spaces
390         lnimage[TAG_COL] = tag;
391
392         // Copy line image and handle control characters
393         d = lnimage + SRC_COL;
394
395         for(s=lnbuf; *s; ++s)
396         {
397                 if (*s >= 0x20 || *s == '\t')
398                         *d++ = *s;
399                 else
400                 {
401                         *d++ = '^';
402                         *d++ = (char)(*s + 0x40);
403                 }
404         }
405
406         *d++ = EOS;
407 }
408
409
410 //
411 // Output a value to listing
412 //
413 int listvalue(uint32_t v)
414 {
415         sprintf(buf, "=%08X", v);
416         strncpy(lnimage + DATA_COL - 1, buf, 9);
417         return 0;
418 }
419
420
421 /*
422  *  .subttl [-] "string"
423  *
424  *  Set subtitle;
425  *    o  leading '-' supresses page eject
426  *    o  don't .eject on first .subttl, but do so on all other ones,
427  *    o  arrange not to print the .subttl directive
428  *
429  */
430 int d_subttl(void)
431 {
432         int ejectok;
433         ejectok = 1;
434
435         if (*tok == '-')
436         {
437                 ejectok = 0;
438                 ++tok;
439         }
440
441         if (*tok != STRING)
442                 return error("missing string");
443
444 //      strcpy(subttl, (char *)tok[1]);
445         strcpy(subttl, string[tok[1]]);
446
447         tok += 2;
448
449         // Always eject on pages 2+
450         if (ejectok && (subflag || pageno > 1))
451                 eject();
452
453         subflag = 1;
454
455         return 0;
456 }
457
458
459 //
460 // Set title on titles not on the first page, do an eject and clobber the
461 // subtitle
462 //
463 int d_title(void)
464 {
465         if (*tok != STRING)
466                 return error("missing string");
467
468 //      strcpy(title, (char*)tok[1]);
469         strcpy(title, string[tok[1]]);
470         tok += 2;
471
472         if (pageno > 1)
473         {
474                 strcpy(subttl, "");
475                 eject();
476         }
477
478         return 0;
479 }
480