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 $IndexCols = 6; # Columns in the file listing
64 my $IndexTitle = "Index"; # Title of index page
65 my $IndexName = "index.html"; # Name of index page
66 my $IndexPage = 0; # Create an index page
67 my $LinkStyle = 0; # Default link style
68 my $ReplaceExt = 0; # Replace extension instead of appending
69 my $TextColor = "#000000"; # Text color
70 my $Verbose = 0; # Be quiet
74 #-----------------------------------------------------------------------------#
76 # ----------------------------------------------------------------------------#
80 # Terminate with an error
82 print STDERR "ca65html: @_\n";
86 # Print a message if verbose is true
89 print "ca65html: @_\n";
93 # Generate a label and return it
96 return sprintf ("L%06X", $LabelNum++);
99 # Make an output file name from an input file name
102 # Input name is parameter
105 # Create the output file name from the input file name
106 if ($ReplaceExt && $InName =~ /^(.+)\.([^\.\/]*)$/) {
109 return "$InName.html";
113 # Remove illegal characters from a string
125 #-----------------------------------------------------------------------------#
126 # Document header and footer #
127 # ----------------------------------------------------------------------------#
131 # Print the document header
133 my $OUT = shift (@_);
134 my $Asm = shift (@_);
136 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
139 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
140 <meta name="GENERATOR" content="ca65html">
143 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
145 <center><h1>$Asm</h1></center>
151 # Print the document footer
153 my $OUT = shift (@_);
154 my $Name = shift (@_);
156 # Get the current date and time
157 my $Today = localtime;
164 <a href=\"http://validator.w3.org/check/referer\"><img border=0
165 src=\"http://validator.w3.org/images/vh40\"
166 alt=\"Valid HTML 4.0!\" height=31 width=88 align=right></a>
167 $Name; generated on $Today by ca65html<br>
168 <a href=\"mailto:uz\@cc65.org\">uz\@cc65.org</a>
177 #-----------------------------------------------------------------------------#
178 # File list management #
179 # ----------------------------------------------------------------------------#
185 # Argument is file to add
186 my $FileName = $_[0];
188 # Remove a path name if we have one
189 $FileName =~ /^(.*?)([^\/]*)$/;
193 # Check if we have the file already
194 if (exists ($Files{$Name})) {
195 Gabble ("File \"$FileName\" already known");
199 # Check with the full pathname. If we don't find it, search in the current
201 if (-f $FileName && -r $FileName) {
202 $Files{$Name} = $FileName;
204 } elsif (-f $Name && -r $Name) {
205 $Files{$Name} = $Name;
208 Abort ("$FileName not found or not readable");
214 #-----------------------------------------------------------------------------#
215 # Referencing and defining labels #
216 # ----------------------------------------------------------------------------#
220 # Get a label reference
223 # Arguments are: Filename, identifier, item that should be tagged
224 my $FileName = $_[0];
228 # Search for the identifier in the list of labels
229 if (exists ($Labels{$FileName}{$Id})) {
230 # It is a label (in this file)
231 return sprintf ("<a href=\"#%s\">%s</a>", $Labels{$FileName}{$Id}, $Item);
232 } elsif (exists ($Imports{$FileName}{$Id})) {
233 # It is an import. If LinkStyle is 1, or if the file exporting the
234 # identifier is not visible, we link to the .import statement in the
235 # current file. Otherwise we link directly to the referenced symbol
236 # in the file that exports it.
237 if ($LinkStyle == 1 or not exists ($Exports{$Id})) {
238 return sprintf ("<a href=\"#%s\">%s</a>", $Imports{$FileName}{$Id}, $Item);
240 # Get the filename from the export
242 ($FileName, $Label) = split (/#/, $Exports{$Id});
243 if (not defined ($Labels{$FileName}{$Id})) {
244 # This may currently happen because we don't see .include
245 # statements, so we may have an export but no definition.
246 # Link to the .export statement instead
247 $Label = $Exports{$Id};
249 # Link to the definition in the file
250 $Label = sprintf ("%s#%s", $FileName, $Labels{$FileName}{$Id});
252 return sprintf ("<a href=\"%s\">%s</a>", $Label, $Item);
255 # The symbol is unknown, return as is
262 #-----------------------------------------------------------------------------#
264 # ----------------------------------------------------------------------------#
268 # Process1: Read one file for the first time.
275 # Filename is parameter
276 my $InName = shift(@_);
278 # Create the output file name from the input file name
279 my $OutName = GetOutName ($InName);
281 # Current cheap local label prefix is empty
282 my $CheapPrefix = "";
284 # Open a the input file
285 my $FileName = $Files{$InName}; # Includes path if needed
286 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
288 # Read and process all lines from the file
289 while ($Line = <INPUT>) {
295 if ($Line =~ /^\s*(\@?)([_a-zA-Z][_\w]*)\s*(:|=)/) {
297 # Is this a local label?
300 $Id = "$CheapPrefix$1$2";
304 # Remember the id as new cheap local prefix
309 $Labels{$OutName}{$Id} = GenLabel();
311 # Check for an import statement
312 } elsif ($Line =~ /^\s*(\.import|\.importzp)\s+(.*?)(\s*)(;.*$|$)/) {
314 # Split into a list of identifiers
315 my @Ids = split (/\s*,\s*/, $2);
317 $Imports{$OutName}{$Id} = GenLabel();
320 # Check for an export statement
321 } elsif ($Line =~ /^\s*(\.export|\.exportzp)\s+(.*?)(\s*)(;.*$|$)/) {
323 # Split into a list of identifiers
324 my @Ids = split (/\s*,\s*/, $2);
326 $Exports{$Id} = sprintf ("%s#%s", $OutName, GenLabel());
329 # Check for a .proc statement
330 } elsif ($Line =~ /^\s*\.proc\s+([_a-zA-Z][_\w]*)?.*$/) {
335 $Labels{$OutName}{$Id} = GenLabel();
341 # Close the input file
347 # Pass1: Read all files for the first time.
350 # Walk over the files
351 for my $InName (keys (%Files)) {
361 #-----------------------------------------------------------------------------#
363 # ----------------------------------------------------------------------------#
367 # Process2: Read one file the second time.
380 # Input file is parameter
381 my $InName = shift(@_);
383 # Create the output file name from the input file name
384 my $OutName = GetOutName ($InName);
386 # Current cheap local label prefix is empty
387 my $CheapPrefix = "";
389 # Open a the input file
390 my $FileName = $Files{$InName}; # Includes path if needed
391 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
393 # Open the output file and print the HTML header
394 open (OUTPUT, ">$OutName") or Abort ("Cannot open $OutName: $!");
395 DocHeader (OUTPUT, $InName);
397 # The instructions that will have hyperlinks if a label is used
398 my $Ins = "adc|add|and|bcc|bcs|beq|bit|bmi|bne|bpl|bcv|bra|bvs|".
399 "cmp|cpx|cpy|dec|eor|inc|jmp|jsr|lda|ldx|ldy|ora|rol|".
400 "sbc|sta|stx|sty|sub|";
402 # Read the input file, replacing references by hyperlinks and mark
403 # labels as link targets.
404 while ($Line = <INPUT>) {
409 # Clear the output line
412 # Check for a label. If we have one, process it and remove it
414 if ($Line =~ /^\s*?(\@?)([_a-zA-Z][_\w]*)(\s*)(:|=)(.*)$/) {
416 # Is this a local label?
419 $Id = "$CheapPrefix$1$2";
423 # Remember the id as new cheap local prefix
427 # Get the label for the id
428 $Label = $Labels{$OutName}{$Id};
430 # Print the label with a tag
431 $OutLine .= sprintf ("<a name=\"%s\">%s%s</a>%s%s", $Label, $1, $2, $3, $4);
433 # Use the remainder for line
437 # Print any leading whitespace and remove it, so we don't have to
438 # care about whitespace below.
439 if ($Line =~ /^(\s+)(.*)$/) {
444 # Handle the import statements
445 if ($Line =~ /^(\.import|\.importzp)(\s+)(.*)$/) {
447 # Print any fixed stuff from the line and remove it
451 # Print all identifiers if there are any
452 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
454 # Identifier is $1, remainder is $2
458 # Variable to assemble HTML representation
461 # If we have an export for this import, add a link to this
463 if (exists ($Exports{$Id})) {
464 $Label = $Exports{$Id};
465 $Item = sprintf ("<a href=\"%s\">%s</a>", $Label, $Item);
468 # Make this import a link target
469 if (exists ($Imports{$OutName}{$Id})) {
470 $Label = $Imports{$OutName}{$1};
471 $Item = sprintf ("<a name=\"%s\">%s</a>", $Label, $Item);
474 # Add the HTML stuff to the output line
477 # Check if another identifier follows
478 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
486 # Add an remainder if there is one
487 $OutLine .= Cleanup ($Line);
489 # Handle export statements
490 } elsif ($Line =~ /^(\.export|\.exportzp)(\s+)(.*)$/) {
492 # Print the command the and white space
496 # Print all identifiers if there are any
497 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
499 # Identifier is $1, remainder is $2
503 # Variable to assemble HTML representation
506 # If we have a definition for this export in this file, add
507 # a link to the definition.
508 if (exists ($Labels{$OutName}{$1})) {
509 $Label = $Labels{$OutName}{$1};
510 $Item = sprintf ("<a href=\"#%s\">%s</a>", $Label, $Item);
513 # If we have this identifier in the list of exports, add a
514 # jump target for the export.
515 if (exists ($Exports{$Id})) {
516 $Label = $Exports{$Id};
517 # Be sure to use only the label part
518 $Label =~ s/^(.*#)(.*)$/$2/;
519 $Item = sprintf ("<a name=\"%s\">%s</a>", $Label, $Item);
522 # Add the HTML stuff to the output line
525 # Check if another identifier follows
526 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
534 # Add an remainder if there is one
535 $OutLine .= Cleanup ($Line);
537 # Check for .addr and .word
538 } elsif ($Line =~ /^(\.addr|\.word)(\s+)(.*)$/) {
540 # Print the command the and white space
544 # Print all identifiers if there are any
545 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
546 if (exists ($Labels{$OutName}{$1})) {
547 $Label = $Labels{$OutName}{$1};
548 $OutLine .= sprintf ("<a href=\"#%s\">%s</a>", $Label, $1);
553 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
561 # Add an remainder if there is one
562 $OutLine .= Cleanup ($Line);
565 } elsif ($Line =~ /^(\.proc\s+)([_a-zA-Z][_\w]*)?(.*)$/) {
567 # Do we have an identifier?
569 # Get the label for the id
570 $Label = $Labels{$OutName}{$2};
572 # Print the label with a tag
573 $OutLine .= sprintf ("%s<a name=\"%s\">%s</a>", $1, $Label, $2);
575 # Use the remainder for line
579 # Cleanup the remainder and add it
580 $OutLine .= Cleanup ($Line);
582 # Check for any legal instruction
583 } elsif ($Line =~ /^($Ins)(\s+)(.*?)(\s*)(;.*$|$)/) {
585 # Print the instruction and white space
588 # Remember the remaining parts
590 $Comment = Cleanup ("$4$5");
592 # Check for the first identifier in the operand and replace it
594 if ($Operand =~ /^([^_a-zA-Z]*?)(\@?)([_a-zA-Z][_\w]*)(.*)$/) {
596 # Is this a local label?
599 $Id = "$CheapPrefix$2$3";
605 # Get the reference to this label if we find it
606 $Operand = Cleanup($1) . RefLabel($OutName, $Id, $2 . $3) . Cleanup($4);
609 # Reassemble and print the line
610 $OutLine .= "$Operand$Comment";
614 # Nothing known - print the line
615 $OutLine .= Cleanup ($Line);
620 print OUTPUT "$OutLine\n";
623 # Print the HTML footer
624 DocFooter (OUTPUT, $OutName);
633 # Pass2: Read all files the second time.
636 # Walk over the files
637 for my $InName (keys (%Files)) {
647 #-----------------------------------------------------------------------------#
648 # Create an index page #
649 # ----------------------------------------------------------------------------#
653 # Print a list of all files
659 # Print the file list in a table
660 print $INDEX "<h2>Files</h2><p>\n";
661 print $INDEX "<table border=\"0\" width=\"100%\">\n";
663 for my $File (sort (keys (%Files))) {
666 if (($Count % $IndexCols) == 0) {
667 print $INDEX "<tr>\n";
669 printf $INDEX "<td><a href=\"%s\">%s</a></td>\n", GetOutName ($File), $File;
670 if (($Count % $IndexCols) == $IndexCols-1) {
671 print $INDEX "</tr>\n";
675 if (($Count % $IndexCols) != 0) {
676 print $INDEX "</tr>\n";
678 print $INDEX "</table><p><br><p>\n";
683 # Print a list of all exports
689 # Print the file list in a table
690 print $INDEX "<h2>Exports</h2><p>\n";
691 print $INDEX "<table border=\"0\" width=\"100%\">\n";
693 for my $Export (sort (keys (%Exports))) {
698 ($File, $Label) = split (/#/, $Exports{$Export});
700 # The label is the label of the export statement. If we can find the
701 # actual label, use this instead.
702 if (exists ($Labels{$File}{$Export})) {
703 $Label = $Labels{$File}{$Export};
707 if (($Count % $IndexCols) == 0) {
708 print $INDEX "<tr>\n";
710 printf $INDEX "<td><a href=\"%s#%s\">%s</a></td>\n", $File, $Label, $Export;
711 if (($Count % $IndexCols) == $IndexCols-1) {
712 print $INDEX "</tr>\n";
716 if (($Count % $IndexCols) != 0) {
717 print $INDEX "</tr>\n";
719 print $INDEX "</table><p><br><p>\n";
726 # Open the index page file
727 open (INDEX, ">$IndexName") or Abort ("Cannot open $IndexName: $!");
731 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
734 <meta http-equiv="Content-Type" content=\"text/html; charset=iso-8859-1">
735 <meta name="GENERATOR" content="ca65html">
736 <title>$IndexTitle</title>
738 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
740 <center><h1>$IndexTitle</h1></center>
744 # Print the file list in a table
748 # Print the document footer
749 my $Today = localtime;
754 <a href="http://validator.w3.org/check/referer"><img border=0
755 src="http://validator.w3.org/images/vh40"
756 alt="Valid HTML 4.0!" height="31" width="88" align=right></a>
757 $IndexName; generated on $Today by ca65html<br>
758 <a href=\"mailto:uz\@cc65.org\">uz\@cc65.org</a>
764 # Close the index file
770 #-----------------------------------------------------------------------------#
771 # Print usage information #
772 # ----------------------------------------------------------------------------#
777 print "Usage: ca65html [options] file ...\n";
779 print "\t--bgcolor c\tUse background color c instead of $BGColor\n";
780 print "\t--help\t\tThis text\n";
781 print "\t--indexcols n\tUse n columns on index page (default $IndexCols)\n";
782 print "\t--indexname f\tUse name f for the index file instead of $IndexName\n";
783 print "\t--indexpage\tCreate an index page\n";
784 print "\t--indextitle t\tUse t as the index title instead of $IndexTitle\n";
785 print "\t--linkstyle s\tUse the given link style\n";
786 print "\t--replaceext\tReplace source extension instead of appending .html\n";
787 print "\t--textcolor c\tUse text color c instead of $TextColor\n";
788 print "\t--verbose\tBe more verbose\n";
793 #-----------------------------------------------------------------------------#
795 # ----------------------------------------------------------------------------#
799 # Get program options
800 GetOptions ("bgcolor=s" => \$BGColor,
803 "indexcols=i" => \$IndexCols,
804 "indexname=s" => \$IndexName,
805 "indexpage" => \$IndexPage,
806 "indextitle=s" => \$IndexTitle,
807 "linkstyle=i" => \$LinkStyle,
808 "replaceext" => \$ReplaceExt,
809 "textcolor=s" => \$TextColor,
810 "verbose!" => \$Verbose,
813 # Check some arguments
814 if ($IndexCols <= 0 || $IndexCols >= 20) {
815 Abort ("Invalid value for --indexcols option");
818 # Print help if requested
823 # Check if we have input files given
824 if ($FileCount == 0) {
825 Abort ("No input files");
828 # Convert the documents
832 # Generate an index page if requested