2 ###############################################################################
6 # Convert a ca65 source into HTML #
10 # (C) 2000 Ullrich von Bassewitz #
13 # EMail: uz@musoftware.de #
16 # This software is provided 'as-is', without any expressed or implied #
17 # warranty. In no event will the authors be held liable for any damages #
18 # arising from the use of this software. #
20 # Permission is granted to anyone to use this software for any purpose, #
21 # including commercial applications, and to alter it and redistribute it #
22 # freely, subject to the following restrictions: #
24 # 1. The origin of this software must not be misrepresented; you must not #
25 # claim that you wrote the original software. If you use this software #
26 # in a product, an acknowledgment in the product documentation would be #
27 # appreciated but is not required. #
28 # 2. Altered source versions must be plainly marked as such, and must not #
29 # be misrepresented as being the original software. #
30 # 3. This notice may not be removed or altered from any source #
33 ###############################################################################
45 #-----------------------------------------------------------------------------#
47 # ----------------------------------------------------------------------------#
52 my %Files = (); # List of all files.
53 my $FileCount = 0; # Number of input files
54 my %Exports = (); # List of exported symbols.
55 my %Imports = (); # List of imported symbols.
56 my %Labels = (); # List of all labels
57 my $LabelNum = 0; # Counter to generate unique labels
59 # Command line options
60 my $BGColor = "#FFFFFF"; # Background color
61 my $Debug = 0; # No debugging
62 my $Help = 0; # Help flag
63 my $HTMLDir = ""; # Directory in which to create the files
64 my $IndexCols = 6; # Columns in the file listing
65 my $IndexTitle = "Index"; # Title of index page
66 my $IndexName = "index.html"; # Name of index page
67 my $IndexPage = 0; # Create an index page
68 my $LinkStyle = 0; # Default link style
69 my $ReplaceExt = 0; # Replace extension instead of appending
70 my $TextColor = "#000000"; # Text color
71 my $Verbose = 0; # Be quiet
73 # Table used to convert the label number into names
74 my @NameTab = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
75 "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
76 "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6",
81 #-----------------------------------------------------------------------------#
83 # ----------------------------------------------------------------------------#
87 # Terminate with an error
89 print STDERR "ca65html: @_\n";
93 # Print a message if verbose is true
96 print "ca65html: @_\n";
100 # Generate a label and return it
105 my $Num = $LabelNum++;
108 for ($I = 0; $I < 4; $I++) {
109 $L = $NameTab[$Num % 36] . $L;
115 # Make an output file name from an input file name
118 # Input name is parameter
121 # Create the output file name from the input file name
122 if ($ReplaceExt && $InName =~ /^(.+)\.([^\.\/]*)$/) {
125 return "$InName.html";
129 # Remove illegal characters from a string
139 # Strip a path from a filename and return just the name
142 # Filename is argument
143 my $FileName = $_[0];
145 # Remove a path name if we have one
146 $FileName =~ /^(.*?)([^\/]*)$/;
152 #-----------------------------------------------------------------------------#
153 # Document header and footer #
154 # ----------------------------------------------------------------------------#
158 # Print the document header
160 my $OUT = shift (@_);
161 my $Asm = shift (@_);
163 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
166 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
167 <meta name="GENERATOR" content="ca65html">
170 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
172 <center><h1>$Asm</h1></center>
178 # Print the document footer
180 my $OUT = shift (@_);
181 my $Name = shift (@_);
183 # Get the current date and time
184 my $Today = localtime;
191 <a href="http://validator.w3.org/check/referer"><img border=0 src="http://validator.w3.org/images/vh40" alt="Valid HTML 4.0!" height=31 width=88 align=right></a>
192 $Name; generated on $Today by ca65html<br>
193 <a href=\"mailto:uz\@cc65.org\">uz\@cc65.org</a>
202 #-----------------------------------------------------------------------------#
203 # File list management #
204 # ----------------------------------------------------------------------------#
210 # Argument is file to add
211 my $FileName = $_[0];
213 # Get just the name (remove a path if there is one)
214 my $Name = StripPath ($FileName);
216 # Check if we have the file already
217 if (exists ($Files{$Name})) {
218 Gabble ("File \"$FileName\" already known");
222 # Check with the full pathname. If we don't find it, search in the current
224 if (-f $FileName && -r $FileName) {
225 $Files{$Name} = $FileName;
227 } elsif (-f $Name && -r $Name) {
228 $Files{$Name} = $Name;
231 Abort ("$FileName not found or not readable");
237 #-----------------------------------------------------------------------------#
238 # Referencing and defining labels #
239 # ----------------------------------------------------------------------------#
243 # Get a label reference
246 # Arguments are: Filename, identifier, item that should be tagged
247 my $FileName = $_[0];
251 # Search for the identifier in the list of labels
252 if (exists ($Labels{$FileName}{$Id})) {
253 # It is a label (in this file)
254 return sprintf ("<a href=\"#%s\">%s</a>", $Labels{$FileName}{$Id}, $Item);
255 } elsif (exists ($Imports{$FileName}{$Id})) {
256 # It is an import. If LinkStyle is 1, or if the file exporting the
257 # identifier is not visible, we link to the .import statement in the
258 # current file. Otherwise we link directly to the referenced symbol
259 # in the file that exports it.
260 if ($LinkStyle == 1 or not exists ($Exports{$Id})) {
261 return sprintf ("<a href=\"#%s\">%s</a>", $Imports{$FileName}{$Id}, $Item);
263 # Get the filename from the export
265 ($FileName, $Label) = split (/#/, $Exports{$Id});
266 if (not defined ($Labels{$FileName}{$Id})) {
267 # This may currently happen because we don't see .include
268 # statements, so we may have an export but no definition.
269 # Link to the .export statement instead
270 $Label = $Exports{$Id};
272 # Link to the definition in the file
273 $Label = sprintf ("%s#%s", $FileName, $Labels{$FileName}{$Id});
275 return sprintf ("<a href=\"%s\">%s</a>", $Label, $Item);
278 # The symbol is unknown, return as is
285 #-----------------------------------------------------------------------------#
287 # ----------------------------------------------------------------------------#
291 # Process1: Read one file for the first time.
298 # Filename is parameter
299 my $InName = shift(@_);
301 # Create the output file name from the input file name
302 my $OutName = GetOutName ($InName);
304 # Current cheap local label prefix is empty
305 my $CheapPrefix = "";
307 # Open a the input file
308 my $FileName = $Files{$InName}; # Includes path if needed
309 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
311 # Keep the user happy
312 Gabble ("$FileName => $OutName");
314 # Read and process all lines from the file
315 while ($Line = <INPUT>) {
321 if ($Line =~ /^\s*(\@?)([_a-zA-Z][_\w]*)\s*(:|=)/) {
323 # Is this a local label?
326 $Id = "$CheapPrefix$1$2";
330 # Remember the id as new cheap local prefix
335 $Labels{$OutName}{$Id} = GenLabel();
337 # Check for an import statement
338 } elsif ($Line =~ /^\s*(\.import|\.importzp)\s+(.*?)(\s*)(;.*$|$)/) {
340 # Split into a list of identifiers
341 my @Ids = split (/\s*,\s*/, $2);
343 $Imports{$OutName}{$Id} = GenLabel();
346 # Check for an export statement
347 } elsif ($Line =~ /^\s*(\.export|\.exportzp)\s+(.*?)(\s*)(;.*$|$)/) {
349 # Split into a list of identifiers
350 my @Ids = split (/\s*,\s*/, $2);
352 $Exports{$Id} = sprintf ("%s#%s", $OutName, GenLabel());
355 # Check for a .proc statement
356 } elsif ($Line =~ /^\s*\.proc\s+([_a-zA-Z][_\w]*)?.*$/) {
361 $Labels{$OutName}{$Id} = GenLabel();
367 # Close the input file
373 # Pass1: Read all files for the first time.
376 # Keep the user happy
379 # Walk over the files
380 for my $InName (keys (%Files)) {
388 #-----------------------------------------------------------------------------#
390 # ----------------------------------------------------------------------------#
394 # Process2: Read one file the second time.
407 # Input file is parameter
408 my $InName = shift(@_);
410 # Create the output file name from the input file name
411 my $OutName = GetOutName ($InName);
413 # Current cheap local label prefix is empty
414 my $CheapPrefix = "";
416 # Open a the input file
417 my $FileName = $Files{$InName}; # Includes path if needed
418 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
420 # Open the output file and print the HTML header
421 open (OUTPUT, ">$HTMLDir$OutName") or Abort ("Cannot open $OutName: $!");
422 DocHeader (OUTPUT, $InName);
424 # Keep the user happy
425 Gabble ("$FileName => $OutName");
427 # The instructions that will have hyperlinks if a label is used
428 my $Ins = "adc|add|and|bcc|bcs|beq|bit|bmi|bne|bpl|bcv|bra|bvs|".
429 "cmp|cpx|cpy|dec|eor|inc|jmp|jsr|lda|ldx|ldy|ora|rol|".
430 "sbc|sta|stx|sty|sub|";
432 # Read the input file, replacing references by hyperlinks and mark
433 # labels as link targets.
434 while ($Line = <INPUT>) {
439 # Clear the output line
442 # Check for a label. If we have one, process it and remove it
444 if ($Line =~ /^\s*?(\@?)([_a-zA-Z][_\w]*)(\s*)(:|=)(.*)$/) {
446 # Is this a local label?
449 $Id = "$CheapPrefix$1$2";
453 # Remember the id as new cheap local prefix
457 # Get the label for the id
458 $Label = $Labels{$OutName}{$Id};
460 # Print the label with a tag
461 $OutLine .= sprintf ("<a name=\"%s\">%s%s</a>%s%s", $Label, $1, $2, $3, $4);
463 # Use the remainder for line
467 # Print any leading whitespace and remove it, so we don't have to
468 # care about whitespace below.
469 if ($Line =~ /^(\s+)(.*)$/) {
474 # Handle the import statements
475 if ($Line =~ /^(\.import|\.importzp)(\s+)(.*)$/) {
477 # Print any fixed stuff from the line and remove it
481 # Print all identifiers if there are any
482 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
484 # Identifier is $1, remainder is $2
488 # Variable to assemble HTML representation
491 # If we have an export for this import, add a link to this
493 if (exists ($Exports{$Id})) {
494 $Label = $Exports{$Id};
495 $Item = sprintf ("<a href=\"%s\">%s</a>", $Label, $Item);
498 # Make this import a link target
499 if (exists ($Imports{$OutName}{$Id})) {
500 $Label = $Imports{$OutName}{$1};
501 $Item = sprintf ("<a name=\"%s\">%s</a>", $Label, $Item);
504 # Add the HTML stuff to the output line
507 # Check if another identifier follows
508 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
516 # Add an remainder if there is one
517 $OutLine .= Cleanup ($Line);
519 # Handle export statements
520 } elsif ($Line =~ /^(\.export|\.exportzp)(\s+)(.*)$/) {
522 # Print the command the and white space
526 # Print all identifiers if there are any
527 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
529 # Identifier is $1, remainder is $2
533 # Variable to assemble HTML representation
536 # If we have a definition for this export in this file, add
537 # a link to the definition.
538 if (exists ($Labels{$OutName}{$1})) {
539 $Label = $Labels{$OutName}{$1};
540 $Item = sprintf ("<a href=\"#%s\">%s</a>", $Label, $Item);
543 # If we have this identifier in the list of exports, add a
544 # jump target for the export.
545 if (exists ($Exports{$Id})) {
546 $Label = $Exports{$Id};
547 # Be sure to use only the label part
548 $Label =~ s/^(.*#)(.*)$/$2/;
549 $Item = sprintf ("<a name=\"%s\">%s</a>", $Label, $Item);
552 # Add the HTML stuff to the output line
555 # Check if another identifier follows
556 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
564 # Add an remainder if there is one
565 $OutLine .= Cleanup ($Line);
567 # Check for .addr and .word
568 } elsif ($Line =~ /^(\.addr|\.word)(\s+)(.*)$/) {
570 # Print the command the and white space
574 # Print all identifiers if there are any
575 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
576 if (exists ($Labels{$OutName}{$1})) {
577 $Label = $Labels{$OutName}{$1};
578 $OutLine .= sprintf ("<a href=\"#%s\">%s</a>", $Label, $1);
583 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
591 # Add an remainder if there is one
592 $OutLine .= Cleanup ($Line);
595 } elsif ($Line =~ /^(\.proc\s+)([_a-zA-Z][_\w]*)?(.*)$/) {
597 # Do we have an identifier?
599 # Get the label for the id
600 $Label = $Labels{$OutName}{$2};
602 # Print the label with a tag
603 $OutLine .= sprintf ("%s<a name=\"%s\">%s</a>", $1, $Label, $2);
605 # Use the remainder for line
609 # Cleanup the remainder and add it
610 $OutLine .= Cleanup ($Line);
613 } elsif ($Line =~ /^(\.include)(\s+)\"((?:[^\"]+?|\\\")+)\"(\s*)(;.*$|$)/) {
615 # Add the fixed stuff to the output line
616 $OutLine .= "$1$2\"";
618 # Get the filename into a named variable
621 # Remember the remainder
624 # Get the name without a path
625 my $Name = StripPath ($FileName);
627 # We don't need FileName any longer as is, so clean it up
628 $FileName = Cleanup ($FileName);
630 # If the include file is among the list of our files, add a link,
631 # otherwise just add the name as is.
632 if (exists ($Files{$Name})) {
633 $OutLine .= sprintf ("<a href=\"%s\">%s</a>", GetOutName ($Name), $FileName);
635 $OutLine .= $FileName;
639 $OutLine .= Cleanup ($Line);
641 # Check for any legal instruction
642 } elsif ($Line =~ /^($Ins)(\s+)(.*?)(\s*)(;.*$|$)/) {
644 # Print the instruction and white space
647 # Remember the remaining parts
649 $Comment = Cleanup ("$4$5");
651 # Check for the first identifier in the operand and replace it
653 if ($Operand =~ /^([^_a-zA-Z]*?)(\@?)([_a-zA-Z][_\w]*)(.*)$/) {
655 # Is this a local label?
658 $Id = "$CheapPrefix$2$3";
664 # Get the reference to this label if we find it
665 $Operand = Cleanup($1) . RefLabel($OutName, $Id, $2 . $3) . Cleanup($4);
668 # Reassemble and print the line
669 $OutLine .= "$Operand$Comment";
673 # Nothing known - print the line
674 $OutLine .= Cleanup ($Line);
679 print OUTPUT "$OutLine\n";
682 # Print the HTML footer
683 DocFooter (OUTPUT, $OutName);
692 # Pass2: Read all files the second time.
695 # Keep the user happy
698 # Walk over the files
699 for my $InName (keys (%Files)) {
707 #-----------------------------------------------------------------------------#
708 # Create an index page #
709 # ----------------------------------------------------------------------------#
713 # Print a list of all files
719 # Print the file list in a table
720 print $INDEX "<h2>Files</h2><p>\n";
721 print $INDEX "<table border=\"0\" width=\"100%\">\n";
723 for my $File (sort (keys (%Files))) {
726 if (($Count % $IndexCols) == 0) {
727 print $INDEX "<tr>\n";
729 printf $INDEX "<td><a href=\"%s\">%s</a></td>\n", GetOutName ($File), $File;
730 if (($Count % $IndexCols) == $IndexCols-1) {
731 print $INDEX "</tr>\n";
735 if (($Count % $IndexCols) != 0) {
736 print $INDEX "</tr>\n";
738 print $INDEX "</table><p><br><p>\n";
743 # Print a list of all exports
749 # Print the file list in a table
750 print $INDEX "<h2>Exports</h2><p>\n";
751 print $INDEX "<table border=\"0\" width=\"100%\">\n";
753 for my $Export (sort (keys (%Exports))) {
758 ($File, $Label) = split (/#/, $Exports{$Export});
760 # The label is the label of the export statement. If we can find the
761 # actual label, use this instead.
762 if (exists ($Labels{$File}{$Export})) {
763 $Label = $Labels{$File}{$Export};
767 if (($Count % $IndexCols) == 0) {
768 print $INDEX "<tr>\n";
770 printf $INDEX "<td><a href=\"%s#%s\">%s</a></td>\n", $File, $Label, $Export;
771 if (($Count % $IndexCols) == $IndexCols-1) {
772 print $INDEX "</tr>\n";
776 if (($Count % $IndexCols) != 0) {
777 print $INDEX "</tr>\n";
779 print $INDEX "</table><p><br><p>\n";
786 # Open the index page file
787 open (INDEX, ">$HTMLDir$IndexName") or Abort ("Cannot open $IndexName: $!");
791 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
794 <meta http-equiv="Content-Type" content=\"text/html; charset=iso-8859-1">
795 <meta name="GENERATOR" content="ca65html">
796 <title>$IndexTitle</title>
798 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
800 <center><h1>$IndexTitle</h1></center>
804 # Print the file list in a table
808 # Print the document footer
809 DocFooter (INDEX, $IndexName);
811 # Close the index file
817 #-----------------------------------------------------------------------------#
818 # Print usage information #
819 # ----------------------------------------------------------------------------#
824 print "Usage: ca65html [options] file ...\n";
826 print " --bgcolor color Use background color c instead of $BGColor\n";
827 print " --help This text\n";
828 print " --htmldir dir Specify directory for HTML files\n";
829 print " --indexcols n Use n columns on index page (default $IndexCols)\n";
830 print " --indexname file Use file for the index file instead of $IndexName\n";
831 print " --indexpage Create an index page\n";
832 print " --indextitle title Use title as the index title instead of $IndexTitle\n";
833 print " --linkstyle style Use the given link style\n";
834 print " --replaceext Replace source extension instead of appending .html\n";
835 print " --textcolor color Use text color c instead of $TextColor\n";
836 print " --verbose Be more verbose\n";
841 #-----------------------------------------------------------------------------#
843 # ----------------------------------------------------------------------------#
847 # Get program options
848 GetOptions ("bgcolor=s" => \$BGColor,
851 "htmldir=s" => \$HTMLDir,
852 "indexcols=i" => \$IndexCols,
853 "indexname=s" => \$IndexName,
854 "indexpage" => \$IndexPage,
855 "indextitle=s" => \$IndexTitle,
856 "linkstyle=i" => \$LinkStyle,
857 "replaceext" => \$ReplaceExt,
858 "textcolor=s" => \$TextColor,
859 "verbose!" => \$Verbose,
862 # Check some arguments
863 if ($IndexCols <= 0 || $IndexCols >= 20) {
864 Abort ("Invalid value for --indexcols option");
866 if ($HTMLDir ne "" && $HTMLDir =~ /[^\/]$/) {
867 # Add a trailing path separator
873 # Print help if requested
878 # Check if we have input files given
879 if ($FileCount == 0) {
880 Abort ("No input files");
883 # Convert the documents
887 # Generate an index page if requested