From 34c8c16aba275de33b983fa0749cca22f5923fd8 Mon Sep 17 00:00:00 2001 From: Denis Molony Date: Thu, 4 Mar 2021 01:34:09 +1000 Subject: [PATCH] created XrefFormatter --- .../applefile/ApplesoftBasicProgram.java | 471 +----------------- .../diskbrowser/applefile/BasicFormatter.java | 31 +- .../diskbrowser/applefile/XrefFormatter.java | 460 +++++++++++++++++ 3 files changed, 495 insertions(+), 467 deletions(-) create mode 100644 src/com/bytezone/diskbrowser/applefile/XrefFormatter.java diff --git a/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java b/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java index 92f6646..75785ae 100644 --- a/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java +++ b/src/com/bytezone/diskbrowser/applefile/ApplesoftBasicProgram.java @@ -1,12 +1,7 @@ package com.bytezone.diskbrowser.applefile; -import static com.bytezone.diskbrowser.utilities.Utility.isPossibleNumber; -import static com.bytezone.diskbrowser.utilities.Utility.unsignedShort; - import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import com.bytezone.diskbrowser.utilities.Utility; @@ -14,40 +9,13 @@ import com.bytezone.diskbrowser.utilities.Utility; public class ApplesoftBasicProgram extends BasicProgram implements ApplesoftConstants // -----------------------------------------------------------------------------------// { - private static final String underline = - "----------------------------------------------------" - + "----------------------------------------------"; - private static final String NEWLINE = "\n"; - private final List sourceLines = new ArrayList<> (); private final int endPtr; - private final int longestVarName; - - private final Map> gotoLines = new TreeMap<> (); - private final Map> gosubLines = new TreeMap<> (); - private final Map> constantsInt = new TreeMap<> (); - private final Map> constantsFloat = new TreeMap<> (); - - private final Map> callLines = new TreeMap<> (); - private final Map> symbolLines = new TreeMap<> (); - private final Map> functionLines = new TreeMap<> (); - private final Map> arrayLines = new TreeMap<> (); - - private final Map> uniqueSymbols = new TreeMap<> (); - private final Map> uniqueArrays = new TreeMap<> (); - - private final List stringsLine = new ArrayList<> (); - private final List stringsText = new ArrayList<> (); - - private final String formatLeft; - private final String formatLineNumber; - private final String formatRight; - - private final int maxDigits; private UserBasicFormatter userBasicFormatter; private AppleBasicFormatter appleBasicFormatter; private DebugBasicFormatter debugBasicFormatter; + private XrefFormatter xrefFormatter; // ---------------------------------------------------------------------------------// public ApplesoftBasicProgram (String name, byte[] buffer) @@ -61,24 +29,15 @@ public class ApplesoftBasicProgram extends BasicProgram implements ApplesoftCons { SourceLine line = new SourceLine (this, buffer, ptr); sourceLines.add (line); - checkXref (line); ptr += line.length; // assumes lines are contiguous } endPtr = ptr; // record where the end-of-program marker is - 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 "; - userBasicFormatter = new UserBasicFormatter (this, basicPreferences); appleBasicFormatter = new AppleBasicFormatter (this, basicPreferences); debugBasicFormatter = new DebugBasicFormatter (this, basicPreferences); + xrefFormatter = new XrefFormatter (this, basicPreferences); } // ---------------------------------------------------------------------------------// @@ -109,66 +68,11 @@ public class ApplesoftBasicProgram extends BasicProgram implements ApplesoftCons appleBasicFormatter.format (text); if (basicPreferences.showAllXref) - addXref (text); + xrefFormatter.format (text); return Utility.rtrim (text); } - // ---------------------------------------------------------------------------------// - private void addXref (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.showXref) - { - if (!gosubLines.isEmpty ()) - showSymbolsRight (fullText, gosubLines, "GOSUB"); - - if (!gotoLines.isEmpty ()) - showSymbolsRight (fullText, gotoLines, "GOTO"); - } - - if (basicPreferences.showCalls && !callLines.isEmpty ()) - showSymbolsLeftRight (fullText, callLines, " CALL"); - } - // ---------------------------------------------------------------------------------// List getSourceLines () // ---------------------------------------------------------------------------------// @@ -190,378 +94,13 @@ public class ApplesoftBasicProgram extends BasicProgram implements ApplesoftCons return endPtr; } - // ---------------------------------------------------------------------------------// - 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 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> map, - String heading) - // ---------------------------------------------------------------------------------// - { - boolean headingShown = false; - for (String key : map.keySet ()) - { - List 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> 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> 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 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 showSymbolsRight (StringBuilder fullText, Map> 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> 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> 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 void appendLineNumbers (StringBuilder fullText, String symbol, - List 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 int getLongestName (Map> map, int longestName) - // ---------------------------------------------------------------------------------// - { - for (String symbol : map.keySet ()) - if (symbol.length () > longestName) - longestName = symbol.length (); - - return longestName; - } - // ---------------------------------------------------------------------------------// private void addHeader (StringBuilder pgm) // ---------------------------------------------------------------------------------// { pgm.append ("Name : " + name + "\n"); pgm.append (String.format ("Length : $%04X (%<,d)%n", buffer.length)); - pgm.append (String.format ("Load at : $%04X (%<,d)%n%n", getLoadAddress ())); - } - - // ---------------------------------------------------------------------------------// - int getLoadAddress () - // ---------------------------------------------------------------------------------// - { - return (buffer.length > 1) ? unsignedShort (buffer, 0) - getLineLength (0) : 0; - } - - // ---------------------------------------------------------------------------------// - private int getLineLength (int ptr) - // ---------------------------------------------------------------------------------// - { - int linkField = unsignedShort (buffer, ptr); - if (linkField == 0) - return 2; - - ptr += 4; // skip link field and line number - int length = 5; - - while (ptr < buffer.length && buffer[ptr++] != 0) - length++; - - assert length == ptr; - return length; - } - - // ---------------------------------------------------------------------------------// - private 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); - } - } - } - - // ---------------------------------------------------------------------------------// - private void checkVar (String var, int lineNumber, Map> map, - Map> unique) - // ---------------------------------------------------------------------------------// - { - List 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 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> map) - // ---------------------------------------------------------------------------------// - { - List 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> map) - // ---------------------------------------------------------------------------------// - { - List 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> map) - // ---------------------------------------------------------------------------------// - { - List lines = map.get (key); - if (lines == null) - { - lines = new ArrayList<> (); - map.put (key, lines); - } - - addLine (lines, sourceLine); - } - - // ---------------------------------------------------------------------------------// - private void addLine (List 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> map) - // ---------------------------------------------------------------------------------// - { - String uniqueName = getUniqueName (symbol); - - List 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); + pgm.append (String.format ("Load at : $%04X (%<,d)%n%n", + BasicFormatter.getLoadAddress (buffer))); } } \ No newline at end of file diff --git a/src/com/bytezone/diskbrowser/applefile/BasicFormatter.java b/src/com/bytezone/diskbrowser/applefile/BasicFormatter.java index 2962d83..8a24081 100644 --- a/src/com/bytezone/diskbrowser/applefile/BasicFormatter.java +++ b/src/com/bytezone/diskbrowser/applefile/BasicFormatter.java @@ -1,5 +1,7 @@ package com.bytezone.diskbrowser.applefile; +import static com.bytezone.diskbrowser.utilities.Utility.unsignedShort; + import java.util.List; import com.bytezone.diskbrowser.gui.BasicPreferences; @@ -35,6 +37,33 @@ public abstract class BasicFormatter implements ApplesoftConstants int getLoadAddress () // ---------------------------------------------------------------------------------// { - return program.getLoadAddress (); + // return program.getLoadAddress (); + return getLoadAddress (buffer); + } + + // ---------------------------------------------------------------------------------// + static int getLoadAddress (byte[] buffer) + // ---------------------------------------------------------------------------------// + { + return (buffer.length > 1) ? unsignedShort (buffer, 0) - getLineLength (buffer, 0) + : 0; + } + + // ---------------------------------------------------------------------------------// + private static int getLineLength (byte[] buffer, int ptr) + // ---------------------------------------------------------------------------------// + { + int linkField = unsignedShort (buffer, ptr); + if (linkField == 0) + return 2; + + ptr += 4; // skip link field and line number + int length = 5; + + while (ptr < buffer.length && buffer[ptr++] != 0) + length++; + + assert length == ptr; + return length; } } diff --git a/src/com/bytezone/diskbrowser/applefile/XrefFormatter.java b/src/com/bytezone/diskbrowser/applefile/XrefFormatter.java new file mode 100644 index 0000000..836e88a --- /dev/null +++ b/src/com/bytezone/diskbrowser/applefile/XrefFormatter.java @@ -0,0 +1,460 @@ +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> gotoLines = new TreeMap<> (); + private final Map> gosubLines = new TreeMap<> (); + private final Map> constantsInt = new TreeMap<> (); + private final Map> constantsFloat = new TreeMap<> (); + + private final Map> callLines = new TreeMap<> (); + private final Map> symbolLines = new TreeMap<> (); + private final Map> functionLines = new TreeMap<> (); + private final Map> arrayLines = new TreeMap<> (); + + private final Map> uniqueSymbols = new TreeMap<> (); + private final Map> uniqueArrays = new TreeMap<> (); + + private final List stringsLine = new ArrayList<> (); + private final List 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 format (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.showXref) + { + 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> 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> map, + String heading) + // ---------------------------------------------------------------------------------// + { + boolean headingShown = false; + for (String key : map.keySet ()) + { + List 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> 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> 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> 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> 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> 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 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> map, + Map> unique) + // ---------------------------------------------------------------------------------// + { + List 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 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> map) + // ---------------------------------------------------------------------------------// + { + List 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> map) + // ---------------------------------------------------------------------------------// + { + List 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> map) + // ---------------------------------------------------------------------------------// + { + List lines = map.get (key); + if (lines == null) + { + lines = new ArrayList<> (); + map.put (key, lines); + } + + addLine (lines, sourceLine); + } + + // ---------------------------------------------------------------------------------// + private void addLine (List 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> map) + // ---------------------------------------------------------------------------------// + { + String uniqueName = getUniqueName (symbol); + + List 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); + } +}