mirror of
https://github.com/cc65/cc65.git
synced 2025-01-12 17:30:50 +00:00
a6b04f6e97
Quotations that are embraced by tick marks now look better, in most fonts.
455 lines
13 KiB
C
455 lines
13 KiB
C
/*****************************************************************************/
|
|
/* */
|
|
/* listing.c */
|
|
/* */
|
|
/* Listing support for the ca65 crossassembler */
|
|
/* */
|
|
/* */
|
|
/* */
|
|
/* (C) 2000-2011, Ullrich von Bassewitz */
|
|
/* Roemerstrasse 52 */
|
|
/* D-70794 Filderstadt */
|
|
/* EMail: uz@cc65.org */
|
|
/* */
|
|
/* */
|
|
/* This software is provided 'as-is', without any expressed or implied */
|
|
/* warranty. In no event will the authors be held liable for any damages */
|
|
/* arising from the use of this software. */
|
|
/* */
|
|
/* Permission is granted to anyone to use this software for any purpose, */
|
|
/* including commercial applications, and to alter it and redistribute it */
|
|
/* freely, subject to the following restrictions: */
|
|
/* */
|
|
/* 1. The origin of this software must not be misrepresented; you must not */
|
|
/* claim that you wrote the original software. If you use this software */
|
|
/* in a product, an acknowledgment in the product documentation would be */
|
|
/* appreciated but is not required. */
|
|
/* 2. Altered source versions must be plainly marked as such, and must not */
|
|
/* be misrepresented as being the original software. */
|
|
/* 3. This notice may not be removed or altered from any source */
|
|
/* distribution. */
|
|
/* */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
/* common */
|
|
#include "check.h"
|
|
#include "fname.h"
|
|
#include "fragdefs.h"
|
|
#include "strbuf.h"
|
|
#include "version.h"
|
|
#include "xmalloc.h"
|
|
|
|
/* ca65 */
|
|
#include "error.h"
|
|
#include "filetab.h"
|
|
#include "global.h"
|
|
#include "listing.h"
|
|
#include "segment.h"
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Data */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
/* Single linked list of lines */
|
|
ListLine* LineList = 0; /* List of listing lines */
|
|
ListLine* LineCur = 0; /* Current listing line */
|
|
ListLine* LineLast = 0; /* Last (current) listing line */
|
|
|
|
/* Page and other formatting */
|
|
int PageLength = -1; /* Length of a listing page */
|
|
static unsigned PageNumber = 1; /* Current listing page number */
|
|
static int PageLines = 0; /* Current line on page */
|
|
static unsigned ListBytes = 12; /* Number of bytes to list for one line */
|
|
|
|
/* Switch the listing on/off */
|
|
static int ListingEnabled = 1; /* Enabled if > 0 */
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Code */
|
|
/*****************************************************************************/
|
|
|
|
|
|
|
|
void NewListingLine (const StrBuf* Line, unsigned char File, unsigned char Depth)
|
|
/* Create a new ListLine struct and insert it */
|
|
{
|
|
/* Store only if listing is enabled */
|
|
if (SB_GetLen (&ListingName) > 0) {
|
|
|
|
ListLine* L;
|
|
|
|
/* Get the length of the line */
|
|
unsigned Len = SB_GetLen (Line);
|
|
|
|
/* Ignore trailing newlines */
|
|
while (Len > 0 && SB_AtUnchecked (Line, Len-1) == '\n') {
|
|
--Len;
|
|
}
|
|
|
|
/* Allocate memory */
|
|
L = xmalloc (sizeof (ListLine) + Len);
|
|
|
|
/* Initialize the fields. */
|
|
L->Next = 0;
|
|
L->FragList = 0;
|
|
L->FragLast = 0;
|
|
L->PC = GetPC ();
|
|
L->Reloc = GetRelocMode ();
|
|
L->File = File;
|
|
L->Depth = Depth;
|
|
L->Output = (ListingEnabled > 0);
|
|
L->ListBytes = (unsigned char) ListBytes;
|
|
memcpy (L->Line, SB_GetConstBuf (Line), Len);
|
|
L->Line[Len] = '\0';
|
|
|
|
/* Insert the line into the list of lines */
|
|
if (LineList == 0) {
|
|
LineList = L;
|
|
} else {
|
|
LineLast->Next = L;
|
|
}
|
|
LineLast = L;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void EnableListing (void)
|
|
/* Enable output of lines to the listing */
|
|
{
|
|
if (SB_GetLen (&ListingName) > 0) {
|
|
/* If we're about to enable the listing, do this for the current line
|
|
** also, so we will see the source line that did this.
|
|
*/
|
|
if (ListingEnabled++ == 0) {
|
|
LineCur->Output = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void DisableListing (void)
|
|
/* Disable output of lines to the listing */
|
|
{
|
|
if (SB_GetLen (&ListingName) > 0) {
|
|
if (ListingEnabled == 0) {
|
|
/* Cannot switch the listing off once more */
|
|
Error ("Counter underflow");
|
|
} else {
|
|
--ListingEnabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void SetListBytes (int Bytes)
|
|
/* Set the maximum number of bytes listed for one line */
|
|
{
|
|
if (Bytes < 0) {
|
|
Bytes = 0; /* Encode "unlimited" as zero */
|
|
}
|
|
ListBytes = Bytes;
|
|
}
|
|
|
|
|
|
|
|
void InitListingLine (void)
|
|
/* Initialize the current listing line */
|
|
{
|
|
if (SB_GetLen (&ListingName) > 0) {
|
|
/* Make the last loaded line the current line */
|
|
/* ###### This code is a hack! We really need to do it right --
|
|
** as soon as we know how. :-(
|
|
*/
|
|
if (LineCur && LineCur->Next && LineCur->Next != LineLast) {
|
|
ListLine* L = LineCur;
|
|
do {
|
|
L = L->Next;
|
|
/* Set the values for this line */
|
|
CHECK (L != 0);
|
|
L->PC = GetPC ();
|
|
L->Reloc = GetRelocMode ();
|
|
L->Output = (ListingEnabled > 0);
|
|
L->ListBytes = (unsigned char) ListBytes;
|
|
} while (L->Next != LineLast);
|
|
}
|
|
LineCur = LineLast;
|
|
|
|
/* Set the values for this line */
|
|
CHECK (LineCur != 0);
|
|
LineCur->PC = GetPC ();
|
|
LineCur->Reloc = GetRelocMode ();
|
|
LineCur->Output = (ListingEnabled > 0);
|
|
LineCur->ListBytes = (unsigned char) ListBytes;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static char* AddHex (char* S, unsigned Val)
|
|
/* Add a hex byte in ASCII to the given string and return the new pointer */
|
|
{
|
|
static const char HexTab [16] = {
|
|
'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
|
|
};
|
|
|
|
*S++ = HexTab [(Val >> 4) & 0x0F];
|
|
*S++ = HexTab [Val & 0x0F];
|
|
|
|
return S;
|
|
}
|
|
|
|
|
|
|
|
static void PrintPageHeader (FILE* F, const ListLine* L)
|
|
/* Print the header for a new page. It is assumed that the given line is the
|
|
** last line of the previous page.
|
|
*/
|
|
{
|
|
/* Gte a pointer to the current input file */
|
|
const StrBuf* CurFile = GetFileName (L->File);
|
|
|
|
/* Print the header on the new page */
|
|
fprintf (F,
|
|
"ca65 V%s\n"
|
|
"Main file : %s\n"
|
|
"Current file: %.*s\n"
|
|
"\n",
|
|
GetVersionAsString (),
|
|
InFile,
|
|
(int) SB_GetLen (CurFile), SB_GetConstBuf (CurFile));
|
|
|
|
/* Count pages, reset lines */
|
|
++PageNumber;
|
|
PageLines = 4;
|
|
}
|
|
|
|
|
|
|
|
static void PrintLine (FILE* F, const char* Header, const char* Line, const ListLine* L)
|
|
/* Print one line to the listing file, adding a newline and counting lines */
|
|
{
|
|
/* Print the given line */
|
|
fprintf (F, "%s%s\n", Header, Line);
|
|
|
|
/* Increment the current line */
|
|
++PageLines;
|
|
|
|
/* Switch to a new page if needed. Do not switch, if the current line is
|
|
** the last one, to avoid pages that consist of just the header.
|
|
*/
|
|
if (PageLength > 0 && PageLines >= PageLength && L->Next != 0) {
|
|
/* Do a formfeed */
|
|
putc ('\f', F);
|
|
/* Print the header on the new page */
|
|
PrintPageHeader (F, L);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static char* AddMult (char* S, char C, unsigned Count)
|
|
/* Add multiple instances of character C to S, return updated S. */
|
|
{
|
|
memset (S, C, Count);
|
|
return S + Count;
|
|
}
|
|
|
|
|
|
|
|
static char* MakeLineHeader (char* H, const ListLine* L)
|
|
/* Prepare the line header */
|
|
{
|
|
char Mode;
|
|
char Depth;
|
|
|
|
/* Setup the PC mode */
|
|
Mode = (L->Reloc)? 'r' : ' ';
|
|
|
|
/* Set up the include depth */
|
|
Depth = (L->Depth < 10)? L->Depth + '0' : '+';
|
|
|
|
/* Format the line */
|
|
sprintf (H, "%06lX%c %c", L->PC, Mode, Depth);
|
|
memset (H+9, ' ', LINE_HEADER_LEN-9);
|
|
|
|
/* Return the buffer */
|
|
return H;
|
|
}
|
|
|
|
|
|
|
|
void CreateListing (void)
|
|
/* Create the listing */
|
|
{
|
|
FILE* F;
|
|
Fragment* Frag;
|
|
ListLine* L;
|
|
char HeaderBuf [LINE_HEADER_LEN+1];
|
|
|
|
/* Open the real listing file */
|
|
F = fopen (SB_GetConstBuf (&ListingName), "w");
|
|
if (F == 0) {
|
|
Fatal ("Cannot open listing file '%s': %s",
|
|
SB_GetConstBuf (&ListingName),
|
|
strerror (errno));
|
|
}
|
|
|
|
/* Reset variables, print the header for the first page */
|
|
PageNumber = 0;
|
|
PrintPageHeader (F, LineList);
|
|
|
|
/* Terminate the header buffer. The last byte will never get overwritten */
|
|
HeaderBuf [LINE_HEADER_LEN] = '\0';
|
|
|
|
/* Walk through all listing lines */
|
|
L = LineList;
|
|
while (L) {
|
|
|
|
char* Buf;
|
|
char* B;
|
|
unsigned Count;
|
|
unsigned I;
|
|
char* Line;
|
|
|
|
/* If we should not output this line, go to the next */
|
|
if (L->Output == 0) {
|
|
L = L->Next;
|
|
continue;
|
|
}
|
|
|
|
/* If we don't have a fragment list for this line, things are easy */
|
|
if (L->FragList == 0) {
|
|
PrintLine (F, MakeLineHeader (HeaderBuf, L), L->Line, L);
|
|
L = L->Next;
|
|
continue;
|
|
}
|
|
|
|
/* Count the number of bytes in the complete fragment list */
|
|
Count = 0;
|
|
Frag = L->FragList;
|
|
while (Frag) {
|
|
Count += Frag->Len;
|
|
Frag = Frag->LineList;
|
|
}
|
|
|
|
/* Allocate memory for the given number of bytes */
|
|
Buf = xmalloc (Count*2+1);
|
|
|
|
/* Copy an ASCII representation of the bytes into the buffer */
|
|
B = Buf;
|
|
Frag = L->FragList;
|
|
while (Frag) {
|
|
|
|
/* Write data depending on the type */
|
|
switch (Frag->Type) {
|
|
|
|
case FRAG_LITERAL:
|
|
for (I = 0; I < Frag->Len; ++I) {
|
|
B = AddHex (B, Frag->V.Data[I]);
|
|
}
|
|
break;
|
|
|
|
case FRAG_EXPR:
|
|
case FRAG_SEXPR:
|
|
B = AddMult (B, 'r', Frag->Len*2);
|
|
break;
|
|
|
|
case FRAG_FILL:
|
|
B = AddMult (B, 'x', Frag->Len*2);
|
|
break;
|
|
|
|
default:
|
|
Internal ("Invalid fragment type: %u", Frag->Type);
|
|
|
|
}
|
|
|
|
/* Next fragment */
|
|
Frag = Frag->LineList;
|
|
|
|
}
|
|
|
|
/* Limit the number of bytes actually printed */
|
|
if (L->ListBytes != 0) {
|
|
/* Not unlimited */
|
|
if (Count > L->ListBytes) {
|
|
Count = L->ListBytes;
|
|
}
|
|
}
|
|
|
|
/* Output the data. The format of a listing line is:
|
|
**
|
|
** PPPPPPm I 11 22 33 44
|
|
**
|
|
** where
|
|
**
|
|
** PPPPPP is the PC
|
|
** m is the mode ('r' or empty)
|
|
** I is the include level
|
|
** 11 .. are code or data bytes
|
|
*/
|
|
Line = L->Line;
|
|
B = Buf;
|
|
while (Count) {
|
|
|
|
unsigned Chunk;
|
|
char* P;
|
|
|
|
/* Prepare the line header */
|
|
MakeLineHeader (HeaderBuf, L);
|
|
|
|
/* Get the number of bytes for the next line */
|
|
Chunk = Count;
|
|
if (Chunk > 4) {
|
|
Chunk = 4;
|
|
}
|
|
Count -= Chunk;
|
|
|
|
/* Increment the program counter. Since we don't need the PC stored
|
|
** in the LineList object for anything else, just increment this
|
|
** variable.
|
|
*/
|
|
L->PC += Chunk;
|
|
|
|
/* Copy the bytes into the line */
|
|
P = HeaderBuf + 11;
|
|
for (I = 0; I < Chunk; ++I) {
|
|
*P++ = *B++;
|
|
*P++ = *B++;
|
|
*P++ = ' ';
|
|
}
|
|
|
|
/* Output this line */
|
|
PrintLine (F, HeaderBuf, Line, L);
|
|
|
|
/* Don't output a line twice */
|
|
Line = "";
|
|
|
|
}
|
|
|
|
/* Delete the temporary buffer */
|
|
xfree (Buf);
|
|
|
|
/* Next line */
|
|
L = L->Next;
|
|
|
|
}
|
|
|
|
/* Close the listing file */
|
|
(void) fclose (F);
|
|
}
|