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 ###############################################################################
37 # Things currently missing:
39 # - Scoping with .proc/.endproc
40 # - .global is ignored
41 # - .constructor/.destructor/.condes dito
42 # - .ignorecase is ignored, labels are always case sensitive
43 # - .include handling (difficult)
44 # - The global namespace operator ::
57 #-----------------------------------------------------------------------------#
59 # ----------------------------------------------------------------------------#
64 my %Files = (); # List of all files.
65 my $FileCount = 0; # Number of input files
66 my %Exports = (); # List of exported symbols.
67 my %Imports = (); # List of imported symbols.
68 my %Labels = (); # List of all labels
69 my $LabelNum = 0; # Counter to generate unique labels
71 # Command line options
72 my $BGColor = "#FFFFFF"; # Background color
73 my $Debug = 0; # No debugging
74 my $Help = 0; # Help flag
75 my $HTMLDir = ""; # Directory in which to create the files
76 my $IndexCols = 6; # Columns in the file listing
77 my $IndexTitle = "Index"; # Title of index page
78 my $IndexName = "index.html"; # Name of index page
79 my $IndexPage = 0; # Create an index page
80 my $LinkStyle = 0; # Default link style
81 my $ReplaceExt = 0; # Replace extension instead of appending
82 my $TextColor = "#000000"; # Text color
83 my $Verbose = 0; # Be quiet
85 # Table used to convert the label number into names
86 my @NameTab = ("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K",
87 "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
88 "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6",
93 #-----------------------------------------------------------------------------#
95 # ----------------------------------------------------------------------------#
99 # Terminate with an error
101 print STDERR "ca65html: @_\n";
105 # Print a message if verbose is true
108 print "ca65html: @_\n";
112 # Generate a label and return it
117 my $Num = $LabelNum++;
120 for ($I = 0; $I < 4; $I++) {
121 $L = $NameTab[$Num % 36] . $L;
127 # Make an output file name from an input file name
130 # Input name is parameter
133 # Create the output file name from the input file name
134 if ($ReplaceExt && $InName =~ /^(.+)\.([^\.\/]*)$/) {
137 return "$InName.html";
141 # Remove illegal characters from a string
151 # Strip a path from a filename and return just the name
154 # Filename is argument
155 my $FileName = $_[0];
157 # Remove a path name if we have one
158 $FileName =~ /^(.*?)([^\/]*)$/;
164 #-----------------------------------------------------------------------------#
165 # Document header and footer #
166 # ----------------------------------------------------------------------------#
170 # Print the document header
172 my $OUT = shift (@_);
173 my $Asm = shift (@_);
175 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
178 <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
179 <meta name="GENERATOR" content="ca65html">
182 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
184 <center><h1>$Asm</h1></center>
190 # Print the document footer
192 my $OUT = shift (@_);
193 my $Name = shift (@_);
195 # Get the current date and time
196 my $Today = localtime;
203 <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>
204 $Name; generated on $Today by ca65html<br>
205 <a href=\"mailto:uz\@cc65.org\">uz\@cc65.org</a>
214 #-----------------------------------------------------------------------------#
215 # File list management #
216 # ----------------------------------------------------------------------------#
222 # Argument is file to add
223 my $FileName = $_[0];
225 # Get just the name (remove a path if there is one)
226 my $Name = StripPath ($FileName);
228 # Check if we have the file already
229 if (exists ($Files{$Name})) {
230 Gabble ("File \"$FileName\" already known");
234 # Check with the full pathname. If we don't find it, search in the current
236 if (-f $FileName && -r $FileName) {
237 $Files{$Name} = $FileName;
239 } elsif (-f $Name && -r $Name) {
240 $Files{$Name} = $Name;
243 Abort ("$FileName not found or not readable");
249 #-----------------------------------------------------------------------------#
250 # Referencing and defining labels #
251 # ----------------------------------------------------------------------------#
255 # Get a label reference
258 # Arguments are: Filename, identifier, item that should be tagged
259 my $FileName = $_[0];
263 # Search for the identifier in the list of labels
264 if (exists ($Labels{$FileName}{$Id})) {
265 # It is a label (in this file)
266 return sprintf ("<a href=\"#%s\">%s</a>", $Labels{$FileName}{$Id}, $Item);
267 } elsif (exists ($Imports{$FileName}{$Id})) {
268 # It is an import. If LinkStyle is 1, or if the file exporting the
269 # identifier is not visible, we link to the .import statement in the
270 # current file. Otherwise we link directly to the referenced symbol
271 # in the file that exports it.
272 if ($LinkStyle == 1 or not exists ($Exports{$Id})) {
273 return sprintf ("<a href=\"#%s\">%s</a>", $Imports{$FileName}{$Id}, $Item);
275 # Get the filename from the export
277 ($FileName, $Label) = split (/#/, $Exports{$Id});
278 if (not defined ($Labels{$FileName}{$Id})) {
279 # This may currently happen because we don't see .include
280 # statements, so we may have an export but no definition.
281 # Link to the .export statement instead
282 $Label = $Exports{$Id};
284 # Link to the definition in the file
285 $Label = sprintf ("%s#%s", $FileName, $Labels{$FileName}{$Id});
287 return sprintf ("<a href=\"%s\">%s</a>", $Label, $Item);
290 # The symbol is unknown, return as is
297 #-----------------------------------------------------------------------------#
299 # ----------------------------------------------------------------------------#
303 # Process1: Read one file for the first time.
310 # Filename is parameter
311 my $InName = shift(@_);
313 # Create the output file name from the input file name
314 my $OutName = GetOutName ($InName);
316 # Current cheap local label prefix is empty
317 my $CheapPrefix = "";
319 # Open a the input file
320 my $FileName = $Files{$InName}; # Includes path if needed
321 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
323 # Keep the user happy
324 Gabble ("$FileName => $OutName");
326 # Read and process all lines from the file
327 while ($Line = <INPUT>) {
333 if ($Line =~ /^\s*(\@?)([_a-zA-Z][_\w]*)\s*(:|=)/) {
335 # Is this a local label?
338 $Id = "$CheapPrefix$1$2";
342 # Remember the id as new cheap local prefix
347 $Labels{$OutName}{$Id} = GenLabel();
349 # Check for an import statement
350 } elsif ($Line =~ /^\s*(\.import|\.importzp)\s+(.*?)(\s*)(;.*$|$)/) {
352 # Split into a list of identifiers
353 my @Ids = split (/\s*,\s*/, $2);
355 $Imports{$OutName}{$Id} = GenLabel();
358 # Check for an export statement
359 } elsif ($Line =~ /^\s*(\.export|\.exportzp)\s+(.*?)(\s*)(;.*$|$)/) {
361 # Split into a list of identifiers
362 my @Ids = split (/\s*,\s*/, $2);
364 $Exports{$Id} = sprintf ("%s#%s", $OutName, GenLabel());
367 # Check for a .proc statement
368 } elsif ($Line =~ /^\s*\.proc\s+([_a-zA-Z][_\w]*)?.*$/) {
373 $Labels{$OutName}{$Id} = GenLabel();
379 # Close the input file
385 # Pass1: Read all files for the first time.
388 # Keep the user happy
391 # Walk over the files
392 for my $InName (keys (%Files)) {
400 #-----------------------------------------------------------------------------#
402 # ----------------------------------------------------------------------------#
406 # Process2: Read one file the second time.
419 # Input file is parameter
420 my $InName = shift(@_);
422 # Create the output file name from the input file name
423 my $OutName = GetOutName ($InName);
425 # Current cheap local label prefix is empty
426 my $CheapPrefix = "";
428 # Open a the input file
429 my $FileName = $Files{$InName}; # Includes path if needed
430 open (INPUT, "<$FileName") or Abort ("Cannot open $FileName: $!");
432 # Open the output file and print the HTML header
433 open (OUTPUT, ">$HTMLDir$OutName") or Abort ("Cannot open $OutName: $!");
434 DocHeader (OUTPUT, $InName);
436 # Keep the user happy
437 Gabble ("$FileName => $OutName");
439 # The instructions that will have hyperlinks if a label is used
440 my $Ins = "adc|add|and|bcc|bcs|beq|bit|bmi|bne|bpl|bcv|bra|bvs|".
441 "cmp|cpx|cpy|dec|eor|inc|jmp|jsr|lda|ldx|ldy|ora|rol|".
442 "sbc|sta|stx|sty|sub|";
444 # Read the input file, replacing references by hyperlinks and mark
445 # labels as link targets.
446 while ($Line = <INPUT>) {
451 # Clear the output line
454 # Check for a label. If we have one, process it and remove it
456 if ($Line =~ /^\s*?(\@?)([_a-zA-Z][_\w]*)(\s*)(:|=)(.*)$/) {
458 # Is this a local label?
461 $Id = "$CheapPrefix$1$2";
465 # Remember the id as new cheap local prefix
469 # Get the label for the id
470 $Label = $Labels{$OutName}{$Id};
472 # Print the label with a tag
473 $OutLine .= sprintf ("<a name=\"%s\">%s%s</a>%s%s", $Label, $1, $2, $3, $4);
475 # Use the remainder for line
479 # Print any leading whitespace and remove it, so we don't have to
480 # care about whitespace below.
481 if ($Line =~ /^(\s+)(.*)$/) {
486 # Handle the import statements
487 if ($Line =~ /^(\.import|\.importzp)(\s+)(.*)$/) {
489 # Print any fixed stuff from the line and remove it
493 # Print all identifiers if there are any
494 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
496 # Identifier is $1, remainder is $2
500 # Variable to assemble HTML representation
503 # Make this import a link target
504 if (exists ($Imports{$OutName}{$Id})) {
505 $Label = $Imports{$OutName}{$1};
506 $Contents .= sprintf (" name=\"%s\"", $Label);
509 # If we have an export for this import, add a link to this
511 if (exists ($Exports{$Id})) {
512 $Label = $Exports{$Id};
513 $Contents .= sprintf (" href=\"%s\"", $Label);
516 # Add the HTML stuff to the output line
517 if ($Contents ne "") {
518 $OutLine .= sprintf ("<a%s>%s</a>", $Contents, $Id);
523 # Check if another identifier follows
524 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
532 # Add an remainder if there is one
533 $OutLine .= Cleanup ($Line);
535 # Handle export statements
536 } elsif ($Line =~ /^(\.export|\.exportzp)(\s+)(.*)$/) {
538 # Print the command the and white space
542 # Print all identifiers if there are any
543 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
545 # Identifier is $1, remainder is $2
549 # Variable to assemble HTML representation
552 # If we have a definition for this export in this file, add
553 # a link to the definition.
554 if (exists ($Labels{$OutName}{$1})) {
555 $Label = $Labels{$OutName}{$1};
556 $Contents = sprintf (" href=\"#%s\"", $Label);
559 # If we have this identifier in the list of exports, add a
560 # jump target for the export.
561 if (exists ($Exports{$Id})) {
562 $Label = $Exports{$Id};
563 # Be sure to use only the label part
564 $Label =~ s/^(.*#)(.*)$/$2/; # ##FIXME: Expensive
565 $Contents .= sprintf (" name=\"%s\"", $Label);
568 # Add the HTML stuff to the output line
569 if ($Contents ne "") {
570 $OutLine .= sprintf ("<a%s>%s</a>", $Contents, $Id);
575 # Check if another identifier follows
576 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
584 # Add an remainder if there is one
585 $OutLine .= Cleanup ($Line);
587 # Check for .addr and .word
588 } elsif ($Line =~ /^(\.addr|\.word)(\s+)(.*)$/) {
590 # Print the command the and white space
594 # Print all identifiers if there are any
595 while ($Line =~ /^([_a-zA-Z][_\w]*)(.*)$/) {
596 if (exists ($Labels{$OutName}{$1})) {
597 $Label = $Labels{$OutName}{$1};
598 $OutLine .= sprintf ("<a href=\"#%s\">%s</a>", $Label, $1);
603 if ($Line =~ /^(\s*),(\s*)(.*)$/) {
611 # Add an remainder if there is one
612 $OutLine .= Cleanup ($Line);
615 } elsif ($Line =~ /^(\.proc\s+)([_a-zA-Z][_\w]*)?(.*)$/) {
617 # Do we have an identifier?
619 # Get the label for the id
620 $Label = $Labels{$OutName}{$2};
622 # Print the label with a tag
623 $OutLine .= sprintf ("%s<a name=\"%s\">%s</a>", $1, $Label, $2);
625 # Use the remainder for line
629 # Cleanup the remainder and add it
630 $OutLine .= Cleanup ($Line);
633 } elsif ($Line =~ /^(\.include)(\s+)\"((?:[^\"]+?|\\\")+)\"(\s*)(;.*$|$)/) {
635 # Add the fixed stuff to the output line
636 $OutLine .= "$1$2\"";
638 # Get the filename into a named variable
641 # Remember the remainder
644 # Get the name without a path
645 my $Name = StripPath ($FileName);
647 # We don't need FileName any longer as is, so clean it up
648 $FileName = Cleanup ($FileName);
650 # If the include file is among the list of our files, add a link,
651 # otherwise just add the name as is.
652 if (exists ($Files{$Name})) {
653 $OutLine .= sprintf ("<a href=\"%s\">%s</a>", GetOutName ($Name), $FileName);
655 $OutLine .= $FileName;
659 $OutLine .= Cleanup ($Line);
661 # Check for any legal instruction
662 } elsif ($Line =~ /^($Ins)(\s+)(.*?)(\s*)(;.*$|$)/) {
664 # Print the instruction and white space
667 # Remember the remaining parts
669 $Comment = Cleanup ("$4$5");
671 # Check for the first identifier in the operand and replace it
673 if ($Operand =~ /^([^_a-zA-Z]*?)(\@?)([_a-zA-Z][_\w]*)(.*)$/) {
675 # Is this a local label?
678 $Id = "$CheapPrefix$2$3";
684 # Get the reference to this label if we find it
685 $Operand = Cleanup($1) . RefLabel($OutName, $Id, $2 . $3) . Cleanup($4);
688 # Reassemble and print the line
689 $OutLine .= "$Operand$Comment";
693 # Nothing known - print the line
694 $OutLine .= Cleanup ($Line);
699 print OUTPUT "$OutLine\n";
702 # Print the HTML footer
703 DocFooter (OUTPUT, $OutName);
712 # Pass2: Read all files the second time.
715 # Keep the user happy
718 # Walk over the files
719 for my $InName (keys (%Files)) {
727 #-----------------------------------------------------------------------------#
728 # Create an index page #
729 # ----------------------------------------------------------------------------#
733 # Print a list of all files
739 # Print the file list in a table
740 print $INDEX "<h2>Files</h2><p>\n";
741 print $INDEX "<table border=\"0\" width=\"100%\">\n";
743 for my $File (sort (keys (%Files))) {
746 if (($Count % $IndexCols) == 0) {
747 print $INDEX "<tr>\n";
749 printf $INDEX "<td><a href=\"%s\">%s</a></td>\n", GetOutName ($File), $File;
750 if (($Count % $IndexCols) == $IndexCols-1) {
751 print $INDEX "</tr>\n";
755 if (($Count % $IndexCols) != 0) {
756 print $INDEX "</tr>\n";
758 print $INDEX "</table><p><br><p>\n";
763 # Print a list of all exports
769 # Print the file list in a table
770 print $INDEX "<h2>Exports</h2><p>\n";
771 print $INDEX "<table border=\"0\" width=\"100%\">\n";
773 for my $Export (sort (keys (%Exports))) {
778 ($File, $Label) = split (/#/, $Exports{$Export});
780 # The label is the label of the export statement. If we can find the
781 # actual label, use this instead.
782 if (exists ($Labels{$File}{$Export})) {
783 $Label = $Labels{$File}{$Export};
787 if (($Count % $IndexCols) == 0) {
788 print $INDEX "<tr>\n";
790 printf $INDEX "<td><a href=\"%s#%s\">%s</a></td>\n", $File, $Label, $Export;
791 if (($Count % $IndexCols) == $IndexCols-1) {
792 print $INDEX "</tr>\n";
796 if (($Count % $IndexCols) != 0) {
797 print $INDEX "</tr>\n";
799 print $INDEX "</table><p><br><p>\n";
806 # Open the index page file
807 open (INDEX, ">$HTMLDir$IndexName") or Abort ("Cannot open $IndexName: $!");
811 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html-40/loose.dtd">
814 <meta http-equiv="Content-Type" content=\"text/html; charset=iso-8859-1">
815 <meta name="GENERATOR" content="ca65html">
816 <title>$IndexTitle</title>
818 <body bgcolor="$BGColor" text="$TextColor" link="#0000d0" vlink="#000060" alink="#00d0d0">
820 <center><h1>$IndexTitle</h1></center>
824 # Print the file list in a table
828 # Print the document footer
829 DocFooter (INDEX, $IndexName);
831 # Close the index file
837 #-----------------------------------------------------------------------------#
838 # Print usage information #
839 # ----------------------------------------------------------------------------#
844 print "Usage: ca65html [options] file ...\n";
846 print " --bgcolor color Use background color c instead of $BGColor\n";
847 print " --help This text\n";
848 print " --htmldir dir Specify directory for HTML files\n";
849 print " --indexcols n Use n columns on index page (default $IndexCols)\n";
850 print " --indexname file Use file for the index file instead of $IndexName\n";
851 print " --indexpage Create an index page\n";
852 print " --indextitle title Use title as the index title instead of $IndexTitle\n";
853 print " --linkstyle style Use the given link style\n";
854 print " --replaceext Replace source extension instead of appending .html\n";
855 print " --textcolor color Use text color c instead of $TextColor\n";
856 print " --verbose Be more verbose\n";
861 #-----------------------------------------------------------------------------#
863 # ----------------------------------------------------------------------------#
867 # Get program options
868 GetOptions ("bgcolor=s" => \$BGColor,
871 "htmldir=s" => \$HTMLDir,
872 "indexcols=i" => \$IndexCols,
873 "indexname=s" => \$IndexName,
874 "indexpage" => \$IndexPage,
875 "indextitle=s" => \$IndexTitle,
876 "linkstyle=i" => \$LinkStyle,
877 "replaceext" => \$ReplaceExt,
878 "textcolor=s" => \$TextColor,
879 "verbose!" => \$Verbose,
882 # Check some arguments
883 if ($IndexCols <= 0 || $IndexCols >= 20) {
884 Abort ("Invalid value for --indexcols option");
886 if ($HTMLDir ne "" && $HTMLDir =~ /[^\/]$/) {
887 # Add a trailing path separator
893 # Print help if requested
898 # Check if we have input files given
899 if ($FileCount == 0) {
900 Abort ("No input files");
903 # Convert the documents
907 # Generate an index page if requested