dmolony-DiskBrowser/src/com/bytezone/diskbrowser/applefile/XrefFormatter.java

461 lines
16 KiB
Java

package com.bytezone.diskbrowser.applefile;
import static com.bytezone.diskbrowser.utilities.Utility.isPossibleNumber;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import com.bytezone.diskbrowser.gui.BasicPreferences;
import com.bytezone.diskbrowser.utilities.Utility;
// -----------------------------------------------------------------------------------//
public class XrefFormatter extends BasicFormatter
// -----------------------------------------------------------------------------------//
{
private static final String underline =
"----------------------------------------------------"
+ "----------------------------------------------";
private final Map<Integer, List<Integer>> gotoLines = new TreeMap<> ();
private final Map<Integer, List<Integer>> gosubLines = new TreeMap<> ();
private final Map<Integer, List<Integer>> constantsInt = new TreeMap<> ();
private final Map<Float, List<Integer>> constantsFloat = new TreeMap<> ();
private final Map<String, List<Integer>> callLines = new TreeMap<> ();
private final Map<String, List<Integer>> symbolLines = new TreeMap<> ();
private final Map<String, List<Integer>> functionLines = new TreeMap<> ();
private final Map<String, List<Integer>> arrayLines = new TreeMap<> ();
private final Map<String, List<String>> uniqueSymbols = new TreeMap<> ();
private final Map<String, List<String>> uniqueArrays = new TreeMap<> ();
private final List<Integer> stringsLine = new ArrayList<> ();
private final List<String> stringsText = new ArrayList<> ();
private final String formatLeft;
private final String formatLineNumber;
private final String formatRight;
private final int longestVarName;
private final int maxDigits;
// ---------------------------------------------------------------------------------//
public XrefFormatter (ApplesoftBasicProgram program, BasicPreferences basicPreferences)
// ---------------------------------------------------------------------------------//
{
super (program, basicPreferences);
for (SourceLine sourceLine : program.getSourceLines ())
checkXref (sourceLine);
longestVarName = getLongestName ();
maxDigits = getMaxDigits ();
// build format strings based on existing line numbers and variable names
formatLeft = longestVarName > 7 ? "%-" + longestVarName + "." + longestVarName + "s "
: "%-7.7s ";
formatRight = formatLeft.replace ("-", "");
formatLineNumber = "%" + maxDigits + "d ";
}
// ---------------------------------------------------------------------------------//
void checkXref (SourceLine line)
// ---------------------------------------------------------------------------------//
{
for (SubLine subline : line.sublines)
{
for (String symbol : subline.getVariables ())
checkVar (symbol, line.lineNumber, symbolLines, uniqueSymbols);
for (String symbol : subline.getArrays ())
checkVar (symbol, line.lineNumber, arrayLines, uniqueArrays);
for (String symbol : subline.getFunctions ())
checkFunction (line.lineNumber, symbol);
for (int targetLine : subline.getGosubLines ())
addNumberInt (line.lineNumber, targetLine, gosubLines);
for (int targetLine : subline.getGotoLines ())
addNumberInt (line.lineNumber, targetLine, gotoLines);
for (int num : subline.getConstantsInt ())
addNumberInt (line.lineNumber, num, constantsInt);
for (float num : subline.getConstantsFloat ())
addNumberFloat (line.lineNumber, num, constantsFloat);
if (subline.callTarget != null)
addString (line.lineNumber, subline.callTarget, callLines);
for (String s : subline.getStringsText ())
{
stringsText.add (s);
stringsLine.add (line.lineNumber);
}
}
}
// ---------------------------------------------------------------------------------//
@Override
public void append (StringBuilder fullText)
// ---------------------------------------------------------------------------------//
{
if (basicPreferences.showSymbols)
{
if (!symbolLines.isEmpty ())
showSymbolsLeft (fullText, symbolLines, "Var");
if (!arrayLines.isEmpty ())
showSymbolsLeft (fullText, arrayLines, "Array");
}
if (basicPreferences.showDuplicateSymbols)
{
if (!uniqueSymbols.isEmpty ())
showDuplicates (fullText, uniqueSymbols, "Var");
if (!uniqueArrays.isEmpty ())
showDuplicates (fullText, uniqueArrays, "Array");
}
if (basicPreferences.showFunctions && !functionLines.isEmpty ())
showSymbolsLeft (fullText, functionLines, "Fnction");
if (basicPreferences.showConstants)
{
if (!constantsInt.isEmpty ())
showSymbolsRightInt (fullText, constantsInt, "Integer");
if (!constantsFloat.isEmpty ())
showSymbolsRightFloat (fullText, constantsFloat, "Float");
if (stringsLine.size () > 0)
{
heading (fullText, formatRight, "Line", "String");
for (int i = 0; i < stringsLine.size (); i++)
fullText.append (String.format (formatRight + "%s%n", stringsLine.get (i),
stringsText.get (i)));
}
}
if (basicPreferences.showGosubGoto)
{
if (!gosubLines.isEmpty ())
showSymbolsRight (fullText, gosubLines, "GOSUB");
if (!gotoLines.isEmpty ())
showSymbolsRight (fullText, gotoLines, "GOTO");
}
if (basicPreferences.showCalls && !callLines.isEmpty ())
showSymbolsLeftRight (fullText, callLines, " CALL");
}
// ---------------------------------------------------------------------------------//
private int getMaxDigits ()
// ---------------------------------------------------------------------------------//
{
if (sourceLines.size () == 0)
return 4; // anything non-zero
SourceLine lastLine = sourceLines.get (sourceLines.size () - 1);
return (lastLine.lineNumber + "").length ();
}
// ---------------------------------------------------------------------------------//
private int getLongestName ()
// ---------------------------------------------------------------------------------//
{
int longestName = getLongestName (symbolLines, 0);
longestName = getLongestName (arrayLines, longestName);
longestName = getLongestName (functionLines, longestName);
return longestName;
}
// ---------------------------------------------------------------------------------//
private int getLongestName (Map<String, List<Integer>> map, int longestName)
// ---------------------------------------------------------------------------------//
{
for (String symbol : map.keySet ())
if (symbol.length () > longestName)
longestName = symbol.length ();
return longestName;
}
// ---------------------------------------------------------------------------------//
private void heading (StringBuilder fullText, String format, String... heading)
// ---------------------------------------------------------------------------------//
{
if (fullText.charAt (fullText.length () - 2) != '\n')
fullText.append (NEWLINE);
fullText.append (String.format (format, underline));
fullText.append (underline);
fullText.append (NEWLINE);
fullText.append (String.format (format, heading[0]));
if (heading.length == 1)
fullText.append ("Line numbers");
else
fullText.append (heading[1]);
fullText.append (NEWLINE);
fullText.append (String.format (format, underline));
fullText.append (underline);
fullText.append (NEWLINE);
}
// ---------------------------------------------------------------------------------//
private void showDuplicates (StringBuilder fullText, Map<String, List<String>> map,
String heading)
// ---------------------------------------------------------------------------------//
{
boolean headingShown = false;
for (String key : map.keySet ())
{
List<String> usage = map.get (key);
if (usage.size () > 1)
{
if (!headingShown)
{
headingShown = true;
heading (fullText, formatLeft, heading, "Duplicate Names");
}
String line = usage.toString ();
line = line.substring (1, line.length () - 1);
fullText.append (String.format ("%-6s %s%n", key, line));
}
}
}
// ---------------------------------------------------------------------------------//
private void showSymbolsLeft (StringBuilder fullText, Map<String, List<Integer>> map,
String heading)
// ---------------------------------------------------------------------------------//
{
heading (fullText, formatLeft, heading);
for (String symbol : map.keySet ()) // left-justify strings
{
if (symbol.length () <= 7)
appendLineNumbers (fullText, String.format (formatLeft, symbol),
map.get (symbol));
else
appendLineNumbers (fullText, symbol + " ", map.get (symbol));
}
}
// ---------------------------------------------------------------------------------//
private void showSymbolsLeftRight (StringBuilder fullText,
Map<String, List<Integer>> map, String heading)
// ---------------------------------------------------------------------------------//
{
heading (fullText, formatLeft, heading);
for (String symbol : map.keySet ()) // left-justify strings
{
if (isNumeric (symbol))
appendLineNumbers (fullText, String.format (formatRight, symbol),
map.get (symbol));
else if (symbol.length () <= 7)
appendLineNumbers (fullText, String.format (formatLeft, symbol),
map.get (symbol));
else
appendLineNumbers (fullText, symbol + " ", map.get (symbol));
}
}
// ---------------------------------------------------------------------------------//
private void showSymbolsRight (StringBuilder fullText, Map<Integer, List<Integer>> map,
String heading)
// ---------------------------------------------------------------------------------//
{
heading (fullText, formatRight, heading);
for (Integer symbol : map.keySet ()) // right-justify integers
appendLineNumbers (fullText, String.format (formatRight, symbol), map.get (symbol));
}
// ---------------------------------------------------------------------------------//
private void showSymbolsRightInt (StringBuilder fullText,
Map<Integer, List<Integer>> map, String heading)
// ---------------------------------------------------------------------------------//
{
heading (fullText, formatRight, heading);
for (int symbol : map.keySet ()) // right-justify integers
appendLineNumbers (fullText, String.format (formatRight, symbol), map.get (symbol));
}
// ---------------------------------------------------------------------------------//
private void showSymbolsRightFloat (StringBuilder fullText,
Map<Float, List<Integer>> map, String heading)
// ---------------------------------------------------------------------------------//
{
heading (fullText, formatRight, heading);
for (float symbol : map.keySet ()) // right-justify integers
appendLineNumbers (fullText, String.format (formatRight, symbol), map.get (symbol));
}
// ---------------------------------------------------------------------------------//
private boolean isNumeric (String value)
// ---------------------------------------------------------------------------------//
{
byte[] bytes = value.getBytes ();
int start = value.charAt (0) == Utility.ASCII_MINUS ? 1 : 0;
for (int i = start; i < bytes.length; i++)
if (!isPossibleNumber (bytes[i]))
return false;
return true;
}
// ---------------------------------------------------------------------------------//
private void appendLineNumbers (StringBuilder fullText, String symbol,
List<Integer> lineNumbers)
// ---------------------------------------------------------------------------------//
{
StringBuilder text = new StringBuilder ();
text.append (symbol);
for (int lineNo : lineNumbers)
{
if (text.length () > underline.length () - maxDigits + longestVarName)
{
fullText.append (text);
fullText.append (NEWLINE);
text.setLength (0);
text.append (String.format (formatRight, ""));
}
text.append (String.format (formatLineNumber, lineNo));
}
if (text.length () > longestVarName + 3)
fullText.append (text + "\n");
}
// ---------------------------------------------------------------------------------//
private void checkVar (String var, int lineNumber, Map<String, List<Integer>> map,
Map<String, List<String>> unique)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (var);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (var, lines);
}
if (lines.size () == 0)
lines.add (lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != lineNumber)
lines.add (lineNumber);
}
checkUniqueName (var, unique);
}
// ---------------------------------------------------------------------------------//
private void checkFunction (int sourceLine, String var)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = functionLines.get (var);
if (lines == null)
{
lines = new ArrayList<> ();
functionLines.put (var, lines);
}
addLine (lines, sourceLine);
}
// ---------------------------------------------------------------------------------//
private void addNumberInt (int sourceLine, Integer key, Map<Integer, List<Integer>> map)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (key);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (key, lines);
}
addLine (lines, sourceLine);
}
// ---------------------------------------------------------------------------------//
private void addNumberFloat (int sourceLine, Float key, Map<Float, List<Integer>> map)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (key);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (key, lines);
}
addLine (lines, sourceLine);
}
// ---------------------------------------------------------------------------------//
private void addString (int sourceLine, String key, Map<String, List<Integer>> map)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (key);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (key, lines);
}
addLine (lines, sourceLine);
}
// ---------------------------------------------------------------------------------//
private void addLine (List<Integer> lines, int lineNumber)
// ---------------------------------------------------------------------------------//
{
if (lines.size () == 0)
lines.add (lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != lineNumber)
lines.add (lineNumber);
}
}
// ---------------------------------------------------------------------------------//
private void checkUniqueName (String symbol, Map<String, List<String>> map)
// ---------------------------------------------------------------------------------//
{
String uniqueName = getUniqueName (symbol);
List<String> usage = map.get (uniqueName);
if (usage == null)
{
usage = new ArrayList<> ();
map.put (uniqueName, usage);
}
if (!usage.contains (symbol))
usage.add (symbol);
}
// ---------------------------------------------------------------------------------//
private String getUniqueName (String symbolName)
// ---------------------------------------------------------------------------------//
{
int ptr = symbolName.length () - 1;
if (symbolName.charAt (ptr) == Utility.ASCII_DOLLAR // string
|| symbolName.charAt (ptr) == Utility.ASCII_PERCENT) // integer
ptr--;
return (ptr <= 1) ? symbolName
: symbolName.substring (0, 2) + symbolName.substring (ptr + 1);
}
}