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

929 lines
33 KiB
Java
Raw Normal View History

2019-08-09 23:45:49 +00:00
package com.bytezone.diskbrowser.applefile;
2021-01-08 02:03:12 +00:00
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_DATA;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_DIM;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_FOR;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_GOSUB;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_GOTO;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_IF;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_INPUT;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_NEXT;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_ON;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_PRINT;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_REM;
import static com.bytezone.diskbrowser.applefile.ApplesoftConstants.TOKEN_RETURN;
2019-08-09 23:45:49 +00:00
import java.util.ArrayList;
import java.util.List;
2020-12-23 10:34:18 +00:00
import java.util.Map;
2019-08-09 23:45:49 +00:00
import java.util.Stack;
2020-12-23 10:34:18 +00:00
import java.util.TreeMap;
2020-12-24 08:41:35 +00:00
import java.util.regex.Matcher;
import java.util.regex.Pattern;
2019-08-09 23:45:49 +00:00
import com.bytezone.diskbrowser.utilities.HexFormatter;
import com.bytezone.diskbrowser.utilities.Utility;
2019-08-09 23:45:49 +00:00
2020-09-13 00:22:49 +00:00
// -----------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
public class ApplesoftBasicProgram extends BasicProgram
2020-09-13 00:22:49 +00:00
// -----------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2021-01-06 01:36:41 +00:00
static final String underline = "----------------------------------------------------"
+ "----------------------------------------------";
2019-08-09 23:45:49 +00:00
private final List<SourceLine> sourceLines = new ArrayList<> ();
private final int endPtr;
2021-01-06 01:36:41 +00:00
private final int longestVarName;
2020-12-31 09:50:53 +00:00
2021-01-05 03:24:23 +00:00
private final Map<Integer, List<Integer>> gotoLines = new TreeMap<> ();
private final Map<Integer, List<Integer>> gosubLines = new TreeMap<> ();
private final Map<Integer, List<Integer>> constants = new TreeMap<> ();
private final Map<String, List<Integer>> callLines = new TreeMap<> ();
2021-01-03 03:04:24 +00:00
private final Map<String, List<Integer>> symbolLines = new TreeMap<> ();
2021-01-04 10:13:31 +00:00
private final Map<String, List<Integer>> functionLines = new TreeMap<> ();
private final Map<String, List<Integer>> arrayLines = new TreeMap<> ();
2021-01-05 03:24:23 +00:00
2021-01-03 03:04:24 +00:00
private final Map<String, List<String>> uniqueSymbols = new TreeMap<> ();
2021-01-05 03:24:23 +00:00
private final Map<String, List<String>> uniqueArrays = new TreeMap<> ();
2020-12-31 09:50:53 +00:00
2020-12-30 03:06:50 +00:00
final List<Integer> stringsLine = new ArrayList<> ();
final List<String> stringsText = new ArrayList<> ();
2019-08-09 23:45:49 +00:00
2021-01-06 07:37:54 +00:00
String formatLeft;
String formatLineNumber;
String formatRight;
2021-01-06 09:15:58 +00:00
int maxDigits;
2021-01-06 07:37:54 +00:00
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
public ApplesoftBasicProgram (String name, byte[] buffer)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
super (name, buffer);
int ptr = 0;
2020-12-31 09:50:53 +00:00
int currentAddress = 0;
2019-08-09 23:45:49 +00:00
2020-11-21 11:15:08 +00:00
int max = buffer.length - 6; // need at least 6 bytes to make a SourceLine
2020-11-20 09:19:51 +00:00
while (ptr <= max)
2019-08-09 23:45:49 +00:00
{
2020-11-21 11:15:08 +00:00
int nextAddress = Utility.unsignedShort (buffer, ptr);
2020-12-31 09:50:53 +00:00
if (nextAddress <= currentAddress) // usually zero when finished
2019-08-09 23:45:49 +00:00
break;
2020-12-30 03:06:50 +00:00
SourceLine line = new SourceLine (this, buffer, ptr);
2019-08-09 23:45:49 +00:00
sourceLines.add (line);
ptr += line.length;
2020-12-31 09:50:53 +00:00
currentAddress = nextAddress;
2021-01-03 03:04:24 +00:00
for (SubLine subline : line.sublines)
{
for (String symbol : subline.getSymbols ())
2021-01-05 03:24:23 +00:00
checkVar (symbol, line.lineNumber, symbolLines, uniqueSymbols);
2021-01-04 10:13:31 +00:00
for (String symbol : subline.getArrays ())
2021-01-05 03:24:23 +00:00
checkVar (symbol, line.lineNumber, arrayLines, uniqueArrays);
2021-01-04 10:13:31 +00:00
for (String symbol : subline.getFunctions ())
checkFunction (symbol, line.lineNumber);
2021-01-03 03:04:24 +00:00
for (int targetLine : subline.getGosubLines ())
addXref (line.lineNumber, targetLine, gosubLines);
for (int targetLine : subline.getGotoLines ())
addXref (line.lineNumber, targetLine, gotoLines);
2021-01-05 03:24:23 +00:00
for (int targetLine : subline.getConstants ())
addXref (line.lineNumber, targetLine, constants);
2021-01-03 05:06:51 +00:00
if (subline.callTarget != null)
addXref (line.lineNumber, subline.callTarget, callLines);
2021-01-03 03:04:24 +00:00
}
2019-08-09 23:45:49 +00:00
}
endPtr = ptr;
2021-01-06 01:36:41 +00:00
longestVarName = getLongestName ();
2021-01-06 07:37:54 +00:00
formatLeft = longestVarName > 7 ? "%-" + longestVarName + "." + longestVarName + "s "
: "%-7.7s ";
formatRight = longestVarName > 7 ? "%" + longestVarName + "." + longestVarName + "s "
: "%7.7s ";
2021-01-06 09:15:58 +00:00
maxDigits = getMaxDigits ();
formatLineNumber = "%" + maxDigits + "d ";
2019-08-09 23:45:49 +00:00
}
2021-01-03 03:04:24 +00:00
// ---------------------------------------------------------------------------------//
2021-01-05 03:24:23 +00:00
void checkVar (String var, int lineNumber, Map<String, List<Integer>> map,
Map<String, List<String>> unique)
2021-01-04 10:13:31 +00:00
// ---------------------------------------------------------------------------------//
{
2021-01-05 03:24:23 +00:00
List<Integer> lines = map.get (var);
2021-01-04 10:13:31 +00:00
if (lines == null)
{
lines = new ArrayList<> ();
2021-01-05 03:24:23 +00:00
map.put (var, lines);
2021-01-04 10:13:31 +00:00
}
2021-01-03 03:04:24 +00:00
if (lines.size () == 0)
lines.add (lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != lineNumber)
lines.add (lineNumber);
}
2021-01-04 10:13:31 +00:00
2021-01-05 03:24:23 +00:00
checkUniqueName (var, unique);
2021-01-03 03:04:24 +00:00
}
2021-01-04 10:13:31 +00:00
// ---------------------------------------------------------------------------------//
void checkFunction (String var, int lineNumber)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = functionLines.get (var);
if (lines == null)
{
lines = new ArrayList<> ();
functionLines.put (var, lines);
}
if (lines.size () == 0)
lines.add (lineNumber);
else
{
int lastLine = lines.get (lines.size () - 1);
if (lastLine != lineNumber)
lines.add (lineNumber);
}
}
2021-01-03 03:04:24 +00:00
// ---------------------------------------------------------------------------------//
private void addXref (int sourceLine, int targetLine, Map<Integer, List<Integer>> map)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (targetLine);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (targetLine, lines);
}
lines.add (sourceLine);
}
2021-01-03 05:06:51 +00:00
// ---------------------------------------------------------------------------------//
private void addXref (int sourceLine, String target, Map<String, List<Integer>> map)
// ---------------------------------------------------------------------------------//
{
List<Integer> lines = map.get (target);
if (lines == null)
{
lines = new ArrayList<> ();
map.put (target, lines);
}
lines.add (sourceLine);
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
@Override
public String getText ()
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2020-12-19 05:42:25 +00:00
{
2020-12-20 01:34:42 +00:00
return showDebugText ? getHexText () : getProgramText ();
2020-12-19 05:42:25 +00:00
}
// ---------------------------------------------------------------------------------//
private String getProgramText ()
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
int indentSize = 2;
2020-12-21 05:33:03 +00:00
boolean insertBlankLine = false;
2019-08-09 23:45:49 +00:00
StringBuilder fullText = new StringBuilder ();
2020-12-19 05:42:25 +00:00
Stack<String> loopVariables = new Stack<> ();
2021-01-03 03:04:24 +00:00
2019-08-09 23:45:49 +00:00
if (basicPreferences.showHeader)
addHeader (fullText);
2021-01-03 03:04:24 +00:00
int alignEqualsPos = 0;
2019-08-09 23:45:49 +00:00
StringBuilder text;
int baseOffset = basicPreferences.showTargets ? 12 : 8;
for (SourceLine line : sourceLines)
{
text = new StringBuilder (getBase (line) + " ");
int indent = loopVariables.size (); // each full line starts at the loop indent
int ifIndent = 0; // IF statement(s) limit back indentation by NEXT
for (SubLine subline : line.sublines)
{
// Allow empty statements (caused by a single colon)
if (subline.isEmpty ())
continue;
// A REM statement might conceal an assembler routine
// - see P.CREATE on Diags2E.DSK
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_REM) && subline.containsToken ())
2019-08-09 23:45:49 +00:00
{
2021-01-03 05:06:51 +00:00
int address = getLoadAddress () + subline.startPtr + 1; // skip the REM token
2019-08-09 23:45:49 +00:00
fullText.append (text + String.format ("REM - Inline assembler @ $%02X (%d)%n",
address, address));
String padding = " ".substring (0, text.length () + 2);
2021-01-03 05:06:51 +00:00
for (String asm : getRemAssembler (subline))
2019-08-09 23:45:49 +00:00
fullText.append (padding + asm + "\n");
continue;
}
2020-11-22 10:23:35 +00:00
// Beagle Bros often have multiline REM statements
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_REM) && subline.containsControlChars ())
2020-11-22 10:23:35 +00:00
{
subline.addFormattedRem (text);
fullText.append (text + "\n");
continue;
}
2019-08-09 23:45:49 +00:00
// Reduce the indent by each NEXT, but only as far as the IF indent allows
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_NEXT))
2019-08-09 23:45:49 +00:00
{
popLoopVariables (loopVariables, subline);
indent = Math.max (ifIndent, loopVariables.size ());
}
// Are we joining REM lines with the previous subline?
if (!basicPreferences.splitRem && subline.isJoinableRem ())
{
// Join this REM statement to the previous line, so no indenting
fullText.deleteCharAt (fullText.length () - 1); // remove newline
fullText.append (" ");
}
else // ... otherwise do all the indenting and showing of targets etc.
{
// Prepare target indicators for subsequent sublines (ie no line number)
if (basicPreferences.showTargets && !subline.isFirst ())
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_GOSUB)
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOSUB)))
2019-08-09 23:45:49 +00:00
text.append ("<<--");
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_GOTO) || subline.isImpliedGoto ()
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOTO)))
2019-08-09 23:45:49 +00:00
text.append (" <--");
// Align assign statements if required
if (basicPreferences.alignAssign)
2021-01-03 03:04:24 +00:00
alignEqualsPos = alignEqualsPosition (subline, alignEqualsPos);
2019-08-09 23:45:49 +00:00
int column = indent * indentSize + baseOffset;
2019-08-09 23:45:49 +00:00
while (text.length () < column)
text.append (" ");
}
// Add the current text, then reset it
2021-01-03 03:04:24 +00:00
String lineText = subline.getAlignedText (alignEqualsPos);
2019-08-09 23:45:49 +00:00
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_REM) && basicPreferences.deleteExtraRemSpace)
2020-12-21 05:33:03 +00:00
lineText = lineText.replaceFirst ("REM ", "REM ");
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_DATA) && basicPreferences.deleteExtraDataSpace)
2020-12-21 06:10:07 +00:00
lineText = lineText.replaceFirst ("DATA ", "DATA ");
2021-01-03 03:04:24 +00:00
// Check for a wrappable REM/DATA/DIM statement
2019-08-09 23:45:49 +00:00
// (see SEA BATTLE on DISK283.DSK)
2020-12-25 04:30:26 +00:00
int inset = Math.max (text.length (), getIndent (fullText)) + 1;
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_REM) && lineText.length () > basicPreferences.wrapRemAt)
2019-08-09 23:45:49 +00:00
{
2020-11-22 01:04:27 +00:00
List<String> lines = splitLine (lineText, basicPreferences.wrapRemAt, ' ');
2020-12-25 04:30:26 +00:00
addSplitLines (lines, text, inset);
2020-11-22 01:04:27 +00:00
}
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_DATA)
2020-11-22 01:04:27 +00:00
&& lineText.length () > basicPreferences.wrapDataAt)
{
List<String> lines = splitLine (lineText, basicPreferences.wrapDataAt, ',');
2020-12-25 04:30:26 +00:00
addSplitLines (lines, text, inset);
2019-08-09 23:45:49 +00:00
}
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_DIM) && basicPreferences.splitDim)
2020-12-24 08:41:35 +00:00
{
List<String> lines = splitDim (lineText);
2020-12-25 04:30:26 +00:00
addSplitLines (lines, text, inset);
2020-12-24 08:41:35 +00:00
}
2019-08-09 23:45:49 +00:00
else
text.append (lineText);
2021-01-01 00:49:42 +00:00
// Check for a wrappable PRINT or INPUT statement
2019-08-09 23:45:49 +00:00
// (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
2021-01-01 00:49:42 +00:00
if (basicPreferences.wrapPrintAt > 0
2021-01-08 02:03:12 +00:00
&& (subline.is (TOKEN_PRINT) || subline.is (TOKEN_INPUT))
2020-12-31 09:50:53 +00:00
&& countChars (text, Utility.ASCII_QUOTE) == 2 // just start and end quotes
&& countChars (text, Utility.ASCII_CARET) == 0) // no control characters
2021-01-01 00:49:42 +00:00
wrapPrint (fullText, text, lineText);
2019-08-09 23:45:49 +00:00
else
fullText.append (text + "\n");
text.setLength (0);
// Calculate indent changes that take effect after the current subline
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_IF))
2019-08-09 23:45:49 +00:00
ifIndent = ++indent;
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_FOR))
2019-08-09 23:45:49 +00:00
{
2021-01-03 03:04:24 +00:00
String latestLoopVar = loopVariables.size () > 0 ? loopVariables.peek () : "";
if (!subline.forVariable.equals (latestLoopVar)) // don't add repeated loop
{
loopVariables.push (subline.forVariable);
++indent;
}
2019-08-09 23:45:49 +00:00
}
2021-01-08 02:03:12 +00:00
else if (basicPreferences.blankAfterReturn && subline.is (TOKEN_RETURN)
&& subline.isFirst ())
2020-12-21 05:33:03 +00:00
insertBlankLine = true;
}
2020-12-21 05:33:03 +00:00
if (insertBlankLine)
{
fullText.append ("\n");
2020-12-21 05:33:03 +00:00
insertBlankLine = false;
2019-08-09 23:45:49 +00:00
}
2020-11-20 09:19:51 +00:00
// Reset alignment value if we just left an IF - the indentation will be different now
2019-08-09 23:45:49 +00:00
if (ifIndent > 0)
2021-01-03 03:04:24 +00:00
alignEqualsPos = 0;
2019-08-09 23:45:49 +00:00
}
2019-10-12 02:47:21 +00:00
int ptr = endPtr + 2;
2019-10-13 05:14:47 +00:00
if (ptr < buffer.length - 1) // sometimes there's an extra byte on the end
2019-10-12 02:47:21 +00:00
{
int offset = Utility.unsignedShort (buffer, 0);
2019-10-12 02:47:21 +00:00
int programLoadAddress = offset - getLineLength (0);
fullText.append ("\nExtra data:\n\n");
fullText.append (HexFormatter.formatNoHeader (buffer, ptr, buffer.length - ptr,
programLoadAddress + ptr));
2020-12-24 02:03:00 +00:00
fullText.append ("\n");
2019-10-12 02:47:21 +00:00
}
2020-12-26 08:32:40 +00:00
if (basicPreferences.showSymbols && !symbolLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsLeft (fullText, symbolLines, "Var");
2020-12-26 08:32:40 +00:00
2021-01-04 10:13:31 +00:00
if (basicPreferences.showSymbols && !arrayLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsLeft (fullText, arrayLines, "Array");
2020-12-30 03:06:50 +00:00
2021-01-05 08:59:36 +00:00
if (basicPreferences.showDuplicateSymbols && !uniqueSymbols.isEmpty ())
2021-01-06 07:37:54 +00:00
showDuplicates (fullText, uniqueSymbols, "Var");
2021-01-05 08:59:36 +00:00
if (basicPreferences.showDuplicateSymbols && !uniqueArrays.isEmpty ())
2021-01-06 07:37:54 +00:00
showDuplicates (fullText, uniqueArrays, "Array");
2021-01-05 08:59:36 +00:00
2021-01-04 10:13:31 +00:00
if (basicPreferences.showFunctions && !functionLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsLeft (fullText, functionLines, "Fnction");
2020-12-29 04:52:32 +00:00
2021-01-05 03:24:23 +00:00
if (basicPreferences.showConstants && !constants.isEmpty ())
2021-01-07 02:56:20 +00:00
showSymbolsRight (fullText, constants, "Literal");
2021-01-05 03:24:23 +00:00
2020-12-24 02:03:00 +00:00
if (basicPreferences.listStrings && stringsLine.size () > 0)
{
2021-01-06 07:37:54 +00:00
heading (fullText, formatRight, "Line", "String");
2020-12-24 02:03:00 +00:00
for (int i = 0; i < stringsLine.size (); i++)
2021-01-06 07:37:54 +00:00
fullText.append (String.format (formatRight + "%s%n", stringsLine.get (i),
stringsText.get (i)));
2020-12-24 02:03:00 +00:00
}
2021-01-04 10:13:31 +00:00
if (basicPreferences.showXref && !gosubLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsRight (fullText, gosubLines, "GOSUB");
2021-01-04 10:13:31 +00:00
if (basicPreferences.showXref && !gotoLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsRight (fullText, gotoLines, "GOTO");
2021-01-04 10:13:31 +00:00
if (basicPreferences.showCalls && !callLines.isEmpty ())
2021-01-06 07:37:54 +00:00
showSymbolsLeft (fullText, callLines, "CALL");
2021-01-04 10:13:31 +00:00
2019-08-11 21:49:23 +00:00
if (fullText.length () > 0)
while (fullText.charAt (fullText.length () - 1) == '\n')
2020-12-24 02:03:00 +00:00
fullText.deleteCharAt (fullText.length () - 1); // remove trailing newlines
2019-08-11 21:49:23 +00:00
2019-08-09 23:45:49 +00:00
return fullText.toString ();
}
2021-01-05 08:59:36 +00:00
// ---------------------------------------------------------------------------------//
private int getMaxDigits ()
// ---------------------------------------------------------------------------------//
{
SourceLine lastLine = sourceLines.get (sourceLines.size () - 1);
return (lastLine.lineNumber + "").length ();
}
2021-01-01 00:49:42 +00:00
// ---------------------------------------------------------------------------------//
2021-01-06 01:36:41 +00:00
private void heading (StringBuilder fullText, String format, String... heading)
2021-01-01 00:49:42 +00:00
// ---------------------------------------------------------------------------------//
{
if (fullText.charAt (fullText.length () - 2) != '\n')
fullText.append ("\n");
2021-01-06 09:15:58 +00:00
fullText.append (String.format (format, underline));
fullText.append (underline);
fullText.append ("\n");
2021-01-06 01:36:41 +00:00
fullText.append (String.format (format, heading[0]));
if (heading.length == 1)
fullText.append ("Line numbers");
2021-01-01 00:49:42 +00:00
else
2021-01-06 01:36:41 +00:00
fullText.append (heading[1]);
2021-01-06 07:37:54 +00:00
2021-01-06 01:36:41 +00:00
fullText.append ("\n");
fullText.append (String.format (format, underline));
fullText.append (underline);
fullText.append ("\n");
2021-01-03 05:06:51 +00:00
}
2020-12-30 03:06:50 +00:00
// ---------------------------------------------------------------------------------//
2021-01-05 08:59:36 +00:00
private void showDuplicates (StringBuilder fullText, Map<String, List<String>> map,
String heading)
2021-01-04 10:13:31 +00:00
// ---------------------------------------------------------------------------------//
{
2021-01-05 08:59:36 +00:00
boolean headingShown = false;
for (String key : map.keySet ())
{
List<String> usage = map.get (key);
if (usage.size () > 1)
{
if (!headingShown)
{
headingShown = true;
2021-01-06 07:37:54 +00:00
heading (fullText, formatLeft, heading, "Duplicate Names");
2021-01-05 08:59:36 +00:00
}
2021-01-06 09:15:58 +00:00
2021-01-05 08:59:36 +00:00
String line = usage.toString ();
line = line.substring (1, line.length () - 1);
fullText.append (String.format ("%-6s %s%n", key, line));
}
}
2021-01-04 10:13:31 +00:00
}
// ---------------------------------------------------------------------------------//
2021-01-06 07:37:54 +00:00
private void showSymbolsLeft (StringBuilder fullText, Map<String, List<Integer>> map,
2021-01-04 10:13:31 +00:00
String heading)
// ---------------------------------------------------------------------------------//
{
2021-01-06 07:37:54 +00:00
heading (fullText, formatLeft, heading);
2021-01-04 10:13:31 +00:00
2021-01-06 07:37:54 +00:00
for (String symbol : map.keySet ()) // left-justify strings
2021-01-07 02:56:20 +00:00
appendLineNumbers (fullText, String.format (formatLeft, symbol), map.get (symbol));
2021-01-04 10:13:31 +00:00
}
2021-01-05 08:59:36 +00:00
// ---------------------------------------------------------------------------------//
2021-01-06 07:37:54 +00:00
private void showSymbolsRight (StringBuilder fullText, Map<Integer, List<Integer>> map,
String heading)
2021-01-05 08:59:36 +00:00
// ---------------------------------------------------------------------------------//
{
2021-01-06 07:37:54 +00:00
heading (fullText, formatRight, heading);
2021-01-05 08:59:36 +00:00
2021-01-06 07:37:54 +00:00
for (Integer symbol : map.keySet ()) // right-justify integers
2021-01-07 02:56:20 +00:00
appendLineNumbers (fullText, String.format (formatRight, symbol), map.get (symbol));
}
// ---------------------------------------------------------------------------------//
private void appendLineNumbers (StringBuilder fullText, String symbol,
List<Integer> lineNumbers)
// ---------------------------------------------------------------------------------//
{
StringBuilder text = new StringBuilder ();
text.append (symbol);
for (int lineNo : lineNumbers)
2021-01-06 01:36:41 +00:00
{
2021-01-07 02:56:20 +00:00
if (text.length () > underline.length () - maxDigits + longestVarName)
2021-01-06 01:36:41 +00:00
{
2021-01-07 02:56:20 +00:00
fullText.append (text);
fullText.append ("\n");
text.setLength (0);
text.append (String.format (formatRight, ""));
2021-01-06 01:36:41 +00:00
}
2021-01-07 02:56:20 +00:00
text.append (String.format (formatLineNumber, lineNo));
2021-01-06 01:36:41 +00:00
}
2021-01-07 02:56:20 +00:00
if (text.length () > longestVarName + 3)
fullText.append (text + "\n");
2021-01-05 08:59:36 +00:00
}
2021-01-04 10:13:31 +00:00
// ---------------------------------------------------------------------------------//
2021-01-06 01:36:41 +00:00
private int getLongestName ()
2020-12-30 03:06:50 +00:00
// ---------------------------------------------------------------------------------//
{
int longestName = 0;
2021-01-04 10:13:31 +00:00
2021-01-06 01:36:41 +00:00
longestName = getLongestName (symbolLines, longestName);
longestName = getLongestName (arrayLines, longestName);
longestName = getLongestName (functionLines, longestName);
return longestName;
}
// ---------------------------------------------------------------------------------//
private int getLongestName (Map<String, List<Integer>> map, int longestName)
// ---------------------------------------------------------------------------------//
{
2021-01-04 10:13:31 +00:00
for (String symbol : map.keySet ())
2020-12-30 03:06:50 +00:00
if (symbol.length () > longestName)
longestName = symbol.length ();
2021-01-04 10:13:31 +00:00
2020-12-30 03:06:50 +00:00
return longestName;
}
2020-12-26 08:32:40 +00:00
// ---------------------------------------------------------------------------------//
2021-01-01 00:49:42 +00:00
private void wrapPrint (StringBuilder fullText, StringBuilder text, String lineText)
2020-12-26 08:32:40 +00:00
// ---------------------------------------------------------------------------------//
{
2021-01-01 00:49:42 +00:00
if (true) // new method
{
List<String> lines = splitPrint (lineText);
if (lines != null)
{
int offset = text.indexOf ("PRINT");
if (offset < 0)
offset = text.indexOf ("INPUT");
String fmt = "%-" + offset + "." + offset + "s%s%n";
String padding = text.substring (0, offset);
for (String s : lines)
{
fullText.append (String.format (fmt, padding, s));
padding = "";
}
}
else
fullText.append (text + "\n");
}
else // old method
{
int first = text.indexOf ("\"") + 1;
int last = text.indexOf ("\"", first + 1) - 1;
if ((last - first) > basicPreferences.wrapPrintAt)
{
int ptr = first + basicPreferences.wrapPrintAt;
do
{
fullText.append (text.substring (0, ptr)
+ "\n ".substring (0, first + 1));
text.delete (0, ptr);
ptr = basicPreferences.wrapPrintAt;
} while (text.length () > basicPreferences.wrapPrintAt);
}
fullText.append (text + "\n");
}
2020-12-26 08:32:40 +00:00
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private List<String> splitPrint (String line)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
int first = line.indexOf ("\"") + 1;
int last = line.indexOf ("\"", first + 1) - 1;
if (first != 7 || (last - first) <= basicPreferences.wrapPrintAt)
return null;
int charsLeft = last - first + 1;
List<String> lines = new ArrayList<> ();
String padding = line.substring (0, 7);
line = line.substring (7);
String sub;
while (true)
{
if (line.length () >= basicPreferences.wrapPrintAt)
{
sub = line.substring (0, basicPreferences.wrapPrintAt);
line = line.substring (basicPreferences.wrapPrintAt);
}
else
{
sub = line;
line = "";
}
String subline = padding + sub;
charsLeft -= basicPreferences.wrapPrintAt;
if (charsLeft > 0)
lines.add (subline);
else
{
lines.add (subline + line);
break;
}
padding = " ";
}
return lines;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2020-11-22 01:04:27 +00:00
private List<String> splitLine (String line, int wrapLength, char breakChar)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2020-11-22 01:04:27 +00:00
int firstSpace = 0;
while (firstSpace < line.length () && line.charAt (firstSpace) != ' ')
++firstSpace;
2020-12-24 08:41:35 +00:00
List<String> lines = new ArrayList<> ();
2020-11-22 01:04:27 +00:00
while (line.length () > wrapLength)
2019-08-09 23:45:49 +00:00
{
2020-11-22 01:04:27 +00:00
int max = Math.min (wrapLength, line.length () - 1);
while (max > 0 && line.charAt (max) != breakChar)
2019-08-09 23:45:49 +00:00
--max;
if (max == 0)
break;
2020-12-24 08:41:35 +00:00
lines.add (line.substring (0, max + 1));
2020-11-22 01:04:27 +00:00
line = " ".substring (0, firstSpace + 1) + line.substring (max + 1);
2019-08-09 23:45:49 +00:00
}
2020-12-24 08:41:35 +00:00
lines.add (line);
2020-12-31 09:50:53 +00:00
return lines;
}
2020-12-24 08:41:35 +00:00
// ---------------------------------------------------------------------------------//
private List<String> splitDim (String line)
// ---------------------------------------------------------------------------------//
{
List<String> lines = new ArrayList<> ();
2020-12-25 04:30:26 +00:00
Pattern p = Pattern.compile ("[A-Z][A-Z0-9]*[$%]?\\([0-9,]*\\)[,:]?");
2020-12-24 08:41:35 +00:00
Matcher m = p.matcher (line);
while (m.find ())
lines.add (" " + m.group ());
if (lines.size () > 0)
lines.set (0, "DIM " + lines.get (0).trim ());
return lines;
2019-08-09 23:45:49 +00:00
}
2020-11-22 01:04:27 +00:00
// ---------------------------------------------------------------------------------//
2020-12-25 04:30:26 +00:00
private void addSplitLines (List<String> lines, StringBuilder text, int indent)
2020-11-22 01:04:27 +00:00
// ---------------------------------------------------------------------------------//
{
boolean first = true;
for (String line : lines)
{
if (first)
{
first = false;
text.append (line);
}
else
2020-12-25 04:30:26 +00:00
text.append (
"\n ".substring (0, indent) + line);
2020-11-22 01:04:27 +00:00
}
}
2020-12-25 04:30:26 +00:00
// ---------------------------------------------------------------------------------//
private int getIndent (StringBuilder fullText)
// ---------------------------------------------------------------------------------//
{
int ptr = fullText.length () - 1;
int indent = 0;
while (ptr >= 0 && fullText.charAt (ptr) != '\n')
{
--ptr;
++indent;
}
return indent;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private int countChars (StringBuilder text, byte ch)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
int total = 0;
for (int i = 0; i < text.length (); i++)
if (text.charAt (i) == ch)
total++;
return total;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private String getBase (SourceLine line)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2020-12-23 10:34:18 +00:00
boolean isTarget = gotoLines.containsKey (line.lineNumber)
|| gosubLines.containsKey (line.lineNumber);
2019-08-09 23:45:49 +00:00
if (!basicPreferences.showTargets)
{
if (!isTarget && basicPreferences.onlyShowTargetLineNumbers)
return " ";
2019-08-09 23:45:49 +00:00
return String.format (" %5d", line.lineNumber);
}
2019-08-09 23:45:49 +00:00
String lineNumberText = String.format ("%5d", line.lineNumber);
SubLine subline = line.sublines.get (0);
String c1 = " ", c2 = " ";
2021-01-08 02:03:12 +00:00
if (subline.is (TOKEN_GOSUB) || (subline.is (TOKEN_ON) && subline.has (TOKEN_GOSUB)))
2019-08-09 23:45:49 +00:00
c1 = "<<";
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_GOTO)
|| (subline.is (TOKEN_ON) && subline.has (TOKEN_GOTO)))
2019-08-09 23:45:49 +00:00
c1 = " <";
2020-12-29 00:45:43 +00:00
2020-12-23 10:34:18 +00:00
if (gotoLines.containsKey (line.lineNumber))
2019-08-09 23:45:49 +00:00
c2 = "> ";
2020-12-23 10:34:18 +00:00
if (gosubLines.containsKey (line.lineNumber))
2019-08-09 23:45:49 +00:00
c2 = ">>";
if (c1.equals (" ") && !c2.equals (" "))
c1 = "--";
if (!c1.equals (" ") && c2.equals (" "))
c2 = "--";
if (!isTarget && basicPreferences.onlyShowTargetLineNumbers)
2019-08-09 23:45:49 +00:00
lineNumberText = "";
2019-08-09 23:45:49 +00:00
return String.format ("%s%s %s", c1, c2, lineNumberText);
}
// Decide whether the current subline needs to be aligned on its equals sign. If so,
// and the column hasn't been calculated, read ahead to find the highest position.
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private int alignEqualsPosition (SubLine subline, int currentAlignPosition)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2021-01-03 03:04:24 +00:00
if (subline.equalsPosition > 0) // does the line have an equals sign?
2019-08-09 23:45:49 +00:00
{
if (currentAlignPosition == 0)
2020-11-20 09:19:51 +00:00
currentAlignPosition = findHighest (subline); // examine following sublines
2019-08-09 23:45:49 +00:00
return currentAlignPosition;
}
return 0; // reset it
}
// The IF processing is so that any assignment that is being aligned doesn't continue
// to the next full line (because the indentation has changed).
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private int findHighest (SubLine startSubline)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
boolean started = false;
2021-01-03 03:04:24 +00:00
int highestAssign = startSubline.equalsPosition;
2020-12-29 00:45:43 +00:00
2019-08-09 23:45:49 +00:00
fast: for (SourceLine line : sourceLines)
{
boolean inIf = false;
for (SubLine subline : line.sublines)
{
if (started)
{
// Stop when we come to a line without an equals sign (except for non-split REMs).
// Lines that start with a REM always break.
2021-01-03 03:04:24 +00:00
if (subline.equalsPosition == 0
2019-08-09 23:45:49 +00:00
// && (splitRem || !subline.is (TOKEN_REM) || subline.isFirst ()))
&& (basicPreferences.splitRem || !subline.isJoinableRem ()))
break fast; // of champions
2021-01-03 03:04:24 +00:00
if (subline.equalsPosition > highestAssign)
highestAssign = subline.equalsPosition;
2019-08-09 23:45:49 +00:00
}
else if (subline == startSubline)
started = true;
2021-01-08 02:03:12 +00:00
else if (subline.is (TOKEN_IF))
2019-08-09 23:45:49 +00:00
inIf = true;
}
if (started && inIf)
break;
}
return highestAssign;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2020-12-19 05:42:25 +00:00
private String getHexText ()
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
if (buffer.length < 2)
return super.getHexDump ();
StringBuilder pgm = new StringBuilder ();
if (basicPreferences.showHeader)
addHeader (pgm);
int ptr = 0;
int offset = Utility.unsignedShort (buffer, 0);
2019-08-09 23:45:49 +00:00
int programLoadAddress = offset - getLineLength (0);
while (ptr <= endPtr) // stop at the same place as the source listing
{
int length = getLineLength (ptr);
if (length == 0)
{
pgm.append (
HexFormatter.formatNoHeader (buffer, ptr, 2, programLoadAddress + ptr));
ptr += 2;
break;
}
if (ptr + length < buffer.length)
pgm.append (
HexFormatter.formatNoHeader (buffer, ptr, length, programLoadAddress + ptr)
+ "\n\n");
ptr += length;
}
if (ptr < buffer.length)
{
int length = buffer.length - ptr;
pgm.append ("\n\n");
pgm.append (
HexFormatter.formatNoHeader (buffer, ptr, length, programLoadAddress + ptr));
}
return pgm.toString ();
}
2021-01-03 05:06:51 +00:00
// A REM statement might conceal an assembler routine
// ---------------------------------------------------------------------------------//
private String[] getRemAssembler (SubLine subline)
// ---------------------------------------------------------------------------------//
{
AssemblerProgram program = new AssemblerProgram ("REM assembler",
subline.getBuffer (), getLoadAddress () + subline.startPtr + 1);
return program.getAssembler ().split ("\n");
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private void addHeader (StringBuilder pgm)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
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 ()));
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2020-12-30 03:06:50 +00:00
int getLoadAddress ()
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
int programLoadAddress = 0;
if (buffer.length > 1)
{
2020-11-20 09:19:51 +00:00
int offset = Utility.unsignedShort (buffer, 0);
2019-08-09 23:45:49 +00:00
programLoadAddress = offset - getLineLength (0);
}
return programLoadAddress;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private int getLineLength (int ptr)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
int offset = Utility.unsignedShort (buffer, ptr);
2019-08-09 23:45:49 +00:00
if (offset == 0)
return 0;
ptr += 4; // skip offset and line number
int length = 5;
while (ptr < buffer.length && buffer[ptr++] != 0)
length++;
return length;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private void popLoopVariables (Stack<String> loopVariables, SubLine subline)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2020-12-29 00:45:43 +00:00
if (subline.nextVariables.length == 0) // naked NEXT
2019-08-09 23:45:49 +00:00
{
if (loopVariables.size () > 0)
loopVariables.pop ();
}
else
2020-12-29 00:45:43 +00:00
for (String variable : subline.nextVariables) // e.g. NEXT X,Y,Z
2019-08-09 23:45:49 +00:00
while (loopVariables.size () > 0)
if (sameVariable (variable, loopVariables.pop ()))
break;
}
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
private boolean sameVariable (String v1, String v2)
2020-09-13 00:22:49 +00:00
// ---------------------------------------------------------------------------------//
2019-08-09 23:45:49 +00:00
{
2021-01-03 03:04:24 +00:00
return getUniqueName (v1).equals (getUniqueName (v2));
}
// ---------------------------------------------------------------------------------//
2021-01-05 03:24:23 +00:00
private void checkUniqueName (String symbol, Map<String, List<String>> map)
2021-01-03 03:04:24 +00:00
// ---------------------------------------------------------------------------------//
{
String uniqueName = getUniqueName (symbol);
2021-01-05 03:24:23 +00:00
List<String> usage = map.get (uniqueName);
2021-01-03 03:04:24 +00:00
if (usage == null)
{
usage = new ArrayList<> ();
2021-01-05 03:24:23 +00:00
map.put (uniqueName, usage);
2021-01-03 03:04:24 +00:00
}
if (!usage.contains (symbol))
usage.add (symbol);
}
// ---------------------------------------------------------------------------------//
private String getUniqueName (String symbol)
// ---------------------------------------------------------------------------------//
{
int ptr = symbol.length () - 1;
2021-01-03 05:06:51 +00:00
2021-01-03 03:04:24 +00:00
if (symbol.charAt (ptr) == Utility.ASCII_DOLLAR // string
|| symbol.charAt (ptr) == Utility.ASCII_PERCENT) // integer
ptr--;
2021-01-03 05:06:51 +00:00
return (ptr <= 1) ? symbol : symbol.substring (0, 2) + symbol.substring (ptr + 1);
2019-08-09 23:45:49 +00:00
}
}